Aug 19 - Re-evaluation

This commit is contained in:
2026-08-19 16:26:32 -04:00
parent 7efc9bb572
commit a4a8d84801
4 changed files with 57 additions and 16 deletions
+45 -8
View File
@@ -423,7 +423,7 @@ def _scope_errors(template_id, facility_id, inspector_id):
# role: the dropdown is a UI hint, this is the boundary, and a stale page
# (or a crafted POST) must not slip an out-of-contract assignee through.
if inspector_id:
allowed = {u.id for u in _inspectors_for_project(
allowed = {u.id for u in _assignable_inspectors(
facility.project_id if facility else None)}
if inspector_id not in allowed:
logger.warning(
@@ -435,17 +435,15 @@ def _scope_errors(template_id, facility_id, inspector_id):
contract = (facility.project.name
if facility is not None and facility.project else None)
if contract:
# Name the fix: an empty list here usually means the contract
# simply has no inspectors assigned yet, which is a setup step,
# not a mistake in this form.
errors.append(
f'{name} is not assigned to {contract}. Choose an inspector '
f'who works on that contract, or assign them to it first '
f'(Admin \u2192 Users \u2192 Assign Contracts).')
else:
# Only reachable for a Customer Director (staff get the
# fallback pool above), whose scope IS the contract.
errors.append(
'That facility is not on a contract, so no inspector can be '
'assigned to work there. Put the facility on a contract first.')
'That inspector does not work on your contracts.')
template = db.session.get(InspectionTemplate, template_id) if template_id else None
if template is not None and facility is not None:
@@ -505,6 +503,45 @@ def _inspectors_for_project(project_id):
return uniq
def _all_active_inspectors():
"""Every active inspector, either role. Never managers (see below)."""
return (User.query
.filter(User.role.in_(User.INSPECTOR_ROLES), User.active == True)
.order_by(User.full_name, User.username)
.all())
def _assignable_inspectors(project_id):
"""Who may be assigned a schedule at a facility on *project_id*.
Contract-scoped, with ONE fallback: when the contract has no inspectors
assigned — or the facility is on no contract at all — our own staff get the
full active inspector pool instead of an empty list.
The fallback exists because the strict rule alone makes scheduling
impossible until someone wires up InspectorAssignment rows, and it fails
with an error about a screen the person may not have thought about. A
facility legitimately has no contract (`facilities.project_id` is nullable),
and that must not become "no inspections can be planned here".
It is deliberately NOT offered to a Customer Director: for them the contract
boundary is a confidentiality boundary, and widening it on a setup gap would
hand one customer another customer's inspectors — the exact leak this
scoping closes (rule 93). They get the empty list and an actionable message.
Managers (admin / director / project_manager) are never in either list. A
schedule names whoever must go and do the work; a manager who intends to do
it holds a contract assignment like anyone else.
"""
scoped = _inspectors_for_project(project_id)
if scoped or _is_customer_director(current_user):
return scoped
logger.info(
'SCHED INSP | no inspectors assigned to contract %s — falling back to '
'the full inspector pool for %s', project_id, current_user.username)
return _all_active_inspectors()
# ── AJAX: inspectors working on a contract ──────────────────────────────────
@bp.route('/inspectors-for-contract/<int:project_id>')
@@ -520,7 +557,7 @@ def inspectors_for_contract(project_id):
return jsonify([
{'id': u.id,
'name': u.display_name + (' (Customer)' if u.is_external_inspector else '')}
for u in _inspectors_for_project(project_id)
for u in _assignable_inspectors(project_id)
])
@@ -650,7 +687,7 @@ def _form_choices(project_id=None):
# contract change. On edit and on a re-render after a validation error the
# contract is known here, so the saved assignee is present in the markup
# before that call returns.
inspectors = _inspectors_for_project(project_id)
inspectors = _assignable_inspectors(project_id)
return templates, facilities, inspectors