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:
@@ -6,6 +6,7 @@ than packet state; `can()` skips those, because they are inputs the user gives w
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Callable, Mapping
|
||||
from typing import Any
|
||||
|
||||
@@ -30,7 +31,10 @@ MESSAGES: dict[str, str] = {
|
||||
"qc_book_spool_location": "Confirm the book and the spool, and give the location.",
|
||||
"qc_missing_empty": "Q.C. is not finished.",
|
||||
"reason_given": "Give a reason of at least 8 characters.",
|
||||
"vclosed_if_databook": "Close the job in VISUAL first.",
|
||||
"closed_in_visual": "Close the job in VISUAL first.",
|
||||
"itp_complete": "Complete the unit's ITP first. Q.C. signs it off on the ITP page.",
|
||||
"shorts_approved": "Every short must be received and approved by the supervisor first.",
|
||||
"ncr_given": "Enter the NCR number of the reject first.",
|
||||
"co_staged_if_dwg_change": "Stage the new drawing for the change order first.",
|
||||
"route_given": "Choose Data Book or Shipping.",
|
||||
"target_dept_given": "Choose a different department.",
|
||||
@@ -45,10 +49,12 @@ INPUT_ONLY: frozenset[str] = frozenset(
|
||||
"reason_given",
|
||||
"qc_book_spool_location",
|
||||
"co_staged_if_dwg_change",
|
||||
"ncr_given",
|
||||
}
|
||||
)
|
||||
|
||||
ROUTES = ("databook", "shipping")
|
||||
RX_NCR = re.compile(r"^NCR-\d{4}-\d{4}$") # domain/ncr.py validates and normalises; this only checks the form
|
||||
|
||||
|
||||
def _fail(name: str, ok: bool) -> str | None:
|
||||
@@ -108,8 +114,36 @@ def reason_given(state: PacketState, extra: Mapping[str, Any]) -> str | None:
|
||||
return _fail("reason_given", reason_ok(extra.get("reason")) is not None)
|
||||
|
||||
|
||||
def vclosed_if_databook(state: PacketState, extra: Mapping[str, Any]) -> str | None:
|
||||
return _fail("vclosed_if_databook", state.routed_to != "databook" or state.vclosed)
|
||||
def closed_in_visual(state: PacketState, extra: Mapping[str, Any]) -> str | None:
|
||||
"""FILE waits for the job to be closed in VISUAL (C5).
|
||||
|
||||
A VISUAL packet, on either route: the feed sets vclosed when it sees every work order closed, and no work order
|
||||
may show open. A hand packet has no feed state: on the data book route Clerical confirms the close by hand."""
|
||||
if state.source_kind != "visual":
|
||||
return _fail("closed_in_visual", state.routed_to != "databook" or state.vclosed)
|
||||
if state.visual_open_wos:
|
||||
wos = ", ".join(state.visual_open_wos)
|
||||
return f"VISUAL still shows work orders open on this packet: {wos}. Filing waits until VISUAL closes them."
|
||||
if not state.vclosed:
|
||||
return "Filing waits until the VISUAL feed sees the job closed."
|
||||
return None
|
||||
|
||||
|
||||
def itp_complete(state: PacketState, extra: Mapping[str, Any]) -> str | None:
|
||||
"""A unit with no ITP does not block (C3)."""
|
||||
return _fail("itp_complete", not state.itp_open)
|
||||
|
||||
|
||||
def shorts_approved(state: PacketState, extra: Mapping[str, Any]) -> str | None:
|
||||
"""No open short and no received short without the supervisor's approval (C7)."""
|
||||
return _fail("shorts_approved", state.shorts_unapproved <= 0)
|
||||
|
||||
|
||||
def ncr_given(state: PacketState, extra: Mapping[str, Any]) -> str | None:
|
||||
"""After a Q.C. reject with no NCR number, the accept carries one (C2). State plus input, so can() skips it."""
|
||||
if not state.ncr_required:
|
||||
return None
|
||||
return _fail("ncr_given", bool(RX_NCR.match(clean_text(extra.get("ncr"), 100).upper())))
|
||||
|
||||
|
||||
def co_staged_if_dwg_change(state: PacketState, extra: Mapping[str, Any]) -> str | None:
|
||||
@@ -148,7 +182,10 @@ PRECONDITIONS: dict[str, Precondition] = {
|
||||
"qc_book_spool_location": qc_book_spool_location,
|
||||
"qc_missing_empty": qc_missing_empty,
|
||||
"reason_given": reason_given,
|
||||
"vclosed_if_databook": vclosed_if_databook,
|
||||
"closed_in_visual": closed_in_visual,
|
||||
"itp_complete": itp_complete,
|
||||
"shorts_approved": shorts_approved,
|
||||
"ncr_given": ncr_given,
|
||||
"co_staged_if_dwg_change": co_staged_if_dwg_change,
|
||||
"route_given": route_given,
|
||||
"target_dept_given": target_dept_given,
|
||||
|
||||
Reference in New Issue
Block a user