Phase 4: Warehouse screen, pull table, shorts, MTRs and the upload pipeline
The Warehouse home with the numbers, the Open shorts table, the find bar and the five-column task board (waiting, being pulled, on hold, pulled, released in the last 3 days) with buttons, hold reasons and drag. Every role sees the warehouse stage on a packet as the WH pill and the line under it. The pull table on the Materials tab: Pulled with auto-start, issued, location, heat with Enter to the next line, shorts with receipts, N/A, MTR per line with a viewer. Send back to Clerical with a reason. Release to supervisor stays blocked until every line is pulled or short and every pulled line has a heat. Server: the heats_recorded precondition on WH_RELEASE, receive by custody, server-validated hold and short reasons, the old packet history texts, GET /api/warehouse/board, cards with the warehouse stage and pull counts. Uploads typed by content per kind (drawing, mtr, document, bom with XLSX and CSV), limits per kind, optional ClamAV behind the clamav compose profile, a storage quota alert on the new notification_events table (migration 0005) shown on the Admin Storage tab. Tests: every warehouse action as every role, the release gate, the short-receive exception, the heat pattern, upload sniffing and limits, the ClamAV path with a fake scanner, the quota alert; frontend board and pull table tests; Playwright scenarios for the board and the pull flow. Verified on docker-1 in the CI stack: 3557 server tests, 106 frontend tests, 17 Playwright scenarios. Docs updated; D5 closed. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
+31
-3
@@ -1,6 +1,6 @@
|
||||
# Packet Station schema
|
||||
|
||||
Status: phases 1 to 3 built (identity, organisation, projects, packets, change orders, documents, jobs, VISUAL and drawing caches). Phase 9 adds the notification tables. 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 9 adds the notification rules and delivery. The DDL is in `server/packetstation/migrations/`; this file explains it. See [PLAN.md](PLAN.md), "Schema".
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -82,6 +82,19 @@ 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`.
|
||||
|
||||
What the warehouse columns mean in practice (phase 4):
|
||||
|
||||
- `bom_lines.pulled`: the line is off the shelf. The first tick sets `pulled_at` and `pulled_by`, and sets `issued` to `qty` when it was empty. Unticking clears all three of `pulled`, `pulled_at` and `pulled_by`. The first tick while the warehouse stage is incoming or on hold also starts pulling (a `packet_wh` row with `auto` true).
|
||||
- `bom_lines.issued`: how many pieces went out. Less than `qty` means the rest should be on a short.
|
||||
- `bom_lines.loc`: where the parts were taken from, as the warehouse types it.
|
||||
- `bom_lines.heat`: the heat number of the pieces issued, checked against the heat pattern. A receipt adds its heat to the line, so a line with parts from two heats reads `H1 / H2` (heats joined with " / ", no repeats).
|
||||
- `bom_lines.na`: the part has no traceability. An N/A line needs no heat, takes no heat, and does not count toward the release gate.
|
||||
- `bom_lines.mtr_asset_id`: the mill test report for the line, an `assets` row that must be a PDF, PNG or JPEG. Null clears it.
|
||||
- A line "needs a heat" when it is not N/A, not a sub-assembly (`sub` is `Y`), is pulled, has `issued` above 0 and has no heat. The release gate counts these lines (see [POLICY.md](POLICY.md)).
|
||||
- `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`), `qc_snapshot`, `opened_at`, `closed_at`, `row_version`. At most one open stage per packet.
|
||||
- `welds`: `weld_no` (from `next_weld_no`), `page`, `fx` and `fy` (fractions of the page), `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`, `carried_from`, `at`, `by`, `row_version`, soft delete. Unique on (`packet_id`, `tag_no`) among live rows.
|
||||
@@ -107,6 +120,17 @@ Every who column references `users (id)`. Every `signature_id` references `signa
|
||||
## Documents (phases 2, 4 and 6)
|
||||
|
||||
- `assets`: `id` uuid, `sha256` (indexed, for dedupe), `size`, `mime`, `original_name`, `storage_key` (the path under ASSETS_DIR, unique), `source` (`upload` by default), `source_ref` jsonb, `pages`, `uploaded_by`, `uploaded_at`, `packet_id` (the packet it was uploaded for, without a foreign key; `packet_pages` and `packet_docs` are the real links).
|
||||
|
||||
Asset kinds (phase 4). An upload names its kind in the `purpose` field, and `source_ref` keeps `{"purpose": kind}`. No purpose means `document`. The type is read from the content, never from the name or the browser.
|
||||
|
||||
| Kind | Types | Limit (setting) |
|
||||
|---|---|---|
|
||||
| drawing | PDF | 25 MB (`upload_max_bytes`) |
|
||||
| mtr | PDF, PNG, JPEG | 25 MB (`upload_max_bytes`) |
|
||||
| document | PDF, PNG, JPEG | 25 MB (`upload_max_bytes`) |
|
||||
| bom | XLSX, CSV | 5 MB (`upload_max_bytes_bom`) |
|
||||
|
||||
`mime` is one of `application/pdf`, `image/png`, `image/jpeg`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` and `text/csv`. An XLSX is a zip that holds `[Content_Types].xml` and `xl/workbook.xml`; a plain zip is refused. A CSV is UTF-8 text with no NUL and no control character other than tab, CR and LF. An upload the virus scanner refuses is never stored, so it has no row.
|
||||
- `packet_docs`: `asset_id`, `kind`, `title`, `added_at`, `added_by`, soft delete.
|
||||
- `qc_docs`: `project_id`, `unit_id`, `type`, `rev`, `asset_id`, `auto`, `superseded_by` (the newer row), `added_at`, `added_by`.
|
||||
- `itps`: one per unit (`unit_id` unique). `template` jsonb, frozen when the ITP is made. `status` (itp_status), `signoff_*` (with the signature and the signed PDF asset), `reopen_*`, `version`.
|
||||
@@ -134,9 +158,13 @@ Phase 3, migration 0004:
|
||||
- `job_schedule`: one row per job (`job` PK). `next_run_at`, `interval_seconds`, `paused`, `updated_at`. Written by the worker after each run for the Admin tab. Seeded for `visual-feed`, `drawing-lookup` and `housekeeping`.
|
||||
- `worker_heartbeat`: a single row (`id` = 1 by CHECK). `at`, `build`, `host`, `pid`, `running` text[] (the jobs running now). Upserted every 30 seconds.
|
||||
|
||||
## Notifications (phase 9)
|
||||
## Notifications (phases 4 and 9)
|
||||
|
||||
Rules, recipients, exclusions, outbox, sent log, state.
|
||||
Phase 4, migration 0005:
|
||||
|
||||
- `notification_events`: one row per alert. `id` bigserial, `kind` (text, for example `assets_quota`), `severity` (`info`, `warning` or `critical` by CHECK, default `warning`), `subject` (what the alert is about, for example `assets`), `detail` jsonb (for the quota alert, the volume usage: `total`, `used`, `free`, `pct`, `warn`), `created_at`, `resolved_at` (set when the condition clears), `acknowledged_at` and `acknowledged_by` (an Admin saw it). The unique index `notification_events_open` on (`kind`, `coalesce(subject, '')`) where `resolved_at` is null keeps one open alert per kind and subject, so a check that runs again does not stack rows. The table is not append-only: the app sets `resolved_at` and the acknowledgement. 0005 also grants `ps_app` USAGE and SELECT on every sequence in the schema.
|
||||
|
||||
Phase 9 builds the delivery on this table: rules, recipients, exclusions, outbox, sent log, state.
|
||||
|
||||
## VISUAL and drawing caches (phase 3)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user