Migration 0003: projects, aliases, renumbers, units, packets and every child table, change orders, assets, documents, ITPs, station limits, department weld flag. Policy package: Principal, PacketState, Action rules, custody, preconditions, CO hold, order of checks, can/blocked/holder on reads. docs/POLICY.md with the tightenings table for Seth. Write pipeline: FOR UPDATE, CAS on status and dept, If-Match row versions, server-assigned numbers, field CAS, version bump, packet log, audit, NOTIFY, signatures, IT reasons, filed lock and HMAC seal. API: /api/packets (header, transitions, work orders, pages, docs, move, remove, BOM, shorts, welds, heat tags, heat table, stage, checklist, clock, warehouse stage, Q.C., change orders, log, seal), /api/projects, /api/units, /api/assets, /_blob. Frontend: Clerical home, projects and units, packet page with pdf.js and QR codes, hand draft, heat map, send to warehouse, department or Q.C. Tests: policy matrix, API, concurrency, schema, projects; Playwright Clerical draft scenario. Verified on docker-1 with the CI stack and the Samba DC. Co-Authored-By: Claude Fable 5.1 <[email protected]>
124 lines
4.4 KiB
Python
124 lines
4.4 KiB
Python
"""Custody: who holds a packet right now, and the Who predicates. A direct port of guard.js holds()."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
from .actions import ACTIVE, SUPPORT_CODES, Who
|
|
from .principal import PacketState, Principal
|
|
|
|
WORKER, LEAD, SUPERVISOR = 1, 2, 3
|
|
|
|
|
|
def works_on(principal: Principal, state: PacketState) -> bool:
|
|
"""A member of the packet's department whose station limits allow the packet's station.
|
|
|
|
Leads and supervisors always pass. A worker passes when they have no station limits or the limits hold
|
|
the packet's station. This is the old worksOn(); START and SUBMIT add the assigned-welder check.
|
|
"""
|
|
level = principal.level(state.dept)
|
|
if level == 0:
|
|
return False
|
|
if level >= LEAD:
|
|
return True
|
|
return not principal.station_limits or (
|
|
state.station_id is not None and state.station_id in principal.station_limits
|
|
)
|
|
|
|
|
|
def holds(principal: Principal, state: PacketState) -> bool:
|
|
"""True when the principal holds the packet in its current status. Admin is not a holder; see the bypass."""
|
|
st = state.status
|
|
if st == "draft":
|
|
return "CL" in principal.support
|
|
if st == "warehouse":
|
|
return "WH" in principal.support
|
|
if st in ("ready", "review"):
|
|
return principal.level(state.dept) >= LEAD
|
|
if st in ACTIVE:
|
|
return principal.level(state.dept) >= LEAD or works_on(principal, state)
|
|
if st == "qc":
|
|
return "QC" in principal.support
|
|
if st == "complete":
|
|
return "CL" in principal.support
|
|
return False
|
|
|
|
|
|
def holder_name(state: PacketState, dept_name: str | None = None, station_name: str | None = None) -> str:
|
|
"""Who has the packet, for messages. The department name falls back to its code."""
|
|
st = state.status
|
|
if st == "draft":
|
|
return "Clerical"
|
|
if st == "warehouse":
|
|
return "Warehouse"
|
|
if st == "qc":
|
|
return "Q.C."
|
|
if st == "complete":
|
|
return "Clerical (data book)"
|
|
if st == "filed":
|
|
return "the data book (filed)"
|
|
name = dept_name or state.dept
|
|
if st in ACTIVE and station_name:
|
|
return f"{name} · {station_name}"
|
|
return name
|
|
|
|
|
|
def _works_on_assigned(principal: Principal, state: PacketState) -> bool:
|
|
"""START and SUBMIT: a lead or above in the department, or the assigned welder within their station limits."""
|
|
level = principal.level(state.dept)
|
|
if level >= LEAD:
|
|
return True
|
|
return (
|
|
level == WORKER
|
|
and principal.user_id is not None
|
|
and state.welder_id is not None
|
|
and str(state.welder_id) == str(principal.user_id)
|
|
and works_on(principal, state)
|
|
)
|
|
|
|
|
|
def _co_verifier(principal: Principal, extra: Mapping[str, Any]) -> bool:
|
|
"""A supervisor of the department named in the request (extra["dept"])."""
|
|
dept = extra.get("dept")
|
|
if not isinstance(dept, str) or not dept:
|
|
return False
|
|
if dept in ("WH", "QC"):
|
|
return dept in principal.support_sup
|
|
if dept in SUPPORT_CODES:
|
|
return False # Clerical issues change orders; it never verifies them. PM has no supervisor.
|
|
return principal.level(dept) >= SUPERVISOR
|
|
|
|
|
|
def allows(who: Who, principal: Principal, state: PacketState, extra: Mapping[str, Any]) -> bool:
|
|
"""Evaluate one Who predicate. Admin is never allowed here except by Who.ADMIN; the bypass is a rule."""
|
|
if who is Who.CL:
|
|
return "CL" in principal.support
|
|
if who is Who.CL_SUP:
|
|
return "CL" in principal.support_sup
|
|
if who is Who.WH:
|
|
return "WH" in principal.support
|
|
if who is Who.WH_SUP:
|
|
return "WH" in principal.support_sup
|
|
if who is Who.QC:
|
|
return "QC" in principal.support
|
|
if who is Who.QC_SUP:
|
|
return "QC" in principal.support_sup
|
|
if who is Who.CL_OR_QC:
|
|
return "CL" in principal.support or "QC" in principal.support
|
|
if who is Who.HOLDER:
|
|
return holds(principal, state)
|
|
if who is Who.DEPT_LEAD:
|
|
return principal.level(state.dept) >= LEAD
|
|
if who is Who.DEPT_SUP:
|
|
return principal.level(state.dept) >= SUPERVISOR
|
|
if who is Who.WORKS_ON:
|
|
return _works_on_assigned(principal, state)
|
|
if who is Who.CO_VERIFIER:
|
|
return _co_verifier(principal, extra)
|
|
if who is Who.ADMIN:
|
|
return principal.admin
|
|
if who is Who.SYSTEM:
|
|
return principal.system is not None
|
|
return False
|