Jul 30 - Allow customer to flag follow-up a inspection

This commit is contained in:
2026-07-30 20:44:38 -04:00
parent 5ed433aabe
commit 0808f8eaff
8 changed files with 256 additions and 21 deletions
+71 -16
View File
@@ -21,6 +21,7 @@ from app.utils.notifications import notify, notify_customers_for_facility, notif
from app.models.notification import (
EVENT_INSPECTION_DONE, EVENT_ISSUE_ASSIGNED,
EVENT_CUSTOMER_INSPECTION_DONE, EVENT_CUSTOMER_ISSUE_UPDATED,
EVENT_FOLLOWUP_REQUESTED,
)
from app.utils.audit import log_action, ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE, ACTION_EXPORT
from app.utils.scope import get_customer_scope, get_inspector_scope
@@ -1215,29 +1216,61 @@ def export_pdf(inspection_id):
@bp.route('/<int:inspection_id>/flag-followup', methods=['POST'])
@login_required
@supervisor_required
def flag_followup(inspection_id):
"""Mark an inspection as requiring a follow-up re-inspection."""
"""Mark an inspection as requiring a follow-up re-inspection.
Open to admin/director AND to customers for their own facilities — a client
unhappy with a result can ask for a re-inspection directly rather than
going through support. Every other role is refused.
Customers may only *request*: they cannot clear the flag (see
clear_followup, still admin/director) nor run the re-inspection itself.
"""
inspection = db.session.get(Inspection, inspection_id)
if inspection is None:
abort(404)
is_customer = current_user.role == 'customer'
if is_customer:
# Same facility scope as view() — a customer must not be able to reach
# another client's inspection with a crafted POST.
if inspection.facility_id not in (get_customer_scope(current_user) or []):
abort(403)
# Nothing to follow up on until the inspection has been submitted.
if inspection.status != 'completed':
flash('You can only request a follow-up on a completed inspection.', 'warning')
return redirect(url_for('inspections.view', inspection_id=inspection_id))
# Don't let a repeat request overwrite the note/attribution of a pending
# one — the flag is already raised and staff are already on it.
if inspection.follow_up_required:
flash('A follow-up has already been requested for this inspection.', 'info')
return redirect(url_for('inspections.view', inspection_id=inspection_id))
elif current_user.role not in ('admin', 'director'):
abort(403)
note = request.form.get('follow_up_note', '').strip() or None
inspection.follow_up_required = True
inspection.follow_up_note = note
inspection.follow_up_required = True
inspection.follow_up_note = note
inspection.follow_up_requested_by = current_user.id
inspection.follow_up_requested_at = now_eastern()
db.session.commit()
# Notify the original inspector so they see it on the iPad
note_suffix = f' Note: {note}' if note else ''
who = (f'The customer ({current_user.display_name})' if is_customer
else current_user.display_name)
body = (
f'{who} has requested a follow-up re-inspection '
f'of "{inspection.template.name}" at {inspection.facility.name}.{note_suffix}'
)
# Notify the original inspector so they see it on the iPad.
inspector = db.session.get(User, inspection.inspector_id)
if inspector and inspector.id != current_user.id:
note_suffix = f' Note: {note}' if note else ''
notify(
recipient = inspector,
title = f'Follow-Up Required: Inspection #{inspection_id}',
body = (
f'{current_user.username} has requested a follow-up re-inspection '
f'of "{inspection.template.name}" at {inspection.facility.name}.{note_suffix}'
),
body = body,
link = url_for('inspections.view', inspection_id=inspection_id),
inspection_id = inspection_id,
event_type = EVENT_INSPECTION_DONE,
@@ -1245,14 +1278,34 @@ def flag_followup(inspection_id):
)
db.session.commit()
# Route to the staff who action follow-ups. Going through notify_by_matrix
# rather than notifying managers directly keeps recipients admin-configurable
# and lets per-contract recipients fire too (rule 73). This matters most for
# a customer request: without it only the inspector would hear about it and
# nobody would be accountable for scheduling the re-inspection.
notify_by_matrix(
event_type = EVENT_FOLLOWUP_REQUESTED,
title = f'Follow-Up Requested: Inspection #{inspection_id}',
body = body,
link = url_for('inspections.view', inspection_id=inspection_id),
inspection_id = inspection_id,
facility_id = inspection.facility_id,
exclude_user_ids = {current_user.id,
inspector.id if inspector else None} - {None},
)
db.session.commit()
current_app.logger.info(
'INSPECTION FOLLOW-UP FLAGGED | id=%s | by=%s | note=%r',
inspection_id, current_user.username, note,
'INSPECTION FOLLOW-UP FLAGGED | id=%s | by=%s (%s) | note=%r',
inspection_id, current_user.username, current_user.role, note,
)
log_action(ACTION_UPDATE, 'Inspection', inspection_id,
f'{inspection.template.name} @ {inspection.facility.name}',
f'follow_up_required=True; note={note!r}')
flash('Follow-up inspection required flag set.', 'warning')
f'follow_up_required=True; by_role={current_user.role}; note={note!r}')
if is_customer:
flash('Follow-up re-inspection requested. The team has been notified.', 'success')
else:
flash('Follow-up inspection required flag set.', 'warning')
return redirect(url_for('inspections.view', inspection_id=inspection_id))
@@ -1264,8 +1317,10 @@ def clear_followup(inspection_id):
inspection = db.session.get(Inspection, inspection_id)
if inspection is None:
abort(404)
inspection.follow_up_required = False
inspection.follow_up_note = None
inspection.follow_up_required = False
inspection.follow_up_note = None
inspection.follow_up_requested_by = None
inspection.follow_up_requested_at = None
db.session.commit()
log_action(ACTION_UPDATE, 'Inspection', inspection_id,
f'{inspection.template.name} @ {inspection.facility.name}',