Complete Non-Conformance Report system replacing the PowerApps/SharePoint prototype: FastAPI + SQLAlchemy 2 (async) + Alembic + MySQL 8 backend, React 18 + Vite + TypeScript + MUI frontend, Entra ID auth (MSAL / JWKS, group-gated), Microsoft Graph delegated Mail.Send notifications (OBO), six-stage workflow state machine with server-side enforcement, atomic NCR-YYYY-NNNN numbering, attachments with camera capture, immutable field-level audit trail, admin reopen, reports + CSV export, WeasyPrint PDF traveler, Power BI reporting views + read-only DB user, documented VISUAL ERP job-lookup stub, pytest suite (26 tests), docker-compose deployment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
"""Domain constants: roles, workflow stages, and the transition map.
|
|
|
|
Workflow note: an NCR in the "New Request" stage is awaiting Initial
|
|
Disposition — the initial-disposition review is the action a Disposition
|
|
Authority performs on a New Request, and it moves the NCR either to
|
|
Secondary Disposition (when secondary review is required) or straight to
|
|
Operations. All transitions are validated server-side against
|
|
ALLOWED_TRANSITIONS; anything else is rejected with HTTP 409.
|
|
"""
|
|
from enum import Enum
|
|
|
|
|
|
class Role(str, Enum):
|
|
REQUESTER = "requester"
|
|
DISPOSITION_AUTHORITY = "disposition_authority"
|
|
SECONDARY_DISPOSITION_AUTHORITY = "secondary_disposition_authority"
|
|
OPERATIONS = "operations"
|
|
QC_INSPECTOR = "qc_inspector"
|
|
COSTING = "costing"
|
|
ADMIN = "admin"
|
|
|
|
|
|
ALL_ROLES: set[str] = {r.value for r in Role}
|
|
|
|
ROLE_LABELS: dict[str, str] = {
|
|
Role.REQUESTER: "Requester",
|
|
Role.DISPOSITION_AUTHORITY: "Disposition Authority",
|
|
Role.SECONDARY_DISPOSITION_AUTHORITY: "Secondary Disposition Authority",
|
|
Role.OPERATIONS: "Operations",
|
|
Role.QC_INSPECTOR: "QC Inspector",
|
|
Role.COSTING: "Costing",
|
|
Role.ADMIN: "Admin",
|
|
}
|
|
|
|
|
|
class Stage(str, Enum):
|
|
NEW_REQUEST = "new_request"
|
|
SECONDARY_DISPOSITION = "secondary_disposition"
|
|
OPERATIONS = "operations"
|
|
QC_INSPECTION = "qc_inspection"
|
|
COSTING = "costing"
|
|
CLOSED = "closed"
|
|
|
|
|
|
STAGE_LABELS: dict[str, str] = {
|
|
Stage.NEW_REQUEST: "New Request",
|
|
Stage.SECONDARY_DISPOSITION: "Secondary Disposition",
|
|
Stage.OPERATIONS: "Operations",
|
|
Stage.QC_INSPECTION: "QC Inspection",
|
|
Stage.COSTING: "Costing",
|
|
Stage.CLOSED: "Closed",
|
|
}
|
|
|
|
# Stages an admin may reopen a closed NCR back into.
|
|
REOPEN_TARGET_STAGES: list[Stage] = [
|
|
Stage.NEW_REQUEST,
|
|
Stage.SECONDARY_DISPOSITION,
|
|
Stage.OPERATIONS,
|
|
Stage.QC_INSPECTION,
|
|
Stage.COSTING,
|
|
]
|
|
|
|
ALLOWED_TRANSITIONS: dict[Stage, set[Stage]] = {
|
|
Stage.NEW_REQUEST: {Stage.SECONDARY_DISPOSITION, Stage.OPERATIONS},
|
|
Stage.SECONDARY_DISPOSITION: {Stage.OPERATIONS},
|
|
Stage.OPERATIONS: {Stage.QC_INSPECTION},
|
|
Stage.QC_INSPECTION: {Stage.COSTING},
|
|
Stage.COSTING: {Stage.CLOSED},
|
|
# Reopen (admin only, reason required) — enforced separately.
|
|
Stage.CLOSED: set(REOPEN_TARGET_STAGES),
|
|
}
|
|
|
|
# Role allowed to act on the NCR in each stage (Admin is always allowed;
|
|
# Secondary Disposition additionally requires being an assigned authority).
|
|
STAGE_ACTING_ROLE: dict[Stage, Role] = {
|
|
Stage.NEW_REQUEST: Role.DISPOSITION_AUTHORITY,
|
|
Stage.SECONDARY_DISPOSITION: Role.SECONDARY_DISPOSITION_AUTHORITY,
|
|
Stage.OPERATIONS: Role.OPERATIONS,
|
|
Stage.QC_INSPECTION: Role.QC_INSPECTOR,
|
|
Stage.COSTING: Role.COSTING,
|
|
}
|
|
|
|
# Owners to notify when an admin reopens an NCR into a given stage.
|
|
STAGE_OWNER_ROLE: dict[Stage, Role] = STAGE_ACTING_ROLE
|