Aug 19 - Update code to catch up with ST

This commit is contained in:
2026-08-19 14:05:18 -04:00
parent c9984e7ae6
commit 12141c2f75
52 changed files with 3321 additions and 342 deletions
+52 -6
View File
@@ -3,17 +3,22 @@ from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from app.utils.time_utils import now_eastern
# Display labels for the role ENUM. 'external_inspector' would otherwise title
# case to "External Inspector" anyway, but the map keeps every label in one
# place for templates that show a role name.
# Display labels for the role ENUM — the single place a role's user-facing name
# is defined.
#
# The two customer-side roles are a LABEL-ONLY rename (same idea as rule 19,
# "Project" -> "Contract"): the stored ENUM values are still 'customer' and
# 'external_inspector', so no migration and no role check anywhere had to move.
# 'customer' -> "Customer Director" (portal access, CustomerAssignment scope)
# 'external_inspector' -> "Customer Inspector" (inspector powers, InspectorAssignment scope)
ROLE_LABELS = {
'admin': 'Admin',
'director': 'Director',
'project_manager': 'Project Manager',
'auditor': 'Auditor',
'inspector': 'Inspector',
'external_inspector': 'External Inspector',
'customer': 'Customer',
'external_inspector': 'Customer Inspector',
'customer': 'Customer Director',
}
@@ -38,6 +43,27 @@ class User(UserMixin, db.Model):
# the same way in Python and in Jinja (`current_user.is_inspector`).
INSPECTOR_ROLES = ('inspector', 'external_inspector')
# ── Customer-side roles (phase51) ────────────────────────────────
# Accounts that belong to the CUSTOMER, not to us. Both are created,
# invited, assigned and switched from Customer Management (/customers) —
# they never appear in User Management.
# 'customer' = Customer Director — portal access, read-mostly,
# scoped by CustomerAssignment.
# 'external_inspector' = Customer Inspector — full inspector capabilities,
# scoped by InspectorAssignment (see INSPECTOR_ROLES).
#
# CAUTION — this tuple is NOT interchangeable with `role == 'customer'`.
# A Customer Inspector is an INSPECTOR everywhere it matters: portal
# read-only gates, @customer_required, get_customer_scope(), support chat
# and the customer branch of every API scope check must keep testing
# `role == 'customer'` exactly. Use CUSTOMER_ROLES / is_customer_account
# ONLY for account-management surfaces (who is listed, invited, edited,
# assigned or switched under /customers). Widening a capability check to
# this tuple hands a third-party inspector the customer portal; narrowing
# an account-management check to 'customer' strands the inspectors in a
# page that no longer manages them.
CUSTOMER_ROLES = ('customer', 'external_inspector')
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), unique=True, nullable=False, index=True)
full_name = db.Column(db.String(150), nullable=True)
@@ -117,9 +143,29 @@ class User(UserMixin, db.Model):
@property
def is_external_inspector(self):
"""True only for third-party / customer-employed inspectors."""
"""True only for third-party / customer-employed inspectors.
Display name: "Customer Inspector". The attribute keeps its MT-15
name so the existing call sites stay put (the rename is a label,
never an identifier).
"""
return self.role == 'external_inspector'
@property
def is_customer_account(self):
"""True for BOTH customer-side roles — an account-management question.
Answers "is this account managed under /customers?", NOT "does this
account get the customer portal". For the latter keep testing
`role == 'customer'`. See the CUSTOMER_ROLES note above.
"""
return self.role in self.CUSTOMER_ROLES
@property
def is_customer_director(self):
"""True for the portal-side customer role ('customer')."""
return self.role == 'customer'
@property
def role_label(self):
"""Human-readable role name, used in staff-facing lists."""