Phase 6: Q.C. inspection, project folders, packet documents and the digital ITP
ci / frontend (push) Successful in 37s
ci / server (push) Successful in 9m18s
ci / image (push) Successful in 35s
ci / e2e (push) Successful in 2m17s

The Q.C. screen (KPIs, boards, project folders), the Q.C. tab on the packet
page (check items from the qc_check_items settings row, visual exam per weld,
NDE, hardness with the NACE label, notes), accept with a route and a
signature gated by qc_missing_empty, reject with a reason or an NCR number
closing the inspection into qc_history, Q.C. folder documents per project and
unit with superseding revisions, packet documents as a closed set with
take-out reasons, the digital ITP per unit from the frozen template (line
initials with CAS, unsign, N/A, customer codes, rejects with NCR, block
signatures, sign-off with the PDF hook, admin reopen, the ITP log), hold-point
warnings by department page group, the scoped policy check for unit and
project writes, migration 0007, tests, Playwright scenarios and docs.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-10-17 20:08:53 -06:00
co-authored by Claude Fable 5.1
parent 8bb1f7ae4d
commit 00531cb329
64 changed files with 11715 additions and 260 deletions
+10 -1
View File
@@ -35,6 +35,7 @@ from ..policy.engine import MSG_FILED
from ..settings import SecretFileError
from ..text import LIMITS, clean_opt, reason_ok
from . import packets
from . import qc as qc_dom
log = logging.getLogger(__name__)
@@ -640,6 +641,7 @@ def _shop_texts(w: Write, a: policy.Action, to_dept: str, stamp: str | None = No
SHOP_TEXTS = frozenset(
{A.ASSIGN, A.UNASSIGN, A.PULL_BACK, A.START, A.SUBMIT, A.RETURN_TO_STATION, A.SEND_TO_QC, A.ROUTE_FROM_REVIEW}
)
QC_TEXTS = frozenset({A.QC_ACCEPT, A.QC_ACCEPT_AND_ROUTE, A.QC_REJECT})
def apply_transition(w: Write) -> None:
@@ -661,6 +663,8 @@ def apply_transition(w: Write) -> None:
stamp: str | None = None
if a in SHOP_TEXTS and a is not A.ASSIGN and a is not A.SUBMIT:
_shop_texts(w, a, to_dept) # before the side effects clear the station
if a in QC_TEXTS:
qc_dom.qc_texts(w, a, to_dept) # before the side effects close the inspection
if a in (A.SEND_TO_WAREHOUSE, A.VIS_AUTO_RELEASE):
_set_packet(w, "wh_skipped = false, wh_sent_at = now(), return_note = NULL")
@@ -748,7 +752,12 @@ def apply_transition(w: Write) -> None:
_open_stage(w, to_dept)
_set_packet(w, "station_id = NULL, welder_id = NULL, welder_stamp = NULL, return_note = NULL, routed_to = NULL")
elif a is A.QC_REJECT:
_set_result(w, _open_inspection(w), "rejected", reason)
# The reject closes the inspection into qc_history at once; the next SEND_TO_QC opens a fresh one.
insp = _open_inspection(w)
if insp is not None:
w.conn.execute("UPDATE qc_inspections SET ncr = %s WHERE id = %s", (ex.get("ncr") or None, insp["id"]))
insp = _set_result(w, insp, "rejected", reason)
_close_inspection(w, insp, from_dept)
_set_packet(w, "return_note = %s", (reason,))
if not packets.one(w.conn, "SELECT id FROM packet_stages WHERE packet_id = %s AND closed_at IS NULL", (w.id,)):
_open_stage(w, from_dept) # a draft sent straight to Q.C. has no stage yet