Build Seth's changes C1 to C11 (Gitea issues 1, 8, 18, 23, 24, 25, 27, 31, 32, 33, 36)
ci / server (pull_request) Successful in 12m48s
ci / frontend (pull_request) Successful in 54s
ci / image (pull_request) Skipped
ci / e2e (pull_request) Successful in 4m38s

- 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:
2026-10-30 16:36:49 -06:00
co-authored by Claude Opus 5.5
parent 150c507a31
commit 70a97398f1
101 changed files with 3871 additions and 1098 deletions
@@ -170,6 +170,11 @@ class ReceiptBody(It):
note: str | None = Field(default=None, max_length=2000)
class ShortApproveBody(It):
sign: SignBody | None = None
note: str | None = Field(default=None, max_length=2000)
class WeldBody(It):
page: int | None = Field(default=None, ge=1, le=100_000)
fx: float | None = Field(default=None, ge=0, le=1, allow_inf_nan=False)
@@ -880,6 +885,75 @@ def receive_short(
return _read(d, pp, packet_id)
def _not_approved(conn: Any, receipt_id: int) -> None:
if conn.execute("SELECT 1 FROM short_approvals WHERE receipt_id = %s", (receipt_id,)).fetchone():
raise ApiError(409, "already_approved", "This receipt is already approved.")
@router.post("/{packet_id}/shorts/receipts/{receipt_id}/approve", dependencies=WRITE)
def approve_short_receipt(
packet_id: int,
receipt_id: int,
body: ShortApproveBody,
request: Request,
p: Principal = Depends(current_principal), # noqa: B008
d: Db = Depends(get_db), # noqa: B008
) -> dict[str, Any]:
"""The department supervisor approves one received short (C7): the part and its heat number match what the
warehouse issued. One approval per receipt; a second answers 409 already_approved."""
note = clean_opt(body.note, LIMITS["note"])
pp = _pp(d, p)
conn = d.conn
receipt = packets.one(
conn,
"SELECT r.*, b.item FROM short_receipts r JOIN bom_shorts s ON s.id = r.short_id"
" JOIN bom_lines b ON b.id = s.bom_line_id WHERE r.id = %s AND r.packet_id = %s",
(receipt_id, packet_id),
)
if receipt is None:
raise ApiError(404, "not_found", "No such short receipt on this packet.")
_not_approved(conn, receipt_id) # before the signature: a second approval costs no attempt
content = {
"action": str(A.SHORT_APPROVE),
"packet_id": packet_id,
"receipt_id": receipt_id,
"item": receipt["item"],
"qty": qty_text(receipt["qty"]),
"heat": receipt["heat"],
"note": note,
}
with packet_write(
d,
p,
packet_id,
A.SHORT_APPROVE,
request=request,
it_reason=body.it_reason,
sign=body.sign,
sign_content=content,
policy_principal=pp,
) as w:
_not_approved(conn, receipt_id) # again under the packet lock: a racing approval waits, then sees the first
conn.execute(
"INSERT INTO short_approvals (receipt_id, short_id, packet_id, dept_code, note, signature_id, by)"
" VALUES (%s, %s, %s, %s, %s, %s, %s)",
(
receipt_id,
receipt["short_id"],
packet_id,
w.state.dept,
note,
w.signature.id if w.signature else None,
w.user_id,
),
)
w.changed("bom.short")
w.detail.update({"item": receipt["item"], "receipt_id": receipt_id})
parts = [f"{qty_text(receipt['qty'])} pc", receipt["heat"] and f"heat {receipt['heat']}", note]
w.log(f"Short approved · item {receipt['item']}", " · ".join(x for x in parts if x))
return _read(d, pp, packet_id)
# ---------------------------------------------------------------- welds