First commit

This commit is contained in:
2026-06-26 09:04:34 -04:00
commit 77678ed724
166 changed files with 34842 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
"""
app/utils/scope.py
------------------
Facility-scoping utilities for the Janitorial QC portal.
get_customer_scope(user) -> list[int] | None
Facility IDs a customer may access via CustomerAssignment rows.
get_inspector_scope(user) -> list[int] | None
Facility IDs an inspector may access via InspectorAssignment rows.
Returns [] (empty list) when the inspector has no contract assignments,
meaning they see nothing (strict mode).
For non-customer / non-inspector roles both functions return None, signalling
that no facility-level scoping is required (full access applies).
"""
import logging
from app.models.project import CustomerAssignment
from app.models.facility import Facility
logger = logging.getLogger(__name__)
def get_customer_scope(user) -> list[int] | None:
"""Return the list of facility IDs accessible to a customer user.
Parameters
----------
user : User
The currently authenticated user.
Returns
-------
list[int]
Facility IDs the customer may access. May be empty if no assignments
exist yet — callers should treat an empty list as "no access".
None
Returned for non-customer roles, indicating unrestricted access.
"""
if user.role != 'customer':
return None # no scoping needed for internal staff
assignments = CustomerAssignment.query.filter_by(user_id=user.id).all()
if not assignments:
return []
# Separate direct facility assignments from project-level assignments
direct_facility_ids = {a.facility_id for a in assignments if a.facility_id}
project_ids = {a.project_id for a in assignments if not a.facility_id}
facility_ids = set(direct_facility_ids)
# Single bulk query for all project-scoped facilities — replaces the
# previous per-assignment Facility.query loop (N+1 pattern).
if project_ids:
project_facilities = (
Facility.query
.filter(
Facility.project_id.in_(project_ids),
Facility.active == True,
)
.all()
)
for f in project_facilities:
facility_ids.add(f.id)
logger.debug(
'SCOPE | customer_scope | user_id=%s username=%s facility_ids=%s',
user.id, user.username, sorted(facility_ids),
)
return sorted(facility_ids)
def get_inspector_scope(user) -> list[int] | None:
"""Return the list of facility IDs accessible to a contract-scoped inspector.
Parameters
----------
user : User
The currently authenticated user.
Returns
-------
list[int]
Facility IDs the inspector may access. An empty list means the
inspector has no contract assignments and should see nothing.
None
Returned for non-inspector roles, indicating unrestricted access.
"""
if user.role != 'inspector':
return None
from app.models.inspector_assignment import InspectorAssignment
project_ids = [
a.project_id
for a in InspectorAssignment.query.filter_by(user_id=user.id).all()
]
if not project_ids:
return [] # strict: no assignments = no access
facility_ids = [
f.id for f in Facility.query.filter(
Facility.project_id.in_(project_ids),
Facility.active == True,
).all()
]
logger.debug(
'SCOPE | inspector_scope | user_id=%s username=%s facility_ids=%s',
user.id, user.username, sorted(facility_ids),
)
return sorted(facility_ids)