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

- C1 (issue 1): over target follows VISUAL ticket hours, not the timer.
- C2 (issue 24): a Q.C. reject always needs a reason; accept needs an NCR number after a reject without one.
- C3 (issue 25): an incomplete unit ITP blocks filing.
- C4 (issue 27): prints open the print dialog on the page, no new tab.
- C5 (issue 31): filing is blocked while VISUAL shows a work order open; the feed sets vclosed on VISUAL packets.
- C6 (issue 8): the data book leaves out the history pages.
- C7 (issue 18): the supervisor approves each received short; accept and filing wait for it.
- C8 (issue 23): per-department checklists, edited by the department supervisor or Admin, frozen at START.
- C9 (issue 32): Q.C. and Clerical may run the stamp check.
- C10 (issue 33): the PM screen and reports are for PS-ProjectMgmt and Admin only.
- C11 (issue 36): the Stall screen lists the packets assigned to the welder, at the supervisor's stall.

Migration 0011. Docs: PLAN (also issue 9 confirmed, issue 4 asked), POLICY, SECURITY, SCHEMA, FEED, README, CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-30 13:33:51 -06:00
co-authored by Claude Opus 5.5
parent 1ae6819289
commit 1cd6688247
101 changed files with 3871 additions and 1098 deletions
+18 -7
View File
@@ -1,6 +1,6 @@
# Packet Station schema
Status: phases 1 to 4 built (identity, organisation, projects, packets, change orders, documents, jobs, VISUAL and drawing caches, the warehouse columns in use, the `notification_events` placeholder). Phase 5 adds the shop columns, the checklist templates and the clock rules (migration 0006). Phase 6 adds the Q.C. columns, the Q.C. check items, the Q.C. folder metadata, the ITP header, line and block details and the ITP log (migration 0007). Phase 7 adds the print log and the data book on the seal (migration 0008). Phase 8 adds no table and no migration: the PM report exports use new `print_log` kinds and presence uses two new session events. Phase 9 adds the notification rules and delivery, the integrity findings and the Admin profile switch (migration 0010). The DDL is in `server/packetstation/migrations/`; this file explains it. See [PLAN.md](PLAN.md), "Schema".
Status: phases 1 to 4 built (identity, organisation, projects, packets, change orders, documents, jobs, VISUAL and drawing caches, the warehouse columns in use, the `notification_events` placeholder). Phase 5 adds the shop columns, the checklist templates and the clock rules (migration 0006). Phase 6 adds the Q.C. columns, the Q.C. check items, the Q.C. folder metadata, the ITP header, line and block details and the ITP log (migration 0007). Phase 7 adds the print log and the data book on the seal (migration 0008). Phase 8 adds no table and no migration: the PM report exports use new `print_log` kinds and presence uses two new session events. Phase 9 adds the notification rules and delivery, the integrity findings and the Admin profile switch (migration 0010). SethD's changes (built 2026-10-30) add the short approvals and the checklist frozen on each stage (migration 0011), and the per-department checklist settings rows. The DDL is in `server/packetstation/migrations/`; this file explains it. See [PLAN.md](PLAN.md), "Schema".
## Conventions
@@ -65,7 +65,7 @@ Smaller closed sets are text with a CHECK: `projects.source` and every `*_src` (
`packets` is the header. The columns that matter:
- Placement: `unit_id`, `dept_code`, `op`, `title`, `customer`, `drawing`, `rev`, `wps`.
- Workflow: `status` (packet_status), `status_at`, `station_id`, `welder_id` and `welder_stamp` (a snapshot taken at assign), `for_sup_id`, `wh_skipped` (true when Clerical sent the draft straight to a department or to Q.C.), `wh_sent_at`, `routed_to` (route_kind, set at Q.C. accept), `return_note` (the reason shown after a send-back), `vclosed_at` and `vclosed_by`, `databook_at`.
- Workflow: `status` (packet_status), `status_at`, `station_id`, `welder_id` and `welder_stamp` (a snapshot taken at assign), `for_sup_id`, `wh_skipped` (true when Clerical sent the draft straight to a department or to Q.C.), `wh_sent_at`, `routed_to` (route_kind, set at Q.C. accept), `return_note` (the reason shown after a send-back), `vclosed_at` and `vclosed_by` (closed in VISUAL: on a VISUAL packet the feed writes it with `vclosed_by` null, on a hand packet Clerical; see "Seth's changes"), `databook_at`.
- Preconditions: `heat_map` is nullable on purpose. Null means not answered yet, and the warehouse step needs an answer.
- Source: `source_kind` (visual or hand), `source` jsonb, `visual_key` (the feed's match key), `visual_rel`, `visual_want`, `drawing_state`, `flags` text[], `visual_fp_applied` (the fingerprint the packet matches), `visual_closed_at`, `visual` jsonb (system-written sub-keys).
- Counters: `next_weld_no`, `next_heat_no`, `next_page_no` and `next_co_no`, all starting at 1. See "Versioning".
@@ -82,6 +82,7 @@ The trigger `ps_packet_touch` sets `updated_at` on every UPDATE. It does not tou
- `bom_lines`: `item`, `qty`, `part_id` (text), `description`, `dwg_ref`, `cl_item`, `length`, `material`, `heat`, `na`, `issued`, `loc`, `pulled`, `pulled_at`, `pulled_by`, `mtr_asset_id`, `src`, `vkey`, `whs`, `comm`, `cons`, `sub`, `grp`, `sort`, `row_version`. Unique on (`packet_id`, `item`).
- `bom_shorts`: `bom_line_id` (cascades with the line), `qty`, `qty_open`, `reason`, `ref`, `eta`, `note`, `opened_at`, `opened_by`, `closed_at`, `row_version`. At most one open short per line (unique on `bom_line_id` where `closed_at` is null).
- `short_receipts`: append-only. `short_id`, `packet_id`, `qty`, `heat`, `note`, `at`, `by`.
- `short_approvals` (migration 0011): append-only. The supervisor's approval of one receipt. See "Seth's changes".
What the warehouse columns mean in practice (phase 4):
@@ -95,7 +96,7 @@ What the warehouse columns mean in practice (phase 4):
- `bom_shorts`: one open short per line at most. Opening a short sets the line's `issued` to `qty` minus the short quantity and ticks it pulled. `qty` is what was short in total, `qty_open` what is still missing. `reason` is one of the closed short reasons, `ref` the PO or requisition, `eta` the expected date. Editing an open short keeps what was already received in `qty`. "Not short after all" closes it (`closed_at`) and sets `issued` to the line's `qty`. While a short is open, the line's heat, issued and location change only through the short.
- `short_receipts`: one row per delivery, never changed. Each receipt lowers `qty_open`, adds its pieces to the line's `issued` and its heat to the line's `heat`. The receipt that brings `qty_open` to 0 closes the short.
- `packet_wh`: `reason` is one of the closed hold reasons when the stage is `hold`, and `note` is up to 200 characters.
- `packet_stages`: one row per visit to a shop department. `dept_code`, `station_id`, `welder_id`, `welder_stamp`, `checklist` jsonb (key to `{done, at, by, stamp}`, written per key with `jsonb_set`), `filler`, `filler_lot`, `preheat`, `notes`, the clock (`clock_elapsed_ms`, `clock_running_since`, `clock_started_at`; only the server writes them, see "The clock"), `qc_snapshot`, `opened_at`, `closed_at`, `row_version`. At most one open stage per packet.
- `packet_stages`: one row per visit to a shop department. `dept_code`, `station_id`, `welder_id`, `welder_stamp`, `checklist` jsonb (key to `{done, at, by, stamp}`, written per key with `jsonb_set`), `filler`, `filler_lot`, `preheat`, `notes`, the clock (`clock_elapsed_ms`, `clock_running_since`, `clock_started_at`; only the server writes them, see "The clock"), `qc_snapshot`, `opened_at`, `closed_at`, `row_version`. At most one open stage per packet. Migration 0011 adds `checklist_template`, the list frozen at START (see "Seth's changes").
- `welds`: `weld_no` (from `next_weld_no`), `page`, `fx` and `fy` (fractions of the page), `bfx` and `bfy` (0006), `wo_base`, `piece`, `items` int[], `type`, `size`, `process`, `stamp`, `welder_id`, `dept_code`, `stage_id`, `carried_from`, `at`, `by`, `row_version`, soft delete. Unique on (`packet_id`, `weld_no`) among live rows.
- `heat_tags`: `tag_no` (from `next_heat_no`), `page`, `item`, `fx`, `fy`, `bfx` and `bfy` (0006), `carried_from`, `at`, `by`, `row_version`, soft delete. Unique on (`packet_id`, `tag_no`) among live rows.
- `heat_table_placements`: `page`, `x`, `y`, `s`, `carried_from` (0006), `by`, `at`. Unique on (`packet_id`, `page`), written as an upsert.
@@ -154,9 +155,9 @@ The `settings` row `checklist_templates` holds the shop checklists. Migration 00
| plain | Before starting | `dwg`, `prev`, `mat` |
| plain | When done | `work`, `insp`, `qty` |
When the row is missing or malformed, the defaults in `domain/checklists.py` apply and a warning is logged. The read model and the checklist write both use the loaded templates. No screen edits the row in phase 5. Phase 9 (Admin) adds that.
When the row is missing or malformed, the defaults in `domain/checklists.py` apply and a warning is logged. The read model and the checklist write both use the loaded templates. No screen edits the row in phase 5. Phase 9 (Admin) adds that. Since change C8 (built 2026-10-30) this row holds the default lists by kind, and each department may have its own list (see "Seth's changes").
`packet_stages.checklist` maps each key to `{done, at, by, stamp}`. `by` is the user id. `stamp` is the user's `welder_stamp`, else the packet's. A key not in the department's list is 422.
`packet_stages.checklist` maps each key to `{done, at, by, stamp}`. `by` is the user id. `stamp` is the user's `welder_stamp`, else the packet's. A key not in the stage's list (the frozen `checklist_template`, else the department's live list) is 422.
### Carried marks
@@ -238,7 +239,7 @@ The texts may hold `{wps}` (else `the WPS`), `{stamp}` (`[75] ` with a trailing
### Column additions
- `packet_seals`: `databook_asset_id` (uuid, references `assets`), the data book PDF built and stored when the packet was filed. `databook_pages` (int), its page count. `open_wos` (text[]), the work orders the feed showed open in VISUAL when Clerical filed anyway, as `wo_base/lot` text (`L489052/1`), or null.
- `packet_seals`: `databook_asset_id` (uuid, references `assets`), the data book PDF built and stored when the packet was filed. `databook_pages` (int), its page count. `open_wos` (text[]), the work orders the feed showed open in VISUAL when Clerical filed anyway, as `wo_base/lot` text (`L489052/1`), or null. Since change C5 (built 2026-10-30) filing is blocked while VISUAL shows a work order open, so new seals leave it null; older seals keep theirs.
### The print log
@@ -339,7 +340,7 @@ A unit folder shows its own current rows and inherits the project's current rows
These tables have the `ps_append_only` row and TRUNCATE triggers, and `ps_app` holds only SELECT and INSERT on them:
`audit_events`, `signin_attempts`, `signatures`, `project_renumbers`, `short_receipts`, `packet_transitions`, `qc_history`, `packet_log`, `itp_rejects`, `itp_log` (0007), `print_log` (0008), `notifications_sent` and `integrity_findings` (0010), `co_verifications`.
`audit_events`, `signin_attempts`, `signatures`, `project_renumbers`, `short_receipts`, `packet_transitions`, `qc_history`, `packet_log`, `itp_rejects`, `itp_log` (0007), `print_log` (0008), `notifications_sent` and `integrity_findings` (0010), `short_approvals` (0011), `co_verifications`.
The app role is refused by privilege first. The migrate role owns the tables, so the trigger refuses it. The views `v_units` and `v_open_cos` are SELECT only for `ps_app`.
@@ -378,6 +379,16 @@ Phase 9, migration 0010, builds the delivery on this table:
- `integrity_findings`: append-only. One row per thing the integrity job found wrong: `run_id` (no foreign key, since housekeeping prunes `job_runs`), `at`, `kind` (seal, asset_hash, asset_missing, blob_orphan, audit_chain), `subject` (the packet, asset, file path or audit id), `detail` jsonb.
- Settings rows used by phase 9: `security` (the Admin's overrides of the session, lockout and rate-limit settings; missing keys fall back to the environment), `checklist_templates` (now edited on the Admin Checklists tab), `notify.last_build` (the build the worker last announced).
## Seth's changes (built 2026-10-30, migration 0011)
Migration 0011 serves changes C7 and C8 in [PLAN.md](PLAN.md). The others need no schema.
- `short_approvals` (new, append-only; Gitea issue 18, change C7): the department supervisor's approval of one short receipt. `receipt_id` (unique, references `short_receipts`), `short_id` (references `bom_shorts`), `packet_id`, `dept_code` (the department the packet was in at the approval), `note`, `signature_id`, `at`, `by`. Indexed on `packet_id`. A second approval of a receipt breaks the unique key; the API answers 409 `already_approved`. The packet read and the board cards count `shorts_unapproved`: open shorts plus receipts with no row here.
- `packet_stages.checklist_template` (jsonb, new; Gitea issue 23, change C8): the checklist the stage froze at START, as a list of `{group, key, text}` with the placeholders not filled. NULL means the stage has not started and reads its department's live list. START sets it once, only when NULL. Every reader uses it when set. 0011 backfills it on the open stage of every packet in progress, in review or at Q.C. with the default list for the stage's kind from the `checklist_templates` row, so a department edit does not reach work already started. When that row is missing or its list is not an array, the stage stays NULL and reads the department's live list until it closes.
- Settings rows `checklist:<dept code>` (no migration; for example `checklist:03`): one department's own checklist, `{"items": [{group, key, text}, ...]}`. `{"items": null}` sets the department back to the default for its kind and keeps the row's `version` for compare and set. A row that does not parse is logged and the default applies. A department with no row uses the default for its kind: the `weld` or `plain` list of `checklist_templates`, else the code's defaults. The department's Supervisor or an Admin saves it through `PUT /api/checklists/{dept}` (see [POLICY.md](POLICY.md), "Checklists").
- `packets.vclosed_at` and `vclosed_by` (no migration; Gitea issue 31, change C5): on a VISUAL packet the feed is now the one writer. It sets `vclosed_at` (with `vclosed_by` null) when every leg is closed or cancelled and clears both when a leg shows open again. On a hand packet Clerical still sets them. See [FEED.md](FEED.md), "The apply".
- `qc_inspections.ncr` (no migration; Gitea issue 24, change C2) is now also written by the accept that carries the NCR number of an earlier reject.
## VISUAL and drawing caches (phase 3)
Migration 0004. Every ID part is text (`wo_base`, `lot`, `split`, `sub`, part IDs, order IDs), with `''` in place of null in a key. `seq` and `piece` are int. Closed sets are text with a CHECK. The worker fills these tables. Screens read them, never VISUAL. [FEED.md](FEED.md) says how each is written.