Aug 25 - Implement new function allow Director (internal & customer) to assign and inspection to another inspector
This commit is contained in:
@@ -969,7 +969,9 @@ def view(inspection_id):
|
||||
'unchanged': sum(1 for r in rows if r['delta'] == 0),
|
||||
}
|
||||
|
||||
followup_assignees = _followup_assignees_for(inspection, current_user)
|
||||
return render_template('inspections/view.html',
|
||||
followup_assignees=followup_assignees,
|
||||
inspection=inspection,
|
||||
form_fields=form_fields,
|
||||
form_data=form_data,
|
||||
@@ -1394,6 +1396,46 @@ def _view_url(inspection_id):
|
||||
return url_for('inspections.view', inspection_id=inspection_id)
|
||||
|
||||
|
||||
def _followup_assignees_for(inspection, actor):
|
||||
"""Inspectors who may be handed this inspection's follow-up.
|
||||
|
||||
Contract-scoped, for the same reason the flag-issue list is (rule 93): a
|
||||
Customer Director must never see — let alone assign work to — another
|
||||
client's inspector, and one of our own directors picking the wrong name
|
||||
would leak this facility to an outsider.
|
||||
|
||||
Only the two INSPECTOR roles are offered: a follow-up is an inspection, and
|
||||
directors/PMs/auditors hold no InspectorAssignment, so they cannot be
|
||||
scoped to a contract and could not open the re-inspection anyway.
|
||||
|
||||
A facility with no contract yields nobody — fail-closed, leaving the
|
||||
follow-up with the original inspector.
|
||||
"""
|
||||
from app.models.inspector_assignment import InspectorAssignment
|
||||
|
||||
project_id = inspection.facility.project_id if inspection.facility else None
|
||||
if not project_id:
|
||||
return []
|
||||
|
||||
users = (
|
||||
User.query
|
||||
.join(InspectorAssignment, InspectorAssignment.user_id == User.id)
|
||||
.filter(
|
||||
InspectorAssignment.project_id == project_id,
|
||||
User.role.in_(User.INSPECTOR_ROLES),
|
||||
User.active == True,
|
||||
)
|
||||
.order_by(User.full_name, User.username)
|
||||
.all()
|
||||
)
|
||||
seen, out = set(), []
|
||||
for u in users: # the join repeats across assignments
|
||||
if u.id not in seen:
|
||||
seen.add(u.id)
|
||||
out.append(u)
|
||||
return out
|
||||
|
||||
|
||||
def _collect_inspection_photos(inspection):
|
||||
"""Relative storage keys owned by an inspection, for cleanup after delete.
|
||||
|
||||
@@ -1597,6 +1639,7 @@ def bulk_action():
|
||||
insp.follow_up_note = None
|
||||
insp.follow_up_requested_by = None
|
||||
insp.follow_up_requested_at = None
|
||||
insp.follow_up_assigned_to = None
|
||||
cleared.append(insp)
|
||||
changed += 1
|
||||
db.session.commit()
|
||||
@@ -1661,22 +1704,48 @@ def flag_followup(inspection_id):
|
||||
|
||||
note = request.form.get('follow_up_note', '').strip() or None
|
||||
|
||||
# ── Assignee (phase53) ────────────────────────────────────────────────
|
||||
# Optional. Blank keeps the original behaviour: the follow-up belongs to
|
||||
# the inspection's own inspector. Validated against the contract-scoped
|
||||
# list rather than trusted, so a crafted id cannot hand work to another
|
||||
# customer's inspector (and tell them this facility's name in the email).
|
||||
assignee_id = request.form.get('follow_up_assigned_to', type=int) or None
|
||||
if assignee_id:
|
||||
allowed = {u.id for u in _followup_assignees_for(inspection, current_user)}
|
||||
if assignee_id not in allowed:
|
||||
current_app.logger.warning(
|
||||
'FOLLOW-UP | out-of-contract assignee blocked | inspection=%s | '
|
||||
'assignee=%s | by=%s',
|
||||
inspection_id, assignee_id, current_user.username)
|
||||
flash('That inspector is not assigned to this facility\'s contract.',
|
||||
'danger')
|
||||
return redirect(_view_url(inspection_id))
|
||||
|
||||
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()
|
||||
inspection.follow_up_assigned_to = assignee_id
|
||||
db.session.commit()
|
||||
|
||||
note_suffix = f' Note: {note}' if note else ''
|
||||
who = (f'The customer ({current_user.display_name})' if is_customer
|
||||
else current_user.display_name)
|
||||
assigned_suffix = ''
|
||||
if inspection.follow_up_assignee:
|
||||
assigned_suffix = (f' It has been assigned to '
|
||||
f'{inspection.follow_up_assignee.display_name}.')
|
||||
body = (
|
||||
f'{who} has requested a follow-up re-inspection '
|
||||
f'of "{inspection.template.name}" at {inspection.facility.name}.{note_suffix}'
|
||||
f'of "{inspection.template.name}" at {inspection.facility.name}.'
|
||||
f'{assigned_suffix}{note_suffix}'
|
||||
)
|
||||
|
||||
# Notify the original inspector so they see it on the iPad.
|
||||
inspector = db.session.get(User, inspection.inspector_id)
|
||||
# Notify whoever now OWNS the follow-up — the assignee when one was named,
|
||||
# otherwise the original inspector (Inspection.follow_up_owner). Notifying
|
||||
# the original inspector for work that has been handed to someone else is
|
||||
# noise, and worse, it implies they are expected to do it.
|
||||
inspector = inspection.follow_up_owner
|
||||
if inspector and inspector.id != current_user.id:
|
||||
notify(
|
||||
recipient = inspector,
|
||||
@@ -1732,6 +1801,7 @@ def clear_followup(inspection_id):
|
||||
inspection.follow_up_note = None
|
||||
inspection.follow_up_requested_by = None
|
||||
inspection.follow_up_requested_at = None
|
||||
inspection.follow_up_assigned_to = None
|
||||
db.session.commit()
|
||||
log_action(ACTION_UPDATE, 'Inspection', inspection_id,
|
||||
f'{inspection.template.name} @ {inspection.facility.name}',
|
||||
|
||||
Reference in New Issue
Block a user