Build Seth's changes C1 to C11 (Gitea issues 1, 8, 18, 23, 24, 25, 27, 31, 32, 33, 36)
- C1 (issue 1): over target follows VISUAL ticket hours, not the timer. - C2 (issue 24): a Q.C. reject always needs a reason; accept needs an NCR number after a reject without one. - C3 (issue 25): an incomplete unit ITP blocks filing. - C4 (issue 27): prints open the print dialog on the page, no new tab. - C5 (issue 31): filing is blocked while VISUAL shows a work order open; the feed sets vclosed on VISUAL packets. - C6 (issue 8): the data book leaves out the history pages. - C7 (issue 18): the supervisor approves each received short; accept and filing wait for it. - C8 (issue 23): per-department checklists, edited by the department supervisor or Admin, frozen at START. - C9 (issue 32): Q.C. and Clerical may run the stamp check. - C10 (issue 33): the PM screen and reports are for PS-ProjectMgmt and Admin only. - C11 (issue 36): the Stall screen lists the packets assigned to the welder, at the supervisor's stall. Migration 0011. Docs: PLAN (also issue 9 confirmed, issue 4 asked), POLICY, SECURITY, SCHEMA, FEED, README, CHANGELOG. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -115,7 +115,6 @@ class TransitionBody(BaseModel):
|
||||
location: str | None = Field(default=None, max_length=500)
|
||||
for_sup_id: uuid.UUID | None = None
|
||||
ncr: str | None = Field(default=None, max_length=100)
|
||||
confirm_open_wos: bool | None = None
|
||||
sign: SignBody | None = None
|
||||
it_reason: str | None = Field(default=None, max_length=1000)
|
||||
|
||||
@@ -539,9 +538,9 @@ TRANSITION_FIELDS: dict[policy.Action, tuple[str, ...]] = {
|
||||
A.SEND_TO_QC: ("book", "spool", "location", "note"),
|
||||
A.ROUTE_FROM_REVIEW: ("dept", "note"),
|
||||
A.QC_REJECT: ("reason", "ncr"),
|
||||
A.QC_ACCEPT: ("routed_to", "note"),
|
||||
A.QC_ACCEPT_AND_ROUTE: ("dept", "note"),
|
||||
A.FILE: ("note", "confirm_open_wos"),
|
||||
A.QC_ACCEPT: ("routed_to", "note", "ncr"),
|
||||
A.QC_ACCEPT_AND_ROUTE: ("dept", "note", "ncr"),
|
||||
A.FILE: ("note",),
|
||||
A.ADMIN_REOPEN: ("reason",),
|
||||
}
|
||||
DEPT_ACTIONS = (A.RELEASE_TO_SHOP, A.ROUTE_FROM_REVIEW, A.QC_ACCEPT_AND_ROUTE)
|
||||
@@ -580,6 +579,8 @@ def transition(
|
||||
extra["for_sup_id"] = active_user(conn, extra["for_sup_id"], "supervisor")
|
||||
if action is A.QC_REJECT:
|
||||
_reject_reason(extra)
|
||||
elif "ncr" in extra:
|
||||
_accept_ncr(extra)
|
||||
if "stamp" in extra:
|
||||
# The assign sheet's stamp overrides the welder's own for this assignment; empty means use theirs.
|
||||
stamp = clean_text(extra["stamp"], 40)
|
||||
@@ -617,20 +618,25 @@ def transition(
|
||||
|
||||
|
||||
def _reject_reason(extra: dict[str, Any]) -> None:
|
||||
"""A Q.C. reject takes a reason, an NCR number, or both. The NCR stands in for the reason text: the reason the
|
||||
policy checks is "NCR-2026-0021" alone, or "NCR-2026-0021 · <reason>". Without an NCR the reason needs its usual
|
||||
8 characters."""
|
||||
"""A Q.C. reject always needs a detailed reason (8 characters or more, C2); an NCR number does not stand in for
|
||||
it. The reject may carry an NCR number too: the return note then reads "NCR-2026-0021 · <reason>"."""
|
||||
text = clean_text(extra.pop("reason", None), LIMITS["reason"])
|
||||
raw = clean_text(extra.pop("ncr", None), 100)
|
||||
ncr = ncrs.validate_ncr(raw) if raw else None
|
||||
if ncr:
|
||||
extra["ncr"] = ncr
|
||||
extra["reason"] = f"{ncr} · {text}" if text else ncr
|
||||
elif text:
|
||||
if text:
|
||||
extra["reason"] = text
|
||||
extra["reason_text"] = text
|
||||
|
||||
|
||||
def _accept_ncr(extra: dict[str, Any]) -> None:
|
||||
"""The NCR number an accept carries after a reject without one (C2). An empty one counts as not given."""
|
||||
raw = clean_text(extra.pop("ncr", None), 100)
|
||||
if raw:
|
||||
extra["ncr"] = ncrs.validate_ncr(raw)
|
||||
|
||||
|
||||
@router.post("/{packet_id}/reopen", dependencies=WRITE)
|
||||
def reopen(
|
||||
packet_id: int,
|
||||
@@ -958,9 +964,8 @@ def set_vclosed(
|
||||
"SELECT DISTINCT wo_base FROM packet_work_orders WHERE packet_id = %s ORDER BY 1", (packet_id,)
|
||||
)
|
||||
]
|
||||
still_open = packets.packet_open_wos(d.conn, w.packet)
|
||||
suffix = "VISUAL still shows open: " + ", ".join(still_open) if still_open else None
|
||||
w.note = " · ".join(x for x in (", ".join(wos), suffix) if x) or None
|
||||
# Only a hand packet gets here (C5): the feed closes a VISUAL packet, so there is no feed state to compare.
|
||||
w.note = ", ".join(wos) or None
|
||||
else:
|
||||
d.conn.execute("UPDATE packets SET vclosed_at = NULL, vclosed_by = NULL WHERE id = %s", (packet_id,))
|
||||
w.changed("vclosed")
|
||||
|
||||
Reference in New Issue
Block a user