Phase 7: close, file, PDFs and prints
Clerical close and file: the VISUAL observed state beside the close confirm, a 409 with a second confirm when the feed still shows work orders open, filing with a signature and the HMAC seal, the data book built on the server and stored on the seal in the filing transaction, admin reopen keeping the broken seal with its data book. Server PDFs with reportlab, pypdf and pypdfium2: the manufacturing data book page for page after the old build (cover, index, drawings with VOID stamps, traceability and heat map rasters, weld maps and welding records, Q.C. inspections, Q.C. and test documents, MTRs, record history), the job traveler with work order QR codes, the ITP PDF filed automatically at sign-off, every print kind with the PRINTED COPY stamp, the controlled-copy stamp signed with the seal key, PDF metadata, the traceability CSV with the formula guard, and the append-only print_log with an audit row for every print and export (migration 0008). Frontend: the close and file panel, the print menu and its entry points, the printed copies list on the History tab, the ITP PDF buttons. Tests: server (close, file, reopen, seal, the builders, prints, CSV, stamps), frontend, and the Playwright close and print scenarios. Docs updated; SECURITY closes M2, S6, S7 and S8. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
+62
-3
@@ -211,7 +211,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: <row_version>`. 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 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, 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
|
||||
|
||||
@@ -556,7 +556,7 @@ The itp_edit_line writes:
|
||||
- NCR entered later: the line must have an open reject without an NCR (409 `no_open_reject`). An `itp_rejects` row (kind `ncr`) is added.
|
||||
- Block fields: `qn` 40, `remarks` 400, `cust_name` 80, `cust_at` (today when a name is given), `cust_clear` clears the customer entry. An empty `cust_name` without `cust_clear` is 422 `invalid_field` "Enter the customer rep's name." No signature.
|
||||
|
||||
A write to a complete ITP, other than the reopen, is 409 `itp_complete` "The ITP is signed off and locked. An admin can reopen it." The sign-off sets status `complete` and calls the phase 7 hook `pdf.itp.file_signed_itp_pdf()`. Today the hook returns nothing. When it returns an asset, the sign-off sets `signoff_asset_id` and files a `qc_docs` row on the unit (type `ITP`, `auto`), superseding any earlier automatic ITP document of the unit. A reopen sets status `open` with the reason. Block and line signatures stay; the inspector undoes what must change.
|
||||
A write to a complete ITP, other than the reopen, is 409 `itp_complete` "The ITP is signed off and locked. An admin can reopen it." The sign-off sets status `complete` and calls `pdf.itp.file_signed_itp_pdf()`, which builds the signed ITP PDF (phase 7). The sign-off sets `signoff_asset_id` and files a `qc_docs` row on the unit (type `ITP`, `auto`), superseding any earlier automatic ITP document of the unit. A PDF that cannot be built answers 500 `itp_pdf_failed` and nothing is signed off. A reopen sets status `open` with the reason. Block and line signatures stay; the inspector undoes what must change.
|
||||
|
||||
ITP actions never look at a packet. A filed packet in the unit does not lock the ITP, because the ITP belongs to the unit (the phase 5 follow-up, open question 28).
|
||||
|
||||
@@ -618,11 +618,70 @@ The `action` and `note` of `itp_log` (and the audit row's `detail.text`) for eac
|
||||
| Block signed | `ITP block 3 signed` | `Sections 1–3` or `Section 4` |
|
||||
| Block unsigned | `ITP block 3 signature removed` | |
|
||||
| Customer complete | `ITP block 3 customer complete recorded`, `ITP block 3 customer complete cleared` | `Rick Inspector` (the cleared name on a clear) |
|
||||
| Sign-off | `ITP signed off` | `PDF filed` when the hook filed one, else `PDF filing reserved for phase 7` |
|
||||
| Sign-off | `ITP signed off` | `PDF filed` |
|
||||
| Reopen | `ITP reopened` | the reason |
|
||||
|
||||
A Q.C. folder document writes an audit row `qc_doc.add` or `qc_doc.revise` (subject `qc_doc`), with `doc_id`, `project_id`, `unit_id` and `replaces` in the detail, `detail.text` `Q.C. document added` or `Q.C. document revised` and `detail.note` `L670 · WPS / PQR · WPS-101 GTAW/SMAW Rev 2 · wps.pdf`.
|
||||
|
||||
## Phase 7: close, file, PDFs and prints
|
||||
|
||||
Nothing in the rule table changes in phase 7. FILE, ADMIN_REOPEN, SET_VCLOSED and SET_DATABOOK_AT keep their rows.
|
||||
|
||||
### Close in VISUAL
|
||||
|
||||
SET_VCLOSED is Clerical, in status complete, only when the packet is routed to the data book (`POST /api/packets/{id}/vclosed` `{closed}`). Confirming sets `vclosed_at` and `vclosed_by`. Taking it back clears both ("Not closed in VISUAL, move back"). A packet routed to Shipping files without it.
|
||||
|
||||
The packet read carries `vclose_check`, outside the seal. It is null for a hand packet. For a feed packet: `observed_at` and `closed_at` from the feed's VISUAL state, `open_wos` (the legs whose status is not `C` and not cancelled, as `wo_base/lot` text, never cast), and `agrees` (true when the Clerical confirm and the feed's closed state say the same). The feed state is a hint. Clerical's confirm is the record, and the precondition `vclosed_if_databook` reads only the confirm.
|
||||
|
||||
### Filing
|
||||
|
||||
FILE is Clerical, from complete, held by an open change order, signed ("File in the data book"), with the precondition `vclosed_if_databook` ("Close the job in VISUAL first.").
|
||||
|
||||
When `vclose_check.open_wos` is not empty, the transition must carry `confirm_open_wos: true`. Without it the answer is 409 `open_work_orders` "VISUAL still shows 2 work orders open on this packet: L489052/1, L489052/2. File anyway?" with `work_orders` in the body. It is a warning, not a block, on both routes. The check runs after the policy and before the signature, so a cancelled confirm costs no PIN attempt. With the confirm, the seal keeps the work orders in `open_wos` and the log note says so. A hand packet has no feed state and no warning.
|
||||
|
||||
In one transaction, filing sets status filed and `databook_at`, writes the seal, builds the data book, stores it as an asset on the seal, and writes the log and audit rows. A data book that cannot be built answers 500 `databook_failed` "The data book could not be built: <reason>. Nothing was filed." and nothing is written. A part that cannot be read (a missing file, an unreadable MTR) does not fail the filing: it becomes a warning row in the data book index.
|
||||
|
||||
### The filed lock
|
||||
|
||||
A filed packet refuses every write except SET_DATABOOK_AT and ADMIN_REOPEN, Admin included (code `filed`). Reads, PDFs, the CSV and prints stay open.
|
||||
|
||||
### The seal and reopen
|
||||
|
||||
The seal is HMAC-SHA256 with the server seal key over the packet's read model in seal mode. `GET /api/packets/{id}/seal` rebuilds the content and answers `{seal, valid, seals}`. `valid` is null when the key is not available.
|
||||
|
||||
ADMIN_REOPEN is Admin, from filed, with a reason of at least 8 characters and a signature ("Reopen a filed packet"). The packet goes back to complete. The seal is marked broken with `broken_at`, `broken_by` and `broken_reason`. The row and its data book are kept: they are the record of what was filed. Filing again writes a second seal with its own data book. The packet read shows the newest seal, broken or not, with its data book, and `seals`, the number of seal rows.
|
||||
|
||||
### PDFs, the CSV and prints
|
||||
|
||||
Every read below needs a signed-in session and a packet that exists and is not removed (404 `not_found`). Every role that can open the packet may read them, PM included. No policy action guards them, and the filed lock does not apply. Each one writes a `print_log` row and an audit row after the bytes are built.
|
||||
|
||||
| Endpoint | What | When | Log kind | Audit |
|
||||
|---|---|---|---|---|
|
||||
| `GET /api/packets/{id}/pdf/databook` | The filed data book as stored, or one built now and stamped "not yet filed" | complete or filed, else 409 `wrong_status` "The data book is built once the packet is back from Q.C." | `databook-export` | `export` |
|
||||
| `GET /api/packets/{id}/pdf/traveler` | The job traveler with the work order QR codes | any status | `traveler` | `export` |
|
||||
| `GET /api/packets/{id}/export/traceability.csv` | The traceability CSV, with the formula guard | any status | `csv` | `export` |
|
||||
| `GET /api/packets/{id}/print/{kind}` | A printed copy: `traveler`, `map-h`, `map-w`, `clean`, `void`, `databook`, `doc` | `databook` from complete on (409); `void` only with void pages (404 `nothing_to_print` "Nothing to print."); `doc` only for an asset of the packet, its unit or project folder, or its filed data book (404) | the kind | `print` |
|
||||
| `GET /api/packets/{id}/prints` | The print log of the packet, newest first, 200 rows | any status | | |
|
||||
| `GET /api/itps/{id}/pdf` | The ITP PDF built now, stamped signed off or open | any status | `itp-pdf` | `export` |
|
||||
| `GET /api/itps/{id}/print` | The ITP PDF stamped PRINTED COPY | any status | `itp` | `print` |
|
||||
|
||||
`?download=1` serves the file as an attachment. `?sheet=n` limits `map-h`, `map-w` and `clean` to packet page n. Prints and exports write no `packet_log` row, because they change nothing on the packet. The History tab lists them from `GET .../prints`.
|
||||
|
||||
### Log texts
|
||||
|
||||
The `action` and `note` of `packet_log` for close, file and reopen.
|
||||
|
||||
| Write | action | note |
|
||||
|---|---|---|
|
||||
| set_vclosed, confirm | `Job closed in VISUAL` | the packet's work order bases, `L489052, L489053`, plus ` · VISUAL still shows open: L489052/1` when the feed shows open legs |
|
||||
| set_vclosed, taken back | `Moved back to ready to close` | |
|
||||
| file | `Filed in the data book` | the note given with the step, plus `Filed with work orders open in VISUAL: L489052/1, L489052/2` when confirmed past open work orders, joined with ` · ` |
|
||||
| file, the data book | `Data book filed` | `N pages` |
|
||||
| admin_reopen | `Reopened by IT` | the reason |
|
||||
| set_databook_at | `Data book date set` | |
|
||||
|
||||
The audit gains `print` (detail `{kind, what, pages, stamp_sig}`, plus `sheet` and `asset_id` when given) and `export` (detail `{kind, what, pages, stamp_sig}`, kind `databook`, `traveler`, `csv` or `itp`). FILE, ADMIN_REOPEN and SET_VCLOSED write their audit rows through the write pipeline as before.
|
||||
|
||||
## Worker writes (phase 3)
|
||||
|
||||
The worker has no session. It writes through the same policy as a person, as the system principal `Principal(user_id=None, system="visual-feed")`. Each write locks the packet row (`FOR UPDATE SKIP LOCKED`; a row locked by someone else is left for the next run), builds the packet state, runs `policy.check()`, makes its change, bumps `version`, and writes a `packet_log` row (by nobody, by name "VISUAL feed", role `system`), an audit row (`packet.<action>`, role `system`) and a change event. See [FEED.md](FEED.md) for when each write happens.
|
||||
|
||||
Reference in New Issue
Block a user