From 70a97398f157184b5d7c08fb835022827c15e193 Mon Sep 17 00:00:00 2001 From: Spencer McGuire Date: Fri, 30 Oct 2026 13:33:51 -0600 Subject: [PATCH] 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 --- CHANGELOG.md | 19 +- README.md | 13 +- docs/FEED.md | 12 +- docs/PLAN.md | 68 +++-- docs/POLICY.md | 141 ++++++--- docs/SCHEMA.md | 25 +- docs/SECURITY.md | 30 +- frontend/src/App.tsx | 4 + frontend/src/api/admin.ts | 34 +-- frontend/src/api/checklists.ts | 59 ++++ frontend/src/api/packets.ts | 4 + frontend/src/api/stall.ts | 40 +++ frontend/src/api/stamp.ts | 15 + frontend/src/api/types.ts | 44 ++- frontend/src/components/Layout.tsx | 15 +- frontend/src/components/RoleSwitcher.test.tsx | 4 +- frontend/src/lib/filters.test.ts | 4 +- frontend/src/lib/filters.ts | 17 +- frontend/src/lib/printPdf.ts | 84 +++++ frontend/src/lib/toolAccess.ts | 29 ++ frontend/src/packet/CloseFile.tsx | 90 ++---- frontend/src/packet/PacketPage.tsx | 10 +- frontend/src/packet/PrintMenu.test.tsx | 45 ++- frontend/src/packet/PrintMenu.tsx | 4 +- frontend/src/packet/QcActions.tsx | 56 +++- frontend/src/packet/ShortReceipts.tsx | 63 ++++ frontend/src/packet/closeUtil.ts | 26 +- frontend/src/packet/closefile.test.tsx | 89 +++--- frontend/src/packet/qc.test.tsx | 83 ++++- frontend/src/packet/shopUtil.ts | 11 +- frontend/src/packet/shorts.test.tsx | 127 ++++++++ frontend/src/packet/tabs.tsx | 20 +- frontend/src/screens/ScreenRoute.tsx | 8 +- .../src/screens/admin/ChecklistsTab.test.tsx | 101 ++---- frontend/src/screens/admin/ChecklistsTab.tsx | 232 +------------- .../src/screens/admin/StampVerifyTab.test.tsx | 12 +- frontend/src/screens/admin/StampVerifyTab.tsx | 9 +- .../checklists/ChecklistsPage.test.tsx | 151 +++++++++ .../src/screens/checklists/ChecklistsPage.tsx | 18 ++ .../src/screens/checklists/DeptChecklists.tsx | 289 ++++++++++++++++++ .../src/screens/clerical/ClericalHome.tsx | 2 +- frontend/src/screens/clerical/boards.ts | 2 +- frontend/src/screens/pm/PmHome.test.tsx | 47 ++- frontend/src/screens/qc/ItpPage.tsx | 19 +- frontend/src/screens/qc/ItpPrint.test.tsx | 22 +- frontend/src/screens/stall/StallHome.test.tsx | 105 +++---- frontend/src/screens/stall/StallHome.tsx | 162 ++-------- .../src/screens/stamp/StampCheckPage.test.tsx | 54 ++++ frontend/src/screens/stamp/StampCheckPage.tsx | 18 ++ frontend/src/screens/tools/RequireRole.tsx | 19 ++ server/packetstation/api/admin_org.py | 49 +-- server/packetstation/api/admin_security.py | 30 +- server/packetstation/api/checklists.py | 155 ++++++++++ server/packetstation/api/packet_children.py | 74 +++++ server/packetstation/api/packets.py | 29 +- server/packetstation/api/pm.py | 13 +- server/packetstation/api/shop.py | 20 +- server/packetstation/domain/checklists.py | 152 ++++++++- server/packetstation/domain/packets.py | 99 +++++- server/packetstation/domain/pipeline.py | 48 ++- server/packetstation/domain/pm.py | 46 ++- server/packetstation/domain/qc.py | 9 +- server/packetstation/domain/search.py | 13 +- server/packetstation/domain/shop.py | 31 ++ server/packetstation/feed/apply.py | 40 ++- server/packetstation/main.py | 4 + .../migrations/0011_seth_changes.sql | 47 +++ server/packetstation/pdf/databook.py | 99 +++--- server/packetstation/pdf/pmreport.py | 2 +- server/packetstation/policy/actions.py | 9 +- server/packetstation/policy/engine.py | 28 +- server/packetstation/policy/preconditions.py | 45 ++- server/packetstation/policy/principal.py | 4 + tests/e2e/tests/admin-stampverify.spec.ts | 32 ++ tests/e2e/tests/checklists.spec.ts | 87 ++++++ tests/e2e/tests/close.spec.ts | 50 ++- tests/e2e/tests/closehelpers.ts | 38 ++- tests/e2e/tests/pm.spec.ts | 20 ++ tests/e2e/tests/qc.spec.ts | 125 +++++++- tests/e2e/tests/qchelpers.ts | 5 + tests/e2e/tests/shop.spec.ts | 25 +- tests/server/closehelpers.py | 40 ++- tests/server/pmhelpers.py | 24 +- tests/server/test_admin_roles.py | 37 ++- tests/server/test_c1_over_target.py | 66 ++++ tests/server/test_c2_ncr_accept.py | 77 +++++ tests/server/test_c3_itp_file.py | 42 +++ tests/server/test_c5_feed_close.py | 80 +++++ tests/server/test_c7_short_approve.py | 169 ++++++++++ tests/server/test_c8_checklists.py | 161 ++++++++++ tests/server/test_close_file.py | 77 +++-- tests/server/test_filters.py | 30 +- tests/server/test_pdf_databook.py | 30 +- tests/server/test_phase8_server_smoke.py | 7 +- tests/server/test_pm_api.py | 21 +- tests/server/test_pm_export.py | 3 +- tests/server/test_policy_matrix.py | 89 +++++- tests/server/test_policy_qc.py | 2 +- tests/server/test_qc_api.py | 14 +- tests/server/test_schema.py | 5 + tests/server/test_shop_api.py | 12 +- 101 files changed, 3871 insertions(+), 1098 deletions(-) create mode 100644 frontend/src/api/checklists.ts create mode 100644 frontend/src/api/stall.ts create mode 100644 frontend/src/api/stamp.ts create mode 100644 frontend/src/lib/printPdf.ts create mode 100644 frontend/src/lib/toolAccess.ts create mode 100644 frontend/src/packet/ShortReceipts.tsx create mode 100644 frontend/src/packet/shorts.test.tsx create mode 100644 frontend/src/screens/checklists/ChecklistsPage.test.tsx create mode 100644 frontend/src/screens/checklists/ChecklistsPage.tsx create mode 100644 frontend/src/screens/checklists/DeptChecklists.tsx create mode 100644 frontend/src/screens/stamp/StampCheckPage.test.tsx create mode 100644 frontend/src/screens/stamp/StampCheckPage.tsx create mode 100644 frontend/src/screens/tools/RequireRole.tsx create mode 100644 server/packetstation/api/checklists.py create mode 100644 server/packetstation/migrations/0011_seth_changes.sql create mode 100644 tests/e2e/tests/checklists.spec.ts create mode 100644 tests/server/test_c1_over_target.py create mode 100644 tests/server/test_c2_ncr_accept.py create mode 100644 tests/server/test_c3_itp_file.py create mode 100644 tests/server/test_c5_feed_close.py create mode 100644 tests/server/test_c7_short_approve.py create mode 100644 tests/server/test_c8_checklists.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 51951eb..2bf96ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,14 @@ All notable changes to this project are recorded here. The format follows [Keep ### Decided +- Gitea issue 9: SethD kept the drawing-origin change order for a same-rev re-release on an in-shop packet (2026-10-29), so the shop confirms the new pages. A byte-identical file is still ignored. Nothing changes in the build. +- Gitea issue 4: SethD wants the order a packet shows to follow the shop schedule, which is Presley's Production Planner (2026-10-29). Asked on the issue on 2026-10-30: what it is, where it lives, what a row holds and how often it changes. Packet Station would only read it. The order rule stays as built until then. +- Gitea issue 31: two follow-up questions asked on 2026-10-30. A work order deleted from VISUAL while open blocks filing for good: count a leg gone for 2 runs as closed, or let an Admin close it with a reason? And should Shipping packets also wait for the VISUAL close? Both stay as built until answered. - Gitea issues 10, 14, 15, 16, 22, 26, 28 and 29: SethD kept the built rules (cancelled work orders flagged only, data book date for Clerical and Q.C., signature undo rules, the server release gate, no "copy marks" button, block signature removal, the data book built at filing, every role may read the PDFs). Nothing changes in the build. - Gitea issue 6: every policy tightening stays, filing for packets routed to Shipping included. -- Gitea issues 8, 18, 23, 32, 33 and 36: SethD changed six more defaults, to build in phase 10 (PLAN.md changes C6 to C11): the data book leaves out the history pages; a supervisor approves each received short and a packet cannot complete until every short is approved; checklists are per department, edited by its supervisor, and changes reach new packets only; Q.C. and Clerical may run the stamp check; the PM screen is for PS-ProjectMgmt and Admin; the Stall screen follows the supervisor's assignment. +- Gitea issues 8, 18, 23, 32, 33 and 36: SethD changed six more defaults (PLAN.md changes C6 to C11, built 2026-10-30; see Added and Changed): the data book leaves out the history pages; a supervisor approves each received short and a packet cannot complete until every short is approved; checklists are per department, edited by its supervisor, and changes reach new packets only; Q.C. and Clerical may run the stamp check; the PM screen is for PS-ProjectMgmt and Admin; the Stall screen follows the supervisor's assignment. - Gitea issues 7, 11, 13, 17, 30, 34 and 35: kept as built, or settled as planned (the archive gets the data book, the documents and a manifest at filing; nothing is purged). -- Gitea issues 1, 24, 25, 27 and 31: SethD changed five defaults, to build in phase 10 (PLAN.md changes C1 to C5): over target follows VISUAL ticket hours; a Q.C. reject always needs a reason and acceptance needs an NCR number; an incomplete ITP blocks filing; prints go to the print dialog from the page; filing waits until VISUAL shows the work order closed. +- Gitea issues 1, 24, 25, 27 and 31: SethD changed five defaults (PLAN.md changes C1 to C5, built 2026-10-30; see Added and Changed): over target follows VISUAL ticket hours; a Q.C. reject always needs a reason and acceptance needs an NCR number; an incomplete ITP blocks filing; prints go to the print dialog from the page; filing waits until VISUAL shows the work order closed. - Gitea issue 21: carried marks stay computed; the next department adds its welds to the same weld map. - Gitea issue 37: production and test each run natively on their own Linux VM, not in Docker. docker-1 keeps CI only. Built in phase 10; the docker-1 test stack runs until the test VM is ready. - Gitea issue 7: project files are also archived to an NFS share on the QNAP. Phase 10 adds an Admin setting for the archive folder that checks read and write. Nothing is deleted from Packet Station. @@ -28,6 +31,15 @@ All notable changes to this project are recorded here. The format follows [Keep ### Changed +- Gitea issue 1 (change C1): over target follows the VISUAL ticket hours, not the station timer. A packet is over when its ticket hours are above its work-order target; a packet with no ticket hours is not over. The PM hours tables, the "Packets over target" table, the KPI, the summary line, the CSV and the report PDF follow. The columns read "Timer h", "Ticket h", "Target h", "Ticket variance h", "Ticket h / target" and "Over target (tickets)", and the over table ends with "Over by (tickets)". The timer is still shown. +- Gitea issue 24 (change C2): a Q.C. reject always needs a reason of at least 8 characters; an NCR number may go with it but no longer stands in for it. A packet rejected without an NCR number cannot be accepted, to complete or to another department, until Q.C. enters the NCR number (422 `ncr_required` "Enter the NCR number of the reject first."). The number is stored on the accepting inspection and in the log. The Q.C. bar shows the NCR field as required (`ncr_required` on the packet read). An NCR document added to the packet goes into section 5 of the data book. +- Gitea issue 25 (change C3): a packet cannot be filed while its unit's ITP is not complete (precondition `itp_complete`, "Complete the unit's ITP first. Q.C. signs it off on the ITP page."). A unit with no ITP does not block. The close panel shows the reason under File. +- Gitea issue 27 (change C4): prints go straight to the browser's print dialog from the page, with no new tab. Every print in the app, the ITP print included, goes through `frontend/src/lib/printPdf.ts`. A print that fails says "The print could not be made. Try again." Viewing a PDF still opens a tab. The stamped pages and the print log are unchanged. +- Gitea issue 31 (change C5): filing is blocked, not warned, while VISUAL shows a work order open, on both routes. The feed now marks a VISUAL packet closed in VISUAL when it sees every work order closed or cancelled, and clears it when one shows open again ("Open again in VISUAL (observed)"); the packet moves from "Ready to close" to "Ready for data book" by itself. Clerical confirms only hand packets; "Job closed in VISUAL" is refused on a VISUAL packet. Precondition `closed_in_visual` replaces `vclosed_if_databook`. The second confirmation (`confirm_open_wos`, 409 `open_work_orders`) is gone. +- Gitea issue 8 (change C6): the data book leaves out the packet history. Section 7 "Record history" is gone, so the book has six sections. The change-order table moved to a "Change order record" page at the end of section 1, only when the packet has change orders. The history stays on the packet page. +- Gitea issue 33 (change C10): the Project Management screen and every `/api/pm/*` endpoint, the CSV and PDF exports included, are for PS-ProjectMgmt and Admin only. Anyone else gets 403 "Project Management only." and sees no PM entry; typing `/pm` says the screen is not for their role. +- Gitea issue 36 (change C11): the Stall screen lists the packets assigned to the signed-in person, each at the station the supervisor picked, across stations (`GET /api/stall/mine`). Nobody picks a station there any more. Moving a welder is a re-assign by the supervisor. The stall's Mine no longer needs a station. With nothing assigned it says "Your supervisor assigns packets to you." +- Deploying C1 to C11: short receipts recorded before the deploy have no approval, so a supervisor approves them before the next Q.C. accept to complete, or before filing a packet already complete. A Q.C. reject since the last accept that has no NCR number needs one at the next accept. Migration 0011 freezes the open stage of each packet in progress, in review or at Q.C. to the default list for its kind, so a department edit does not reach it; if the `checklist_templates` row is missing, such a stage reads its department's live list until it closes. Nothing else is backfilled. - Security fixes in the pinned dependencies, found by the new CI scan: `pypdf` 6.21.0 to 6.21.2 (GHSA-5hx5-vpf9-5wcv, a crafted PDF could reach the image parsers; 6.21.2 for GHSA-6c7f-9265-g8fw, GHSA-jr96-gvgh-j3r4 and GHSA-gmh7-3g9r-mjh5) and `certifi` 2026.7.22 to 2026.10.19 (PYSEC-2026-4320, a distrusted root removed). The image must be rebuilt for both to reach the stacks. - `POST /api/admin/ad/refresh` no longer runs the AD refresh in the request. It asks the worker for an `ad-reconcile` run and answers 202 `{requested, pending, job}`; `GET /api/admin/ad/reconcile` gives the latest report. - The storage quota alert is now mailed through the Notifications rules (to PS-Admins by default), with a "Cleared" message when usage drops back. @@ -39,6 +51,9 @@ All notable changes to this project are recorded here. The format follows [Keep ### Added +- Gitea issue 18 (change C7): the department supervisor approves every received short, one receipt at a time, with a signature ("Approve a received short"), after checking the part and its heat number against what the warehouse issued. A lead is not enough. The Materials tab lists each receipt with who received it and who approved it, and an Approve button for the supervisor. A packet cannot be accepted as complete, or filed, until every short is received and approved ("Every short must be received and approved by the supervisor first."). Accept and route to another department does not wait. `POST /api/packets/{id}/shorts/receipts/{receipt_id}/approve`; the packet read gains `shorts` and `shorts_unapproved`. Migration 0011 adds the append-only `short_approvals` table. +- Gitea issue 23 (change C8): checklists per department. The Checklists screen (`/checklists`) lets a department supervisor edit their own department's checklist, and Admin any department's; a lead and Q.C. cannot. A department without its own list uses the default for its kind (weld or plain). A save needs a password session and the version it started from, and is audited (`checklists.dept_updated`). A stage keeps the list it had when work started (`packet_stages.checklist_template`, migration 0011), so a change reaches only stages not yet started. The Admin Checklists tab now edits department lists too. `GET /api/checklists`, `GET` and `PUT /api/checklists/{dept}`. The Q.C. check items are unchanged. +- Gitea issue 32 (change C9): the Stamp check screen (`/stamp-check`) for Admin, Q.C. and Clerical, in a password session. `POST /api/stamp-verify` and `POST /api/stamp-verify/pdf`; the `/api/admin` paths stay as aliases with the same rule. - Phase 9: Admin completion, AD reconcile, integrity, notifications, backups. The Admin page gains eight tabs: Departments, Profiles, Checklists, Security, Access log, Who is in, Stamp check and Notifications. The tab list wraps into rows on a narrow screen. - Departments tab: every department with its AD groups (and whether each is in the OU) and its stations. Edit a department's name, turn it on or off, rename, reorder, add or hide stations. Stations are never deleted, since station limits and packets point at them. The code and the short name cannot change. "New department" adds a shop department with its stations and shows the three AD groups IT must create (`PS---Worker`, `-Lead`, `-Supervisor`) before it is saved. Only shop departments can be added; the support departments are fixed. - Profiles tab: a person's welder stamp (1 to 12 letters, numbers or dashes, unique), whether their profile is active, and their station limits per department. An inactive profile drops out of the welder picker and the worker filter; it changes nothing the person may do. A CSV import by employee ID (columns `employee_id`, `welder_stamp`, `active`, `stations`; an empty cell leaves a value, `-` clears it; the `stations` cell is the person's whole list) shows a preview with every change and every error, and applies only when the preview has no errors, in one go. The Directory flags panel lists active users with no employee ID and employee IDs shared by more than one account. diff --git a/README.md b/README.md index d048c37..3b45831 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ This repo is the rebuild of Packet Station as a server application: Docker Compo ## Status -**Rebuild in progress. Phase 9 done: the Admin page completed (departments, profiles with the CSV import, checklists, security settings and alerts, the access log, who is in, the stamp check), the nightly AD reconcile and the 15-minute role refresh, the integrity job, notifications by email with rules per event, nightly database backups with a rehearsed restore, and dependency scanning in CI.** +**Rebuild in progress. Phase 9 done: the Admin page completed (departments, profiles with the CSV import, checklists, security settings and alerts, the access log, who is in, the stamp check), the nightly AD reconcile and the 15-minute role refresh, the integrity job, notifications by email with rules per event, nightly database backups with a rehearsed restore, and dependency scanning in CI. SethD's changes C1 to C11 (Gitea, 2026-10-26 and 2026-10-28) were built on 2026-10-30.** -People can sign in, set up projects and units, make hand drafts, attach a drawing, and send a packet to the warehouse, a department or Q.C. The worker reads released work orders from VISUAL on a schedule, makes and updates drafts, opens change orders on packets already in the shop, looks drawings up in M-Files and attaches them at the rev VISUAL calls. Clerical sees it all on the From VISUAL panel and in the Flagged and Changes to approve columns; Admin sets the feed up on the Feed tab. Warehouse works its task board (waiting, being pulled, on hold, pulled, released), pulls parts line by line with heat numbers, shorts, receipts and MTRs, and releases to the supervisor once every line is pulled or short and every pulled line has a heat. A supervisor assigns the packet to a station and a welder, the welder starts the clock, marks welds on the drawing, places the heat table, finishes the checklist and submits with a PIN signature, and the supervisor sends it to Q.C. or routes it to another department with the marks carried. Department supervisors verify change orders with a signature. Q.C. inspects the packet on its own tab (the check items, a visual result per weld, NDE, hardness, notes), accepts it to the data book, to Shipping or on to another department with a PIN signature, or rejects it back to the supervisor with a reason or an NCR number. Q.C. keeps project folders of documents per project and unit (ITP, WPS / PQR, procedures, certificates) where a new revision supersedes the old one and nothing is deleted, adds reports to a packet for the data book and takes them out with a reason, and works each unit's digital ITP from the Q.C. template through line initials, N/A, rejects with NCR numbers, section block signatures and a sign-off. Open hold and witness points show as a warning on the unit's packets. Clerical confirms the job closed in VISUAL (the feed's closed state and any open work orders shown beside it) and files the packet with a signature. Filing seals the packet with an HMAC and builds the manufacturing data book on the server, stored on the seal; filing past open work orders needs a second confirmation. IT can reopen a filed packet with a reason and a signature, which keeps the broken seal and its data book. Anyone who can open a packet can view or download the data book, the job traveler with the work order QR codes, the ITP PDF and the traceability CSV, and print a stamped copy of the traveler, the drawings with the heat or weld map, the drawings without markup, the VOID sheets, the data book or any document. Every copy carries a stamp signed with the seal key, and every print and export is logged. The ITP sign-off files the signed ITP PDF. An Admin manages departments and stations, welder stamps and station limits (one at a time or from a CSV), the checklists, the session and lockout settings, and who gets which email. The worker reconciles users with AD every night, re-reads each signed-in person's groups every 15 minutes, checks seals, files and the audit chain every night, and checks that the nightly backup ran. Problems and events reach people by email through app-03. Every packet write goes through the server policy. The old build on `M:\Production\Packet Station` stays the live system until go-live. The new system starts empty; nothing is migrated from M:. +People can sign in, set up projects and units, make hand drafts, attach a drawing, and send a packet to the warehouse, a department or Q.C. The worker reads released work orders from VISUAL on a schedule, makes and updates drafts, opens change orders on packets already in the shop, looks drawings up in M-Files and attaches them at the rev VISUAL calls. Clerical sees it all on the From VISUAL panel and in the Flagged and Changes to approve columns; Admin sets the feed up on the Feed tab. Warehouse works its task board (waiting, being pulled, on hold, pulled, released), pulls parts line by line with heat numbers, shorts, receipts and MTRs (the department supervisor then approves each received short with a signature), and releases to the supervisor once every line is pulled or short and every pulled line has a heat. A supervisor assigns the packet to a station and a welder. The welder's Stall screen lists the packets assigned to them, each at the station the supervisor picked. The welder starts the clock, marks welds on the drawing, places the heat table, finishes the checklist and submits with a PIN signature, and the supervisor sends it to Q.C. or routes it to another department with the marks carried. Department supervisors verify change orders with a signature. Q.C. inspects the packet on its own tab (the check items, a visual result per weld, NDE, hardness, notes), accepts it to the data book, to Shipping or on to another department with a PIN signature, or rejects it back to the supervisor with a reason and, when there is one, an NCR number. A packet rejected without an NCR number needs one at the accept, and a packet with a short not yet approved cannot be accepted as complete. Q.C. keeps project folders of documents per project and unit (ITP, WPS / PQR, procedures, certificates) where a new revision supersedes the old one and nothing is deleted, adds reports to a packet for the data book and takes them out with a reason, and works each unit's digital ITP from the Q.C. template through line initials, N/A, rejects with NCR numbers, section block signatures and a sign-off. Open hold and witness points show as a warning on the unit's packets. The feed marks a VISUAL packet closed in VISUAL when it sees every work order closed, and the packet moves to Clerical's "Ready for data book" column by itself; Clerical confirms a hand packet by hand. Clerical files the packet with a signature once VISUAL is closed, the unit's ITP is complete and every short is approved. Filing seals the packet with an HMAC and builds the manufacturing data book on the server, stored on the seal. The data book goes to the customer and has no history pages; the history stays in Packet Station. IT can reopen a filed packet with a reason and a signature, which keeps the broken seal and its data book. Anyone who can open a packet can view or download the data book, the job traveler with the work order QR codes, the ITP PDF and the traceability CSV, and print a stamped copy of the traveler, the drawings with the heat or weld map, the drawings without markup, the VOID sheets, the data book or any document. A print goes straight to the browser's print dialog. Every copy carries a stamp signed with the seal key, and every print and export is logged. Admin, Q.C. and Clerical check a copy's stamp on the Stamp check screen. Project Management and Admin read the PM reports. The ITP sign-off files the signed ITP PDF. Each department supervisor edits their department's checklist on the Checklists screen; a stage keeps the list it started with. An Admin manages departments and stations, welder stamps and station limits (one at a time or from a CSV), any department's checklist, the session and lockout settings, and who gets which email. The worker reconciles users with AD every night, re-reads each signed-in person's groups every 15 minutes, checks seals, files and the audit chain every night, and checks that the nightly backup ran. Problems and events reach people by email through app-03. Every packet write goes through the server policy. The old build on `M:\Production\Packet Station` stays the live system until go-live. The new system starts empty; nothing is migrated from M:. | Area | Exists yet? | |---|---| @@ -23,6 +23,7 @@ People can sign in, set up projects and units, make hand drafts, attach a drawin | Close and file, HMAC seal with the data book, reopen, data book, traveler and ITP PDFs, traceability CSV, prints and the print log | Yes (phase 7) | | Project Management screen (six tabs, periods, health, CSV and PDF reports), the find bar completed (search, filters, quick flags, Mine, Board and List, saved per browser), presence, motion, iPad layout | Yes (phase 8, verified in the CI stack on docker-1) | | Admin completion (departments, profiles, checklists, Security, access log, who is in, stamp check, Notifications), AD reconcile and role refresh, integrity job, email notifications, backups and restore, CI dependency scan | Yes (phase 9) | +| SethD's changes C1 to C11: over target on VISUAL ticket hours, the Q.C. reject reason and the NCR on the accept, the ITP complete before filing, prints to the print dialog, filing blocked until VISUAL closes, no history pages in the data book, supervisor approval of received shorts, checklists per department (`/checklists`), the stamp check for Q.C. and Clerical (`/stamp-check`), PM reports for PM and Admin only, the Stall screen by assignment | Yes (2026-10-30) | | Regression bench and go-live | No (phase 10) | ## Phases @@ -88,12 +89,14 @@ Phase 3 adds the worker (`packetstation worker`: APScheduler, the `visual-feed`, Phase 6 adds the Q.C. screen (`/qc`: numbers, boards, project folders), `GET /api/qc/home`, `GET /api/qc/folders` with the project and unit folder reads, `POST /api/qc/docs` (a new revision supersedes the old row), the per-key Q.C. endpoints under `/api/packets/{id}/qc`, document kinds as a closed set with take-out reasons, and the digital ITP under `/api/units/{id}/itp` and `/api/itps/{id}` (start, line sign and unsign, N/A, reject with NCR, block sign, sign-off, reopen, log). The ITP PDF filed on sign-off is a hook for phase 7. See [docs/POLICY.md](docs/POLICY.md) "Q.C. (phase 6)". -Phase 7 adds the close and file panel, `confirm_open_wos` on the file transition (409 `open_work_orders` without it), the data book built and stored on the seal at filing, `GET /api/packets/{id}/pdf/databook`, `GET /api/packets/{id}/pdf/traveler`, `GET /api/packets/{id}/export/traceability.csv`, `GET /api/packets/{id}/print/{kind}` (`traveler`, `map-h`, `map-w`, `clean`, `void`, `databook`, `doc`), `GET /api/packets/{id}/prints`, `GET /api/itps/{id}/pdf` and `GET /api/itps/{id}/print`, and the `print_log` table (migration 0008). The PDFs are built on the server with reportlab 5.0.1, pypdf and pypdfium2; the heat and weld map rasters use Pillow 13.0.0. Both are pinned in `pyproject.toml` and `server/requirements.txt`, and need no system package or font in the image. See [docs/POLICY.md](docs/POLICY.md) "Phase 7: close, file, PDFs and prints". +Phase 7 adds the close and file panel, `confirm_open_wos` on the file transition (409 `open_work_orders` without it; removed on 2026-10-30 by change C5, since filing is now blocked while VISUAL shows a work order open), the data book built and stored on the seal at filing, `GET /api/packets/{id}/pdf/databook`, `GET /api/packets/{id}/pdf/traveler`, `GET /api/packets/{id}/export/traceability.csv`, `GET /api/packets/{id}/print/{kind}` (`traveler`, `map-h`, `map-w`, `clean`, `void`, `databook`, `doc`), `GET /api/packets/{id}/prints`, `GET /api/itps/{id}/pdf` and `GET /api/itps/{id}/print`, and the `print_log` table (migration 0008). The PDFs are built on the server with reportlab 5.0.1, pypdf and pypdfium2; the heat and weld map rasters use Pillow 13.0.0. Both are pinned in `pyproject.toml` and `server/requirements.txt`, and need no system package or font in the image. See [docs/POLICY.md](docs/POLICY.md) "Phase 7: close, file, PDFs and prints". -Phase 8 adds the Project Management screen (`/pm`) and its API under `/api/pm` (`overview`, `projects` with `expand=`, `aging`, `hours`, `quality`, `find`, `export.csv?table=|jobs|all`, `report.pdf`), read by any signed-in role and writing nothing; every export is logged as `report exported` in the audit and the print log. The find bar params (`q`, `status`, `dept`, `station_id`, `welder_id`, `flags`, `flag`, `sort`, `mine_screen`, `mine_station_id`, `facets`) are read by `GET /api/packets` and by the shop, stall, warehouse and Q.C. boards, so the server applies every filter; the KPI numbers follow the project pick only. Presence over `/api/events` now carries the role and drops locked sessions (session events `locked` and `unlocked`). No new table or migration. See [docs/POLICY.md](docs/POLICY.md) "Phase 8: Project Management, find bar, presence". +Phase 8 adds the Project Management screen (`/pm`) and its API under `/api/pm` (`overview`, `projects` with `expand=`, `aging`, `hours`, `quality`, `find`, `export.csv?table=|jobs|all`, `report.pdf`), read by PS-ProjectMgmt and Admin only since change C10 (any signed-in role in phase 8) and writing nothing; every export is logged as `report exported` in the audit and the print log. The find bar params (`q`, `status`, `dept`, `station_id`, `welder_id`, `flags`, `flag`, `sort`, `mine_screen`, `mine_station_id` (ignored since change C11), `facets`) are read by `GET /api/packets` and by the shop, stall, warehouse and Q.C. boards, so the server applies every filter; the KPI numbers follow the project pick only. Presence over `/api/events` now carries the role and drops locked sessions (session events `locked` and `unlocked`). No new table or migration. See [docs/POLICY.md](docs/POLICY.md) "Phase 8: Project Management, find bar, presence". Phase 9 adds, under `/api/admin`: `departments` (list, `PATCH /departments/{code}`, `POST /departments`), `PATCH /users/{id}/profile`, `POST /users/import` (CSV preview and apply), `checklists`, `security/settings`, `security/alerts`, `security/integrity`, `access-log` and `access-log.csv`, `presence`, `stamp-verify` and `stamp-verify/pdf`, `directory-flags`, `ad/reconcile`, and `notifications` (rules, group search, test send, sent log). `POST /api/admin/ad/refresh` now asks the worker for an `ad-reconcile` run and answers 202. Any signed-in person reads `GET /api/account/notifications`, and opts out of an event type with `PUT /api/account/notifications/{event_type}` (password session; security events cannot be turned off). The Admin page gains the tabs Departments, Profiles, Checklists, Security, Access log, Who is in, Stamp check and Notifications, and Account settings gains a Notifications section. The worker gains six jobs: `ad-reconcile` (01:30), `session-refresh` (every 15 minutes), `integrity` (04:00), `backup-check` (07:15), `notify-send` (every minute) and `notify-digest` (every 5 minutes). Migration 0010 adds the notification tables, the integrity findings and the profile switch. See [docs/POLICY.md](docs/POLICY.md) "Phase 9: Admin, notifications, reconcile" and [docs/FEED.md](docs/FEED.md) "Phase 9 jobs". +SethD's changes (built 2026-10-30, [docs/PLAN.md](docs/PLAN.md) changes C1 to C11) add two screens, `/checklists` (department supervisors and Admin) and `/stamp-check` (Admin, Q.C. and Clerical), both linked in the top bar. The API gains `GET /api/checklists`, `GET` and `PUT /api/checklists/{dept}`, `POST /api/stamp-verify` and `POST /api/stamp-verify/pdf` (the `/api/admin` paths stay as aliases), `GET /api/stall/mine` (the Stall screen; the station picker is gone) and `POST /api/packets/{id}/shorts/receipts/{receipt_id}/approve`. The Q.C. accepts take `ncr`, and the packet read gains `ncr_required`, `shorts` and `shorts_unapproved`. Every `/api/pm/*` endpoint is for PS-ProjectMgmt and Admin only. Every print goes through `frontend/src/lib/printPdf.ts`. The data book has six sections. Migration 0011 adds `short_approvals` and `packet_stages.checklist_template`. See [docs/POLICY.md](docs/POLICY.md) for the rules and [docs/SCHEMA.md](docs/SCHEMA.md), "Seth's changes", for the schema. + Backups run on the host, outside the app: `scripts/backup/ps-backup.sh` (nightly `pg_dump` of each stack from a systemd timer at 02:00 Denver time, 7 kept, with markers the worker checks) and `scripts/backup/ps-restore.sh` (restore into a fresh database, copy assets back, check hashes, compare row counts). [scripts/backup/README.md](scripts/backup/README.md) and [docs/DEPLOY.md](docs/DEPLOY.md) "Backup" have the install steps and the restore rehearsal of 2026-10-21. CI stack with the Samba domain controller and the Playwright scenario, on a Docker host: @@ -117,7 +120,7 @@ Server tests that need PostgreSQL run when `TEST_PG_SUPERUSER_URL` is set; the o - [docs/DEPLOY.md](docs/DEPLOY.md): host, stacks, secrets, NPM proxy host, doctor, notifications, backups and the restore rehearsal. - [docs/FEED.md](docs/FEED.md): the worker and its jobs, the VISUAL feed, the packet rules, the fingerprint, the drawing decision table, the flags and the feed settings. - [docs/POLICY.md](docs/POLICY.md): who may do what, the preconditions, the order of checks, concurrency, and the tightenings against the old build. -- [docs/SCHEMA.md](docs/SCHEMA.md): the database schema (migrations 0001 to 0010). +- [docs/SCHEMA.md](docs/SCHEMA.md): the database schema (migrations 0001 to 0011). - [docs/GO-LIVE.md](docs/GO-LIVE.md): go-live procedure (phase 10). - [docs/PAPER-FALLBACK.md](docs/PAPER-FALLBACK.md): what the shop does when the server is down (phase 10). - [CHANGELOG.md](CHANGELOG.md): what changed. diff --git a/docs/FEED.md b/docs/FEED.md index 056c4b5..5e9b39a 100644 --- a/docs/FEED.md +++ b/docs/FEED.md @@ -272,7 +272,13 @@ The apply runs in one transaction with a 5-second lock timeout. A packet row loc 5. **Past draft: change orders** (action `vis_open_co`). See "Change orders from the feed". Filed packets get nothing. 6. **Retire moved drafts** (action `vis_retire_draft`). A live draft made by the feed, with no drawing pages and no hand uploads, that is not among this run's candidates, and whose every leg now sits on another live candidate, is soft-removed with reason `moved`. `visual.moved` records where the legs went. Log "Retired: work orders moved to another packet". It is hidden from the boards and, being removed, is never recreated. 7. **Work order gone.** Skipped on a suspect batch. Each tracked leg VISUAL did not return gets `missing_runs + 1`. At 2 the leg gets `gone_at`, the packet gets flag `wo_gone` and `wo_gone_at`, and the log says "Work order no longer in VISUAL". A leg that comes back resets to 0. When no leg is gone any more the flag goes and the log says "Work order back in VISUAL". The feed never removes a packet for this. -8. **Closed and cancelled.** Each leg's status code is stored, with ticket hours and the time observed. When every leg is C, `visual_closed_at` is set and the log says "Closed in VISUAL (observed)". Any leg X sets flag `cancelled` and `visual_cancelled_at`. The flag goes when no leg is X. A cancelled draft is not removed. +8. **Closed and cancelled.** Each leg's status code is stored, with ticket hours and the time observed. When every leg is C, `visual_closed_at` is set. Any leg X sets flag `cancelled` and `visual_cancelled_at`. The flag goes when no leg is X. A cancelled draft is not removed. + Since change C5 (Gitea issue 31, built 2026-10-30) the feed also sets and clears the packet's close in VISUAL, `vclosed_at`, on a VISUAL packet that is not filed or removed. It is the one writer there; Clerical sets it only on hand packets. + - When every leg has a status and each is C or X, the feed sets `vclosed_at` to now, with `vclosed_by` null (an existing value is kept). The log says "Closed in VISUAL (observed)" and the run counts `closed_observed`. The Clerical board then moves the packet from "Ready to close" to "Ready for data book" by itself. + - When a leg shows open again (a status that is not C or X) on a packet with `vclosed_at` set, the feed clears `vclosed_at`, `vclosed_by` and `visual_closed_at`. The log says "Open again in VISUAL (observed)" with the open work orders as the note. No count. + - The feed does this in any status before filed, so a packet can come back from Q.C. already "Ready for data book". A filed packet is never touched. + - FILE on a VISUAL packet needs `vclosed_at` set and no leg open, on both routes (precondition `closed_in_visual`, see [POLICY.md](POLICY.md), "Filing"). + - A leg flagged `wo_gone` keeps its last status. If that status was open, the packet cannot file. Asked on Gitea issue 31 on 2026-10-30: count a leg gone for 2 runs as closed, or let an Admin close it with a reason. 9. **Auto-release** when `auto_release` is on and the feed is not paused: up to `auto_release_max` (default 25) live drafts made by the feed with the drawing attached, no flags at all, and the rev confirmed by the drawing job. A draft whose heat map is unanswered gets "yes" when it has a real material line (not a sub-assembly, not n/a, not consumable) and "no" otherwise. Then action `vis_auto_release` moves it to the warehouse with an automatic warehouse stage row and a transition row. The policy's preconditions (heat map answered, drawing attached) still apply. 10. **Summary.** One audit row `feed.run` with the counts, and one `feed` event to the screens after commit. @@ -417,7 +423,7 @@ Flags live in `packets.flags`. A draft with any flag shows in Clerical's Flagged With both hand flags the text is "Drawing and BOM uploaded by hand - check it and send it on". Hand drafts keep their phase 2 text. -"Closed in VISUAL" is not a flag. It is an observed state (`visual_closed_at`), shown on the packet. Filing a Data Book packet needs it, as the `vclosed_if_databook` precondition says. +"Closed in VISUAL" is not a flag. It is an observed state, shown on the packet: `vclosed_at` when every leg is closed or cancelled, `visual_closed_at` when every leg is C. Filing a VISUAL packet needs `vclosed_at` and no open leg, on both routes, as the `closed_in_visual` precondition says (change C5). ### Hand uploads @@ -506,7 +512,7 @@ The packet read model has a `labor` field from `visual_labor`. It joins the pack `labor` is null for a hand packet, or when no `visual_labor` row matches. The packet page shows the sum beside the station clock, as `VISUAL tickets 3.2 h`. -The station timer stays the clock of record (open question 1, default kept). The ticket hours are shown for comparison only. Nothing is computed from them, and they never change the clock. The screen reads them from PostgreSQL, never from VISUAL. +The station timer stays and is shown as before. Since change C1 (Gitea issue 1, built 2026-10-30) the ticket hours decide "over target" on the Project Management reports: ticket hours above the work-order target. A packet with no ticket hours is not over target. The ticket hours never change the clock. The screen reads them from PostgreSQL, never from VISUAL. ## What the screens read diff --git a/docs/PLAN.md b/docs/PLAN.md index 6810336..5083096 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -145,7 +145,8 @@ Each phase ends with passing tests, docs updated (README, CHANGELOG, SECURITY.md | D1, D2, D4 | Deferred (envelope encryption, Key Vault) | | D3 | IT's (host disk encryption on docker-1) | | D5 | Closed, phase 4 (optional ClamAV) | -| D6, D7 | Phase 10 (decided 2026-10-28, Gitea issues 7 and 8: nothing is purged and filed packets are archived to the QNAP share at filing; the one data book leaves out the history pages) | +| D6 | Phase 10 (decided 2026-10-28, Gitea issue 7: nothing is purged and filed packets are archived to the QNAP share at filing) | +| D7 | Closed 2026-10-30 (Gitea issue 8, change C6: the one data book goes to the customer and leaves out the history pages) | | M1 | Closed, phase 2 | | M2 | Closed, phase 7 | | M3 | Closed, phase 9 | @@ -181,7 +182,7 @@ Conventions: identity bigints except users (uuid = AD objectGUID); Postgres enum - **Inputs:** `Principal` (user, admin, support set, clerical_supervisor, shop dept to level map, station limits, session kind, or `system="visual-feed"`) and `PacketState` (status, dept, station, wh_skipped, routed_to, vclosed, removed, open COs, open shorts). Pure functions in `server/packetstation/policy/`, no I/O. - **Custody** `holds()` is a direct port: draft CL; warehouse WH; ready and review dept lead or above; assigned and in_progress dept lead or above or works_on (worker in dept whose station limits include the station, and, tightened per the README, the assigned welder for start and submit); qc QC; complete CL; filed nobody. - **Action enum** with a rule per action: who, from and to status, dept change, CO hold, preconditions, signature meaning, reason required. Transitions: SEND_TO_WAREHOUSE, RELEASE_TO_SHOP, SEND_DRAFT_TO_QC, RETURN_TO_CLERICAL, WH_RELEASE, WH_SEND_BACK, SEND_BACK_TO_WAREHOUSE, ASSIGN, UNASSIGN, PULL_BACK, START, SUBMIT, RETURN_TO_STATION, SEND_TO_QC, ROUTE_FROM_REVIEW, QC_REJECT, QC_ACCEPT, QC_ACCEPT_AND_ROUTE, FILE, ADMIN_REOPEN. Edits: EDIT_BOM, WH_RECEIVE_SHORT (a narrow endpoint that replaces onlyShortLines), EDIT_HEATS, EDIT_WELDS, EDIT_CHECKLIST, EDIT_STAGE_FIELDS, CLOCK, EDIT_QC, SET_WH_STAGE, EDIT_DEPT, MOVE_UNIT, EDIT_WOS, EDIT_DOCUMENT_SET, EDIT_HEADER, REV_APPROVE (clerical supervisor), SET_VCLOSED, SET_DATABOOK_AT, DOC_ADD, DOC_REMOVE, CO_ISSUE, CO_APPROVE, CO_REJECT, CO_HOLD_TOGGLE, CO_VERIFY, ITP_*, VIS_* (worker only). -- **Preconditions** as named predicates, now server-side: heat_map_answered, drawing_attached, bom_all_pulled_or_short, welder_set, checklist_complete, welds_recorded, filler_set, qc_book_spool_location, qc_missing_empty, reason_given, vclosed_if_databook (warning only when work orders are still open in VISUAL; block only when vclosed is unset and the route is Data Book), co_staged_if_dwg_change. +- **Preconditions** as named predicates, now server-side: heat_map_answered, drawing_attached, bom_all_pulled_or_short, welder_set, checklist_complete, welds_recorded, filler_set, qc_book_spool_location, qc_missing_empty, reason_given, closed_in_visual (replaced vclosed_if_databook on 2026-10-30, change C5: a VISUAL packet files only when the feed has seen every work order closed, on both routes; a hand packet on the Data Book route needs the Clerical confirm), itp_complete (C3), shorts_approved (C7), ncr_given (C2), co_staged_if_dwg_change. - **Order of checks** keeps the old semantics: removed, wrong status, CO hold (applies to admins too), admin bypass of custody with a required IT reason, filed lock (only SET_DATABOOK_AT and ADMIN_REOPEN), who and custody, then preconditions (never bypassed). - **`can` on reads:** every packet read returns `can: [...]` (passes role, custody, status and preconditions), `blocked: {action: [reasons]}` (passes role and custody but fails a precondition or the CO hold, so the UI can show a disabled control with the reason), `holder`. Boards evaluate transitions only from header columns plus an open-CO count. - **Signatures:** required for SUBMIT, SEND_TO_QC, QC_ACCEPT, QC_ACCEPT_AND_ROUTE, QC_REJECT, FILE, ROUTE_FROM_REVIEW, ADMIN_REOPEN, CO_ISSUE, CO_VERIFY, CO_APPROVE, CO_REJECT, REV_APPROVE, hand uploads, ITP_BLOCK_SIGN, ITP_SIGN_OFF. The body carries `sign: {factor, secret}`; the server verifies the factor against the session's own user inside the transaction (PIN allowed in any session on any device, as the brief says; password everywhere), logs the attempt, applies the five-strike PIN disable and the three-strike session end, and writes the signatures row with a content hash over what was signed. @@ -217,19 +218,19 @@ Conventions: identity bigints except users (uuid = AD objectGUID); Postgres enum Need an answer before the named phase. Defaults apply if none arrives in time. -1. **Station timer** (Seth, before phase 5): keep, cross-check against labor tickets, or drop. Default: keep the timer, show ticket hours beside it. Phase 5 (2026-10-16) applied the default: the station timer is the clock of record and the VISUAL ticket hours are shown beside it. Decided by SethD on 2026-10-26, Gitea issue 1: keep the timer as built, but flag over target on the VISUAL ticket hours (change C1 below). +1. **Station timer** (Seth, before phase 5): keep, cross-check against labor tickets, or drop. Default: keep the timer, show ticket hours beside it. Phase 5 (2026-10-16) applied the default: the station timer is the clock of record and the VISUAL ticket hours are shown beside it. Decided by SethD on 2026-10-26, Gitea issue 1: keep the timer as built, but flag over target on the VISUAL ticket hours (change C1 below). Built on 2026-10-30. 2. **PDFs on the server** (confirm before phase 7): reportlab + pypdf + pypdfium2. Default: yes. Phase 7 (2026-10-17) applied the default: reportlab 5.0.1, pypdf and pypdfium2, with Pillow 13.0.0 for the rasters. 3. **Frontend stack** (confirm before phase 1): TypeScript + React + Vite. Default: yes. Plain JavaScript would not be faster for parity: the old 7,000 lines are screen code that must be rewritten either way, and typed API models cut rework. 4. **PIN defaults** (confirm): 6 to 12 digits, five attempts, 10-hour shop session, device cookie. Was open (Seth and Q.C., before phase 6): whether Q.C. accept and filing require the domain password even with a PIN set. Default: PIN is enough. Confirmed by SethD on 2026-10-26, Gitea issue 2: the PIN is enough for every sign-off, Q.C. accept and filing included. IT: device certificate later. The device cookie stays and no device certificate is planned: confirmed by Spencer (IT) on 2026-10-18, Gitea issue 3. -5. **Several open orders on a project** (before phase 3): which order a packet shows. Proposed rule: the open order whose line's top job is the packet's work order base, else the newest open order, else the newest closed one; the packet shows "via base" or "via project" and Clerical can override. Needs Seth's view. Phase 3 (2026-10-16) built the default in one function (`feed/orders.py`): a Clerical pin, else the base's own linked order, else the project order whose top job is the unit, else the project's first open order, else its newest; the source (`via`) is stored on the project and unit and shown on the project page, which lists every open order. Seth may change the rule later in that one place. SethD on 2026-10-28, Gitea issue 4: the order should follow the shop schedule, linked live so a job that is moved, pushed or pulled up changes what its packets show. That needs a discussion first; the rule stays as built until then. +5. **Several open orders on a project** (before phase 3): which order a packet shows. Proposed rule: the open order whose line's top job is the packet's work order base, else the newest open order, else the newest closed one; the packet shows "via base" or "via project" and Clerical can override. Needs Seth's view. Phase 3 (2026-10-16) built the default in one function (`feed/orders.py`): a Clerical pin, else the base's own linked order, else the project order whose top job is the unit, else the project's first open order, else its newest; the source (`via`) is stored on the project and unit and shown on the project page, which lists every open order. Seth may change the rule later in that one place. SethD on 2026-10-28, Gitea issue 4: the order should follow the shop schedule, linked live so a job that is moved, pushed or pulled up changes what its packets show. That needs a discussion first; the rule stays as built until then. SethD on 2026-10-29: the schedule is Presley's Production Planner, which the whole shop works from today. Asked on issue 4 (2026-10-30): what it is, where it lives, what a row holds and how often it changes. Packet Station would only read it. 6. **VISUAL_ENCRYPT** (IT, before production): yes once vsql has a trusted certificate; `no` until then. 7. **Policy tightenings** listed under "Challenges" 1 to 3 (Seth, before phase 2). Confirmed by SethD on 2026-10-26, Gitea issue 6: "good as shown". Every row is kept, filing for packets routed to Shipping included (Spencer read the answer that way on 2026-10-27). 8. **Repo location**: PacketStation2 as proposed, or another name. -9. **Retention period** (D6) and customer-facing data book copy (D7): deferred, but a decision from Seth and Q.C. lets a later phase close them. Gitea issue 7: SethD expects all data to be kept (2026-10-26). Spencer decided on 2026-10-27 that project files are also archived to an NFS share he sets up on the QNAP, with an Admin setting for the folder path that checks read and write (phase 10, "Archive location"). Nothing is deleted from Packet Station; the archive is a copy. Still open on issue 7: a minimum retention period, if any, and what goes into the archive and when. Decided on 2026-10-28: all data is kept and nothing is purged; at filing the data book and each of the packet's documents go to the archive with a manifest (SethD, Gitea issue 7). There is one data book, the customer copy, without the history pages (SethD, Gitea issue 8, change C6). +9. **Retention period** (D6) and customer-facing data book copy (D7): deferred, but a decision from Seth and Q.C. lets a later phase close them. Gitea issue 7: SethD expects all data to be kept (2026-10-26). Spencer decided on 2026-10-27 that project files are also archived to an NFS share he sets up on the QNAP, with an Admin setting for the folder path that checks read and write (phase 10, "Archive location"). Nothing is deleted from Packet Station; the archive is a copy. Still open on issue 7: a minimum retention period, if any, and what goes into the archive and when. Decided on 2026-10-28: all data is kept and nothing is purged; at filing the data book and each of the packet's documents go to the archive with a manifest (SethD, Gitea issue 7). There is one data book, the customer copy, without the history pages (SethD, Gitea issue 8, change C6). C6 was built on 2026-10-30, which closes D7. D6 (the archive) is phase 10. Smaller questions from the design, with the default the build takes unless told otherwise: -10. Same-rev drawing re-release (new file, same rev) on an in-shop packet: default a drawing-origin change order, not a silent swap. +10. Same-rev drawing re-release (new file, same rev) on an in-shop packet: default a drawing-origin change order, not a silent swap. Confirmed by SethD on 2026-10-29, Gitea issue 9: keep the change order as built, so the shop confirms the new pages. A byte-identical file is still ignored. 11. Cancelled (X) work orders: default flag only, like gone; drafts are not removed. SethD on 2026-10-26, Gitea issue 10: the default is fine, left to Spencer. Kept as built. 12. Partspecs, title or folded-operation target changes on an in-shop packet: default no change order, as the old build. Confirmed by SethD on 2026-10-28, Gitea issue 11: only a new drawing rev, a quantity change or a removed operation opens a change order. 13. Work-order-gone confirmation after 2 consecutive runs and the 10% suspect-batch guard: defaults as stated. SethD left both numbers to Spencer on 2026-10-26, Gitea issue 12. The defaults stay unless Spencer changes them. @@ -243,7 +244,7 @@ From phase 4 (2026-10-16), for Seth to veto: 19. The release gate has two server rules: every line not N/A is pulled or on an open short, and every pulled line that issued pieces has a heat unless it is N/A or a sub-assembly. The old build enforced both on the screen only. Default: both on the server. Confirmed by SethD on 2026-10-26, Gitea issue 16: both stay, so the warehouse fills the heat map for the parts it sends. 20. Hold and short reasons: the old build accepted any text. Default: closed sets on the server; "Other" on a hold needs a note. Confirmed by SethD on 2026-10-28, Gitea issue 17: keep the lists. -21. Receiving a short on a packet the warehouse no longer holds: default it stays a warehouse right (WH_RECEIVE_SHORT, any status but filed), and the holder of the packet (department lead or supervisor, Q.C., Clerical) can receive too, through edit_bom. Changed by SethD on 2026-10-28, Gitea issue 18 (change C7). +21. Receiving a short on a packet the warehouse no longer holds: default it stays a warehouse right (WH_RECEIVE_SHORT, any status but filed), and the holder of the packet (department lead or supervisor, Q.C., Clerical) can receive too, through edit_bom. Changed by SethD on 2026-10-28, Gitea issue 18 (change C7). Built on 2026-10-30: receiving works as before, and the department supervisor then approves each receipt. 22. Upload kinds and limits: default an MTR is PDF, PNG or JPG up to 25 MB, a hand BOM is XLSX or CSV up to 5 MB. ClamAV is optional and off unless CLAMAV_HOST is set; when set, an unreachable scanner refuses uploads (fail closed). Confirmed by Spencer (IT) on 2026-10-18, Gitea issue 19: the limits stay, and ClamAV stays off in both stacks because every workstation that uses Packet Station runs Microsoft Defender. The `clamav` profile stays in the build, unused. 23. The quota alert: default a `notification_events` row (phase 9 builds the delivery on this table) at 85 percent of the assets volume. Confirmed by Spencer (IT) on 2026-10-18, Gitea issue 20: 85 percent stays, and the alert goes to PS-Admins. Phase 9 seeds the `assets_quota` notification rule with PS-Admins as its recipient. @@ -251,32 +252,32 @@ From phase 5 (2026-10-16), for Seth to veto: 24. Carried marks: when a packet is routed to another department, the welds, heat tags and heat table stay on the packet. The read model computes a `carried` flag. Nothing is copied. Default: computed, not copied. Confirmed by SethD on 2026-10-26, Gitea issue 21: the marks stay with the packet as it moves on, and a department that receives the packet (for example weld assembly after parts weld) adds its welds to the same weld map, so the data book carries one map. 25. The old build's manual "copy marks to the new sheet" button on a change order is not ported. On a page replacement only the heat table placement is copied to the new page. Welds and heat tags stay on the VOID page. Default: not ported in phase 5; a follow-up. Confirmed by SethD on 2026-10-26, Gitea issue 22: not ported. After a change order the department re-verifies the work, since a change can alter what was marked. -26. Checklist templates live in the `checklist_templates` settings row, with the old lists as defaults. Default: no screen edits them in phase 5; Admin editing comes in phase 9. Changed by SethD on 2026-10-26 and 2026-10-28, Gitea issue 23 (change C8). +26. Checklist templates live in the `checklist_templates` settings row, with the old lists as defaults. Default: no screen edits them in phase 5; Admin editing comes in phase 9. Changed by SethD on 2026-10-26 and 2026-10-28, Gitea issue 23 (change C8). Built on 2026-10-30: the `checklist_templates` row now holds the default lists by kind, used by a department that has no list of its own. From phase 6 (2026-10-17), for Seth and Q.C. to veto: -27. Q.C. reject: the old build took any non-empty reason. Default: a reason of at least 8 characters, or a well-formed NCR number (`NCR-YYYY-NNNN`) in its place, or both. The NCR is stored on the inspection and linked to pesco-ncr. Applied in phase 6. Changed by SethD on 2026-10-26, Gitea issue 24 (change C2 below). -28. A filed packet in a unit and the unit's ITP: the ITP belongs to the unit, not to a packet, so ITP writes never look at a packet's status. Default: a filed packet does not lock the ITP. This resolves the phase 5 follow-up. Applied in phase 6. Changed by SethD on 2026-10-26, Gitea issue 25 (change C3 below). +27. Q.C. reject: the old build took any non-empty reason. Default: a reason of at least 8 characters, or a well-formed NCR number (`NCR-YYYY-NNNN`) in its place, or both. The NCR is stored on the inspection and linked to pesco-ncr. Applied in phase 6. Changed by SethD on 2026-10-26, Gitea issue 24 (change C2 below). Built on 2026-10-30. +28. A filed packet in a unit and the unit's ITP: the ITP belongs to the unit, not to a packet, so ITP writes never look at a packet's status. Default: a filed packet does not lock the ITP. This resolves the phase 5 follow-up. Applied in phase 6. Changed by SethD on 2026-10-26, Gitea issue 25 (change C3 below). Built on 2026-10-30: ITP writes still never look at a packet, but FILE waits until the unit's ITP is complete. 29. ITP status: the 0003 enum had `open`, `signed` and `reopened`. Default: `itp_status` gains `complete` for a signed-off ITP; a reopened ITP is `open` again with the reopen recorded. `signed` and `reopened` stay in the enum unused, since PostgreSQL cannot drop an enum value. Applied in phase 6. 30. Removing an ITP block signature: the old build let any Q.C. editor undo it. Default: the signer or an admin, while the ITP is open. The block's QN, remarks and customer fields stay. Applied in phase 6. Confirmed by SethD on 2026-10-26, Gitea issue 26: a fix to customer fields or an earlier step's remarks goes back to that step or to Clerical. 31. The signed ITP PDF on sign-off: default a hook (`pdf.itp.file_signed_itp_pdf`) that phase 7 builds. Today it files nothing, the sign-off log says `PDF filing reserved for phase 7`, and the automatic `qc_docs` ITP row is written only when the hook returns a file. Applied in phase 6. Phase 7 (2026-10-17) built the hook: the sign-off files the signed ITP PDF and the automatic ITP document in the same transaction, and a PDF that cannot be built fails the sign-off. From phase 7 (2026-10-17), for Seth and Clerical to veto: -32. Prints: the old build pushed page images into the browser's print dialog and offered the same PDF when the dialog was blocked. Default: a print opens as a stamped PDF in a new browser tab, and the person prints it from the PDF viewer (Ctrl+P). One path is simpler and prints the same stamped pages. Phase 7 (2026-10-17) applied the default. Changed by SethD on 2026-10-26, Gitea issue 27 (change C4 below). +32. Prints: the old build pushed page images into the browser's print dialog and offered the same PDF when the dialog was blocked. Default: a print opens as a stamped PDF in a new browser tab, and the person prints it from the PDF viewer (Ctrl+P). One path is simpler and prints the same stamped pages. Phase 7 (2026-10-17) applied the default. Changed by SethD on 2026-10-26, Gitea issue 27 (change C4 below). Built on 2026-10-30. 33. When the data book is built: default on the server when the packet is filed, inside the filing transaction, stored as an asset on the seal. A data book that cannot be built fails the filing, so nothing is filed half way. Before filing, the data book can be viewed or downloaded at any time from status complete on; that copy is stamped "not yet filed". Phase 7 (2026-10-17) applied the default. Confirmed by SethD on 2026-10-26, Gitea issue 28. 34. Who may read the PDFs and the CSV: default every signed-in role that can open a packet, PM included. Every print and every export is logged with who, when, what and how many pages, in the append-only `print_log` and in the audit. Phase 7 (2026-10-17) applied the default. Confirmed by SethD on 2026-10-26, Gitea issue 29: everyone may view the whole packet; only the holder edits, and missing work goes back to the department that owes it. 35. The old "Original packet PDF" print kind: the rebuild has no VISUAL packet PDF. The drawing pages come from M-Files or a hand upload, and "Drawing (no markup)" prints them as attached. Default: the kind is gone. Phase 7 (2026-10-17) applied the default. Confirmed by SethD on 2026-10-28, Gitea issue 30: nothing is missed. -36. Filing while the feed still shows a work order open in VISUAL: default a second confirmation (`confirm_open_wos`), a warning and not a block, on both routes. The work orders are kept on the seal and in the log note. A hand packet has no feed state and no warning. Phase 7 (2026-10-17) applied the default. Changed by SethD on 2026-10-26, Gitea issue 31 (change C5 below). -37. The controlled-copy stamp carries a short signature made with the seal key, so a printed or exported copy can be checked against the server later. Default: phase 7 ships the signature, the check function and the print log; phase 9 adds the Admin screen that checks a copy. Phase 7 (2026-10-17) applied the default. Changed by SethD on 2026-10-28, Gitea issue 32 (change C9). +36. Filing while the feed still shows a work order open in VISUAL: default a second confirmation (`confirm_open_wos`), a warning and not a block, on both routes. The work orders are kept on the seal and in the log note. A hand packet has no feed state and no warning. Phase 7 (2026-10-17) applied the default. Changed by SethD on 2026-10-26, Gitea issue 31 (change C5 below). Built on 2026-10-30: `confirm_open_wos` is gone and filing is blocked. Two follow-up questions were asked on issue 31 on 2026-10-30 (see C5). +37. The controlled-copy stamp carries a short signature made with the seal key, so a printed or exported copy can be checked against the server later. Default: phase 7 ships the signature, the check function and the print log; phase 9 adds the Admin screen that checks a copy. Phase 7 (2026-10-17) applied the default. Changed by SethD on 2026-10-28, Gitea issue 32 (change C9). Built on 2026-10-30. From phase 8 (2026-10-18), for Seth to veto. The phase 8 design numbered these 35 to 39; 35 to 37 were already taken by phase 7, so they follow on from 38. -38. Hours vs target: the station timer stays the clock of record. "Over target" means timer hours over the work-order target. VISUAL ticket hours are shown beside the timer in every hours table and never decide the flag. Phase 8 (2026-10-18) applied the default. Changed by SethD on 2026-10-26, Gitea issue 1 (change C1 below). -39. PM exports and who reads the reports: every PM export is logged as `report exported` in the audit and in `print_log`, with the scope and the period. Default: any signed-in role may read the PM reports (as every role may open every packet), not only PS-ProjectMgmt. Phase 8 (2026-10-18) applied the default. Changed by SethD on 2026-10-28, Gitea issue 33 (change C10). +38. Hours vs target: the station timer stays the clock of record. "Over target" means timer hours over the work-order target. VISUAL ticket hours are shown beside the timer in every hours table and never decide the flag. Phase 8 (2026-10-18) applied the default. Changed by SethD on 2026-10-26, Gitea issue 1 (change C1 below). Built on 2026-10-30. +39. PM exports and who reads the reports: every PM export is logged as `report exported` in the audit and in `print_log`, with the scope and the period. Default: any signed-in role may read the PM reports (as every role may open every packet), not only PS-ProjectMgmt. Phase 8 (2026-10-18) applied the default. Changed by SethD on 2026-10-28, Gitea issue 33 (change C10). Built on 2026-10-30. 40. Presence shows the display name and the role (the screen the person has open). Default: locked sessions drop out of presence at once; closing the tab drops out within the stream's ping cycle (25 seconds). Phase 8 (2026-10-18) applied the default. Confirmed by SethD on 2026-10-28, Gitea issue 34. 41. The find bar filters are applied by the server on every board and list, so a board never fetches more than it shows. Default: the KPI numbers follow the project pick only, not the other filters. Phase 8 (2026-10-18) applied the default. Confirmed by SethD on 2026-10-28, Gitea issue 35. -42. Mine is decided from the person's AD roles and the screen they are on. Default: the stall's Mine needs the picked station and shows nothing without one. Phase 8 (2026-10-18) applied the default. Changed by SethD on 2026-10-28, Gitea issue 36 (change C11). +42. Mine is decided from the person's AD roles and the screen they are on. Default: the stall's Mine needs the picked station and shows nothing without one. Phase 8 (2026-10-18) applied the default. Changed by SethD on 2026-10-28, Gitea issue 36 (change C11). Built on 2026-10-30. From phase 9 (2026-10-21), for Seth or Spencer to veto: @@ -289,24 +290,31 @@ From phase 9 (2026-10-21), for Seth or Spencer to veto: 49. New departments: default only shop departments can be added in the app (422 `support_fixed` for a support department). The support roles are fixed in the `role_kind` enum and the group map, so a new support department would map to no role. Phase 9 (2026-10-21) applied the default. 50. The profile CSV `stations` cell: default the cell is the person's whole list of station limits across departments and replaces every limit they had; `-` clears them all; an empty cell leaves them. Phase 9 (2026-10-21) applied the default. 51. A direct welder assign: default it does not check `profile_active`, so an Admin's inactive switch hides a person from the picker but a typed assign still works. Phase 9 (2026-10-21) applied the default; say if an inactive profile should be refused there too. -52. A checklist item added while a stage is open: default it applies to that open stage too, because completeness reads the live template, so the item must be ticked before submit. Phase 9 (2026-10-21) applied the default. Replaced by change C8: a template change applies to new packets only. +52. A checklist item added while a stage is open: default it applies to that open stage too, because completeness reads the live template, so the item must be ticked before submit. Phase 9 (2026-10-21) applied the default. Replaced by change C8: a template change applies to new packets only. Built on 2026-10-30: a stage freezes its list at START, so a change reaches only stages not yet started. -Changes decided by SethD on 2026-10-26, to build (phase 10 unless a later plan moves them). Each replaces a default above: +Changes decided by SethD on 2026-10-26. Each replaces a default above. All five were built on 2026-10-30 (branch `seth-changes`): -- C1 (issue 1, Q1 and Q38): the station timer stays and is shown as today, but "over target" is decided by the VISUAL ticket hours against the work-order target, not by the timer. -- C2 (issue 24, Q27): a Q.C. reject always needs a detailed reason; an NCR number no longer stands in for it. A rejected item cannot be accepted until an NCR number is entered. Q.C. can upload the NCR document to the packet, and it goes into the data book. -- C3 (issue 25, Q28): a packet whose unit has an ITP that is not complete cannot be filed, so no data book is built. The packet goes back to have the ITP completed. -- C4 (issue 27, Q32): prints go straight to the browser's print dialog from the page, as the old build did, with no new tab. The stamped pages stay the same. -- C5 (issue 31, Q36): filing is blocked, not warned, while VISUAL still shows a work order open. The packet waits in a Clerical "Ready to close" column until the feed sees VISUAL closed, then moves to "Ready for data book". This applies to both routes unless Seth says Shipping is different. +- C1 (issue 1, Q1 and Q38): the station timer stays and is shown as today, but "over target" is decided by the VISUAL ticket hours against the work-order target, not by the timer. Built 2026-10-30: `domain/pm.py` `is_over()` flags a packet when its ticket hours are above a target above zero, so a packet with no ticket hours is never over. The hours tables, the "Packets over target" table, the KPI, the summary line, the CSV and the report PDF follow, with columns named "Ticket h / target" and "Over target (tickets)". The timer column stays. +- C2 (issue 24, Q27): a Q.C. reject always needs a detailed reason; an NCR number no longer stands in for it. A rejected item cannot be accepted until an NCR number is entered. Q.C. can upload the NCR document to the packet, and it goes into the data book. Built 2026-10-30: QC_REJECT needs a reason of 8 characters and may also carry an NCR number. When a reject since the last accept has no NCR number, QC_ACCEPT and QC_ACCEPT_AND_ROUTE need one in the request (precondition `ncr_given`, 422 `ncr_required`); the number is stored on the accepting inspection and in the log. The packet read has `ncr_required`, and the Q.C. bar shows the NCR field as required. An `ncr` packet document is in section 5 of the data book. +- C3 (issue 25, Q28): a packet whose unit has an ITP that is not complete cannot be filed, so no data book is built. The packet goes back to have the ITP completed. Built 2026-10-30: precondition `itp_complete` on FILE. An ITP that is not complete (open, signed or reopened) blocks; a unit with no ITP does not. The reason shows under File in the close panel. +- C4 (issue 27, Q32): prints go straight to the browser's print dialog from the page, as the old build did, with no new tab. The stamped pages stay the same. Built 2026-10-30: every print in the app goes through `frontend/src/lib/printPdf.ts`, which renders the stamped PDF with pdf.js into a hidden print area and opens the print dialog. Viewing a PDF (data book, traveler, ITP PDF, PM report) still opens a tab. The server did not change. +- C5 (issue 31, Q36): filing is blocked, not warned, while VISUAL still shows a work order open. The packet waits in a Clerical "Ready to close" column until the feed sees VISUAL closed, then moves to "Ready for data book". This applies to both routes unless Seth says Shipping is different. Built 2026-10-30: precondition `closed_in_visual` replaces `vclosed_if_databook`. On a VISUAL packet the feed is the one writer of `vclosed_at`: it sets it when every leg is closed or cancelled and clears it when a leg shows open again, so the Clerical board moves the packet by itself. FILE needs it and no open leg, on both routes. A hand packet has no feed state, so Clerical still confirms the close by hand (SET_VCLOSED, Data Book route only); SET_VCLOSED is refused on a VISUAL packet. `confirm_open_wos` and the 409 `open_work_orders` are gone. + Asked on issue 31 on 2026-10-30, open until answered: (a) a work order deleted from VISUAL while open keeps its last status, so its packet can never file. Should a leg gone for 2 runs count as closed, or should an Admin close it with a reason? (b) Shipping packets now wait for the VISUAL close too. Is that right for Shipping, or should Shipping file without it? Until Seth answers, both stay as built. -Changes decided by SethD on 2026-10-28, to build in phase 10: +Changes decided by SethD on 2026-10-28. All six were built on 2026-10-30 (branch `seth-changes`): -- C6 (issue 8, Q9, D7): the data book leaves out the packet history pages. It is the one copy, and it goes to the customer. History stays readable in Packet Station. -- C7 (issue 18, Q21): every received short also needs the department supervisor's approval, one short at a time. The supervisor checks the part and its heat number against what the warehouse issued. A packet cannot be marked complete until every short is received and approved. -- C8 (issue 23, Q26 and Q52): checklists become per department. Each department supervisor edits their own department's checklist, Admin edits any, and Q.C. does not edit department checklists. A change applies to packets not yet started, never to packets already started. -- C9 (issue 32, Q37): the stamp check is open to Admin, Q.C. and Clerical. -- C10 (issue 33, Q39): the PM screen and its reports are for PS-ProjectMgmt and Admin only. -- C11 (issue 36, Q42): the Stall screen shows a welder the packets assigned to him, each at the stall the supervisor picked when assigning. The welder does not pick a stall. Moving a welder to another stall is a re-assign by the supervisor. +- C6 (issue 8, Q9, D7): the data book leaves out the packet history pages. It is the one copy, and it goes to the customer. History stays readable in Packet Station. Built 2026-10-30: section 7 "Record history" is gone and the data book has six sections. The change-order table it held moved to a "Change order record" page at the end of section 1, only when the packet has change orders. Say if that page should go too. +- C7 (issue 18, Q21): every received short also needs the department supervisor's approval, one short at a time. The supervisor checks the part and its heat number against what the warehouse issued. A packet cannot be marked complete until every short is received and approved. Built 2026-10-30: migration 0011 adds `short_approvals` (append-only, one row per receipt). New action SHORT_APPROVE: the supervisor of the packet's current department (a lead is not enough), Admin through the bypass, any status but filed, signature "Approve a received short", at `POST /api/packets/{id}/shorts/receipts/{receipt_id}/approve`. Precondition `shorts_approved` on QC_ACCEPT and FILE, not on QC_ACCEPT_AND_ROUTE. The Materials tab shows each receipt with who received it and who approved it. There is no list of receipts to approve on the supervisor board yet. +- C8 (issue 23, Q26 and Q52): checklists become per department. Each department supervisor edits their own department's checklist, Admin edits any, and Q.C. does not edit department checklists. A change applies to packets not yet started, never to packets already started. Built 2026-10-30: a department's own list is the settings row `checklist:`; without one it uses the default for its kind (the weld or plain list in `checklist_templates`). `GET /api/checklists`, `GET` and `PUT /api/checklists/{dept}` with version compare-and-set. The department's Supervisor (not a lead) and Admin may save, in a password session. `packet_stages.checklist_template` (migration 0011) freezes the list at START, and every reader uses the frozen list when set. New screen `/checklists`. +- C9 (issue 32, Q37): the stamp check is open to Admin, Q.C. and Clerical. Built 2026-10-30: `POST /api/stamp-verify` and `POST /api/stamp-verify/pdf` for Admin and every Q.C. and Clerical level, in a password session. The `/api/admin` paths stay as aliases. New screen `/stamp-check`. +- C10 (issue 33, Q39): the PM screen and its reports are for PS-ProjectMgmt and Admin only. Built 2026-10-30: every `/api/pm/*` endpoint is 403 for anyone else. No other screen calls `/api/pm`. The PM screen entry shows only for those roles. +- C11 (issue 36, Q42): the Stall screen shows a welder the packets assigned to him, each at the stall the supervisor picked when assigning. The welder does not pick a stall. Moving a welder to another stall is a re-assign by the supervisor. Built 2026-10-30: `GET /api/stall/mine` lists the packets assigned to the person (assigned, in progress, in review), each with the station the supervisor picked, across stations. The Stall screen has no station picker, and the stall's Mine no longer needs a station. Someone with nothing assigned sees "Your supervisor assigns packets to you." + +Known edges of C1 to C11 at deploy: + +- Packets in flight. Every short receipt recorded before the deploy has no approval, so the supervisor approves it before the next Q.C. accept, or before filing a packet that is already complete. A Q.C. reject since the last accept that has no NCR number needs one at the next accept. Nothing is backfilled. +- Stages started before migration 0011. The migration freezes the open stage of every packet in progress, in review or at Q.C. to the default list for its kind, from the `checklist_templates` row. A department edit does not reach those stages. When that row is missing, such a stage has no frozen list and reads its department's live list until it closes. A packet whose stages all closed before 0011 also reads the live list, so a later edit changes what its checklist shows. +- The feed sets `vclosed_at` in any status before filed, so a packet can come back from Q.C. straight into "Ready for data book". ## Verification diff --git a/docs/POLICY.md b/docs/POLICY.md index 0a4cd5f..36c3bb5 100644 --- a/docs/POLICY.md +++ b/docs/POLICY.md @@ -90,9 +90,9 @@ Statuses in workflow order: draft, warehouse, ready, assigned, in_progress, revi | send_to_qc | DEPT_SUP | review | qc | no | yes | qc_book_spool_location | Send to Q.C. | | | route_from_review | DEPT_SUP | review | ready | required | yes | target_dept_given | Route to another department | | | qc_reject | QC | qc | review | no | | reason_given | Q.C. reject | yes | -| qc_accept | QC | qc | complete | no | yes | qc_missing_empty, route_given | Q.C. accept | | -| qc_accept_and_route | QC | qc | ready | required | yes | qc_missing_empty, target_dept_given | Q.C. accept and route | | -| file | CL | complete | filed | no | yes | vclosed_if_databook | File in the data book | | +| qc_accept | QC | qc | complete | no | yes | qc_missing_empty, shorts_approved, route_given, ncr_given | Q.C. accept | | +| qc_accept_and_route | QC | qc | ready | required | yes | qc_missing_empty, target_dept_given, ncr_given | Q.C. accept and route | | +| file | CL | complete | filed | no | yes | closed_in_visual, itp_complete, shorts_approved | File in the data book | | | admin_reopen | ADMIN | filed (filed_ok) | complete | no | | reason_given | Reopen a filed packet | yes | | edit_header | CL | draft | | | | | | | | edit_drawing_ref | CL | any | | | | | | | @@ -101,6 +101,7 @@ Statuses in workflow order: draft, warehouse, ready, assigned, in_progress, revi | edit_wos | CL | draft | | | | | | | | edit_bom | HOLDER | draft, warehouse, SHOP | | | | | | | | wh_receive_short | WH | any | | | | short_open | | | +| short_approve | DEPT_SUP | any | | | | | Approve a received short | | | edit_heats | HOLDER | warehouse, SHOP | | | | | | | | edit_heat_table | HOLDER | warehouse, SHOP | | | | | | | | edit_welds | HOLDER | SHOP | | | | | | | @@ -113,7 +114,7 @@ Statuses in workflow order: draft, warehouse, ready, assigned, in_progress, revi | doc_add | CL_OR_QC | any | | | | | | | | doc_remove | CL_OR_QC | any | | | | | | | | rev_approve | CL_SUP | draft | | | | reason_given | Approve the M-Files revision | yes | -| set_vclosed | CL | complete, only when routed_to is databook | | | | | | | +| set_vclosed | CL | complete, only for a hand packet routed to the data book | | | | | | | | set_databook_at | CL_OR_QC | any (filed_ok) | | | | | | | | remove | CL | draft | | | | reason_given | | yes | | co_issue | CL | any | | | | | Issue a change order | | @@ -141,7 +142,11 @@ The `vis_*` actions are system only. People can never do them, and the worker ca The Q.C. folder and ITP actions ("no packet (scoped)") belong to a project or a unit, not to a packet. They are checked with `check_scoped()` (see "Scoped checks"), never with the packet status, custody or the filed lock. -qc_reject takes an NCR number in place of the reason text (see "Q.C. (phase 6)", "Reject"). +qc_reject always needs a reason of 8 characters and may also carry an NCR number. After a reject with no NCR number, qc_accept and qc_accept_and_route carry one (Gitea issue 24, change C2; see "Q.C. (phase 6)", "Reject"). + +short_approve is the department supervisor's approval of one received short (Gitea issue 18, change C7; see "Supervisor approval of received shorts"). A lead is not enough. The filed lock applies, so it is open in any status but filed. + +set_vclosed is for hand packets only. On a VISUAL packet the feed sets the close (Gitea issue 31, change C5; see "Close in VISUAL"). ## Preconditions @@ -161,29 +166,34 @@ Each precondition is a named predicate over the packet state and the request inp | qc_book_spool_location (input) | book and spool confirmed and a location given | Confirm the book and the spool, and give the location. | | qc_missing_empty | the Q.C. record has nothing missing | Q.C. still needs: (the list). | | reason_given (input) | a reason of at least 8 characters after cleaning | Give a reason of at least 8 characters. | -| vclosed_if_databook | routed to Shipping, or closed in VISUAL | Close the job in VISUAL first. | +| closed_in_visual | VISUAL packet, either route: the feed shows no work order open and has set `vclosed`. Hand packet: routed to Shipping, or Clerical confirmed the close | Hand packet: Close the job in VISUAL first. VISUAL packet: VISUAL still shows work orders open on this packet: L1/001, L2/7A. Filing waits until VISUAL closes them. / Filing waits until the VISUAL feed sees the job closed. | +| itp_complete | the packet's unit has no ITP, or its ITP is complete | Complete the unit's ITP first. Q.C. signs it off on the ITP page. | +| shorts_approved | no open short, and every short receipt has the supervisor's approval | Every short must be received and approved by the supervisor first. | +| ncr_given (input) | no Q.C. reject since the last accept lacks an NCR number, or the request gives a well-formed `ncr` | Enter the NCR number of the reject first. | | co_staged_if_dwg_change (input) | no drawing change, or the new drawing is staged (phase 3) | Stage the new drawing for the change order first. | | route_given (input) | routed_to is databook or shipping | Choose Data Book or Shipping. | | target_dept_given (input) | a shop department different from the packet's; from a draft any shop department | Choose a department. / Choose a different department. | -"(input)" marks a precondition that reads request input, not packet state. `can()` skips those. +"(input)" marks a precondition that reads request input, not packet state. `can()` skips those. `ncr_given` reads the state too (`ncr_required`), but the user gives the number with the step, so `can()` skips it and the packet read carries `ncr_required` for the screen. + +closed_in_visual replaced vclosed_if_databook (Gitea issue 31, change C5). itp_complete is Gitea issue 25 (change C3). shorts_approved is Gitea issue 18 (change C7). ncr_given is Gitea issue 24 (change C2). All built 2026-10-30. A FILE that fails several shows every reason in `blocked.file`. ## Order of checks `check(principal, state, action, *, extra={}, it_reason=None) -> Decision` runs these in this order and stops at the first failure: 1. **Removed.** A removed packet refuses everything, Admin included. Code `removed`. -2. **Status.** The status must be in the rule's from set. RETURN_TO_CLERICAL also needs wh_skipped. SET_VCLOSED also needs routed_to databook. An unknown status fails here too. Code `wrong_status`. +2. **Status.** The status must be in the rule's from set. RETURN_TO_CLERICAL also needs wh_skipped. SET_VCLOSED also needs a hand packet routed to the data book; on a VISUAL packet it fails with "The VISUAL feed marks this job closed when it sees every work order closed." An unknown status fails here too. Code `wrong_status`. 3. **Change-order hold.** A held action refuses while an approved change order waits for verification, or a pending one is on hold. Admin included. Code `co_hold`, with the hold text. 4. **System.** A system-only action refuses every person (`system_only`). The system principal passes only system-only actions (`forbidden`). 5. **Admin bypass.** An admin who is not allowed by the rule's Who passes who and custody when an IT reason of at least 8 characters is given. Without one the code is `it_reason_required`. SET_DATABOOK_AT needs no reason. The decision is marked `admin_bypass`, and the write marks the log row as an IT edit. 6. **Filed lock.** A filed packet refuses everything except SET_DATABOOK_AT and ADMIN_REOPEN, Admin included. Code `filed`: "This packet is filed and locked. IT can reopen it if a correction is needed." 7. **Who and custody.** Code `forbidden`. For a step: "Your access doesn't allow this step." For an edit: "Your access doesn't allow changing (what) on this packet. It's with (holder) right now." PM fails here for everything. -8. **Preconditions,** in rule order, never bypassed. Code `precondition` with the message, or `reason_required` when the missing piece is the rule's reason, so the screen can ask for one. +8. **Preconditions,** in rule order, never bypassed. Code `precondition` with the message, `reason_required` when the missing piece is the rule's reason, so the screen can ask for one, or `ncr_required` when `ncr_given` fails. `Decision` carries `ok`, `code`, `why`, `admin_bypass`, `signature` (the meaning text when the rule needs one) and, in read mode only, `reasons`. -The packet API answers 403 with the code for most failures, and 400 for `it_reason_required` and `reason_required`. +The packet API answers 403 with the code for most failures, 400 for `it_reason_required` and `reason_required`, and 422 for `ncr_required`. ### Scoped checks @@ -198,7 +208,7 @@ There is no removed, status, change-order hold, filed lock or custody step: ther ## Signatures -These actions need a signature: submit, send_to_qc, route_from_review, qc_reject, qc_accept, qc_accept_and_route, file, admin_reopen, edit_document_set (attach only), rev_approve, co_issue, co_approve, co_reject, co_verify, itp_block_sign and itp_sign_off. The rule's signature text is the meaning that is signed. +These actions need a signature: submit, send_to_qc, route_from_review, qc_reject, qc_accept, qc_accept_and_route, file, admin_reopen, short_approve, edit_document_set (attach only), rev_approve, co_issue, co_approve, co_reject, co_verify, itp_block_sign and itp_sign_off. The rule's signature text is the meaning that is signed. The request carries `sign: {factor, secret}`. The write pipeline calls the phase 1 helper `packetstation.auth.signatures.sign()` inside the same transaction, after the policy passes. It checks the PIN or domain password against the session's own user. A PIN works in any session. It logs the attempt and applies the five-strike PIN disable and the three-strike session end. It writes an append-only `signatures` row with the user, session, account, display name, session kind, factor, action, meaning, packet, subject and a sha256 over the canonical JSON of what was signed (the action, the packet id, the expected state and the request fields). The `packet_transitions` and `packet_log` rows point at it. @@ -211,7 +221,7 @@ The request carries `sign: {factor, secret}`. The write pipeline calls the phase - **Work orders** are a draft-only full replace with If-Match on the packet version. - **Shorts and change-order actions** are state compare-and-set. Checklist keys are written one key at a time. The heat table position is an idempotent upsert. A change-order verification is an insert, so a second one is "already verified". - **ITP lines** are approved under the `itps` row lock, with a state check and an optional `If-Match: `. ITP header fields use the same `set` and `base` field compare-and-set as packet headers. -- **Append-only tables** never conflict: packet_transitions, packet_log, qc_history, short_receipts, project_renumbers, itp_rejects, itp_log, print_log and co_verifications. Triggers refuse UPDATE, DELETE and TRUNCATE, and the app role has no grant for them. +- **Append-only tables** never conflict: packet_transitions, packet_log, qc_history, short_receipts, short_approvals, project_renumbers, itp_rejects, itp_log, print_log and co_verifications. Triggers refuse UPDATE, DELETE and TRUNCATE, and the app role has no grant for them. ## Reads: can, blocked and holder @@ -283,6 +293,16 @@ Receiving short parts (`POST .../bom/{item}/short/receipts`) picks its action fr The warehouse still cannot change any other field of a packet it no longer holds. EDIT_BOM stays the holder's. A receipt needs a heat unless the line is N/A, and its quantity is 1 to what is still open. +### Supervisor approval of received shorts + +Built 2026-10-30 (Gitea issue 18, change C7). Every receipt also needs the approval of the department supervisor, who checks the part and its heat number against what the warehouse issued. One approval per receipt. + +- SHORT_APPROVE: the supervisor of the packet's current department (DEPT_SUP; a lead is not enough), or Admin through the bypass with an IT reason. Any status but filed. Signature "Approve a received short". +- `POST /api/packets/{id}/shorts/receipts/{receipt_id}/approve` `{sign, note?, it_reason?}` answers the packet read. A receipt not on the packet is 404 `not_found`. A second approval is 409 `already_approved` "This receipt is already approved.", checked before the signature and again under the packet lock; the unique key on `short_approvals.receipt_id` backs it. +- The approval row keeps the department the packet was in. After a QC_ACCEPT_AND_ROUTE the new department's supervisor approves the earlier receipts. +- `shorts_unapproved` (packet read, board cards and the policy state) counts open shorts plus receipts without an approval. The precondition `shorts_approved` needs it at 0 on QC_ACCEPT and FILE. QC_ACCEPT_AND_ROUTE does not need it, because the packet goes on to another department and is not complete. +- The packet read has `shorts`: every short of the packet, open or closed, each with its `receipts` and each receipt's `approval` (`null`, or `{at, by, note, dept_code}`). A short that is fully received closes and leaves the BOM line, but its receipts still need approval, so the screen reads `shorts`. `can` holds `short_approve` for the supervisor in every status but filed; the screen shows Approve only on a receipt without an approval. + ### MTR files The MTR on a line must be an asset whose type is PDF, PNG or JPEG. Anything else, or an asset that is not there, is 422 `invalid_asset`. Clearing the MTR is allowed. @@ -307,6 +327,7 @@ The `action` column of `packet_log` for each warehouse write. `{item}` is the BO | Short opened | `Short recorded · item {item}` | `{qty} of {need} short · {reason} · {ref} · ETA {eta}`, parts present only | | Short edited | `Short updated · item {item}` | the same as recorded | | Not short after all | `Short cleared · item {item}` | | +| Short approved | `Short approved · item {item}` | `{qty} pc · heat {heat} · {note}`, parts present only | | Short received | `Short received · item {item}` | `{qty} pc · heat {heat} · {still} still short` or `... · short closed`, then ` · packet {status}` (lower case) and ` in {station}` when at a station | | Stage incoming to pulling | `Warehouse: Started pulling` | | | Stage to hold | `Warehouse: Put on hold` | `{reason} · {note}` | @@ -359,7 +380,7 @@ A lead holds a packet in review (see "Custody"), but return_to_station, send_to_ | Step | Precondition | Passes when | Message | |---|---|---|---| | assign | welder_set (input) | the request gives station_id and welder_id | Pick a station and a welder. | -| submit | checklist_complete | every key of the department's checklist is done | Finish the checklist first. | +| submit | checklist_complete | every key of the stage's checklist is done: the list frozen at START, else the department's live list (see "Checklists") | Finish the checklist first. | | submit | welds_recorded | at least one live weld whose `dept_code` is the packet's current department, in a weld department | Record at least one weld first. | | submit | filler_set | filler metal entered on the open stage, in a weld department | Enter the filler metal first. | | send_to_qc | qc_book_spool_location (input) | book and spool confirmed and a location given | Confirm the book and the spool, and give the location. | @@ -410,8 +431,9 @@ The sibling is a live packet in the same unit, with a different id and the same ### Shop screens - `GET /api/shop/board` covers the shop departments where the person is lead or supervisor. Admin sees every active shop department. A department outside that scope is 403 `forbidden`. A person who leads no department gets 403 `forbidden` "You do not lead a department." The change order list also shows open change orders anywhere that wait on one of the person's departments. -- `GET /api/shop/stations` lists the stations the person may pick for the Stall screen: every station of their shop departments at any level, cut to their station limits for a worker. Admin gets every station. -- `GET /api/shop/stall?station_id=` is 403 for a station not in that list. Admin passes. +- `GET /api/stall/mine` is the Stall screen (Gitea issue 36, change C11, built 2026-10-30). It lists the packets assigned to the signed-in person (`welder_id` is them; status assigned, in progress or review), each at the station the supervisor picked, across stations. Nobody picks a station on the Stall screen, and the request names none: the session decides. The answer is `{station: null, mine, submitted, facets, empty}`; `mine` holds the assigned and in-progress cards, `submitted` the cards in review, and every card carries `station_id` and `station_name`. With nothing assigned, `empty` says "Nothing is assigned to you now. Your supervisor assigns packets to you." The find bar narrows the cards; a `station_id` in the query narrows, never widens. Moving a welder to another station is a re-assign by the supervisor. +- `GET /api/shop/stations` lists the stations of the person's shop departments, cut to their station limits for a worker. Admin gets every station. The Stall screen no longer uses it. +- `GET /api/shop/stall?station_id=` is the board of one station, 403 for a station not in that list. Admin passes. It stays for now; the Stall screen no longer uses it. ### Presence @@ -470,7 +492,7 @@ Q.C. holds a packet in status qc. EDIT_QC (QC, status qc) covers every write to ### Accept -Two actions accept. Both are Q.C. from status qc, held by an open change order, gated by `qc_missing_empty`, and signed. +Two actions accept. Both are Q.C. from status qc, held by an open change order, gated by `qc_missing_empty` and `ncr_given`, and signed. qc_accept, which marks the packet complete, also needs `shorts_approved` (Gitea issue 18, change C7): no open short and every receipt approved by the supervisor. qc_accept_and_route does not, because the packet goes on to another department. | Route | Action | Body | Result | |---|---|---|---| @@ -480,6 +502,8 @@ Two actions accept. Both are Q.C. from status qc, held by an open change order, The signature meanings are "Q.C. accept" and "Q.C. accept and route". The accept closes the inspection into `qc_history` and closes the stage with the inspection as its `qc_snapshot`. The note in the log counts every live weld of the packet. +NCR on the accept (Gitea issue 24, change C2, built 2026-10-30). The packet read's `ncr_required` is true when a Q.C. reject after the packet's last Q.C. accept has no NCR number (a `qc_history` row with result `rejected` and no `snapshot.ncr`). Then both accepts need `ncr` in the request: without it, or with a blank one, the answer is 422 `ncr_required` "Enter the NCR number of the reject first." A malformed one is 422 `invalid_ncr`. The number goes through `validate_ncr`, is stored on the accepting inspection (`qc_inspections.ncr`, so `qc_last` and the `qc_history` snapshot show it) and is added to the log note. An NCR given on an accept that does not need one is stored the same way. `can()` skips `ncr_given`, since the number comes with the step; the screen shows the NCR field as required from `ncr_required`. + ### What Q.C. still needs `qc_missing_empty` reads the list built by `checklists.qc_missing()`. Its message is `Q.C. still needs: ` followed by the parts joined by `, ` and a full stop. `blocked.qc_accept` and `blocked.qc_accept_and_route` carry it, so the screen shows Accept disabled with the reason. @@ -494,11 +518,14 @@ The signature meanings are "Q.C. accept" and "Q.C. accept and route". The accept ### Reject -qc_reject is Q.C. from status qc back to review, signed ("Q.C. reject"). It takes a reason, an NCR number, or both: +qc_reject is Q.C. from status qc back to review, signed ("Q.C. reject"). It always needs a detailed reason and may also carry an NCR number (Gitea issue 24, change C2, built 2026-10-30): -- An NCR number goes through `validate_ncr`: trimmed, upper case, then the pattern `NCR-` four digits `-` four digits. A bad one is 422 `invalid_ncr` "Enter an NCR number like NCR-2026-0021." -- The reason checked by `reason_given` is the NCR alone (`NCR-2026-0021`) when no text is given, else `NCR-2026-0021 · `. So an NCR number stands in for the reason, and the text has no minimum when an NCR is given. -- With neither, the answer is 400 `reason_required` with the rule's message. A reason without an NCR needs 8 characters. +- The reason must pass `reason_given`: at least 8 characters after cleaning. An NCR number no longer stands in for it. Without one the answer is 400 `reason_required` with the rule's message. +- An NCR number, when given, goes through `validate_ncr`: trimmed, upper case, then the pattern `NCR-` four digits `-` four digits. A bad one is 422 `invalid_ncr` "Enter an NCR number like NCR-2026-0021." +- With both, the return note and the result note read `NCR-2026-0021 · `. +- A reject without an NCR number means the next accept must carry one (see "Accept"). + +Q.C. can add the NCR document to the packet as kind `ncr` (see "Packet documents"). It goes into section 5 of the data book. The reject stores the NCR on the inspection, sets the packet's `return_note` to the reason, and closes the inspection into `qc_history` at once. The packet in review has no open inspection; the read model gives the reject as `qc_last`, and the screens show the "Q.C. rejected" callout from it. The supervisor's next send to Q.C. opens a fresh inspection. @@ -596,6 +623,7 @@ The `action` and `note` of `packet_log` for each Q.C. packet write. | qc_accept to the data book | `Q.C. accepted → Clerical – Data Book` | `Quinn QC [Q1] · 3 welds VT accepted`, plus ` · RT Acceptable` when an NDE method other than None required is set, plus ` · ` when the step carries a note | | qc_accept to Shipping | `Q.C. accepted → Shipping` | the same | | qc_accept_and_route | `Q.C. accepted → 09 Hydro` | `Quinn QC [Q1] · from 03 Weld Parts` | +| either accept, with an NCR number | as above | the note above plus ` · NCR-2026-0021` | | qc_reject | `Q.C. rejected → supervisor` | `NCR-2026-0021 · · welds W1, W3` (parts present only; the rejected welds by number) | | Document added | `Document added` | `