Phase 5: Supervisor and Stall screens, weld map, heat table, clock and CO verification
ci / frontend (push) Successful in 34s
ci / server (push) Successful in 7m15s
ci / image (push) Successful in 39s
ci / e2e (push) Successful in 2m4s

The shop at parity with the old build. Supervisor screen (department chips, CO headline, numbers, Change orders
on hold, station grid with clocks and CO tags, boards) and Stall screen (station picker, hero, My packets,
Submitted). Shop steps on the packet page: assign with station, welder picker and stamp; pull back; Start, Pause,
Resume; Submit with a PIN signature and the missing steps as links; review with Send to Q.C., Return to station
and Skip Q.C. route; a lead's read-only review. Weld map (pdf.js viewer with pan, pinch, zoom, full screen; marks
with leader lines; weld sheet; weld record with field CAS; carried marks). Heat map (heat table placed, dragged,
sized, removed; legacy amber tags; borrowed heat map read-only). Checklist tab with templates from settings.
VOID stamps on voided pages. Change orders: CO-n labels, supervisor verify with signature, approval copies the
heat table to the new sheet and the BOM merge keeps the shop's values. Server clock of record with VISUAL ticket
hours beside it. Presence per packet over SSE. Migration 0006 (bfx, bfy, carried_from, checklist_templates).
New endpoints /api/shop/board, /api/shop/stations, /api/shop/stall, /api/departments/{code}/people.

Tests: policy oracle for every shop action, role and status; clock arithmetic; concurrent inserts; CO apply;
frontend geometry, heat table, clock, checklist, weld sheet, screens; seven Playwright scenarios. Docs updated.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-10-16 22:46:56 -06:00
co-authored by Claude Fable 5.1
parent 39a4eb5213
commit 5a4174e0c2
81 changed files with 11532 additions and 352 deletions
+142 -2
View File
@@ -147,7 +147,7 @@ Each precondition is a named predicate over the packet state and the request inp
| short_open | at least one BOM line has an open short (the old whCanReceive) | No part is short on this packet. |
| welder_set (input) | the request gives station_id and welder_id | Pick a station and a welder. |
| checklist_complete | every key of the department checklist is done | Finish the checklist first. |
| welds_recorded | at least one weld, in a weld department | Record at least one weld first. |
| welds_recorded | at least one weld in the packet's current department, in a weld department | Record at least one weld first. |
| filler_set | filler metal entered, in a weld department | Enter the filler metal first. |
| 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). |
@@ -297,6 +297,142 @@ The `action` column of `packet_log` for each warehouse write. `{item}` is the BO
When a Pulled tick starts pulling by itself, there is no separate stage row in the log: the line's own row gets ` · started pulling` or ` · pulling resumed` on its note. Every write above also writes its audit row through the write pipeline, with `detail.item`, `wh_from` and `wh_to` as before, and `detail.qty` on short writes.
## Shop (phase 5)
### Who takes each step
| Action | Who | From | Notes |
|---|---|---|---|
| assign | Lead or supervisor of the packet's department (DEPT_LEAD) | ready | Station and welder checked at assign time (below) |
| unassign | DEPT_LEAD | assigned | Clears station and welder, folds the clock |
| pull_back | DEPT_LEAD | in_progress | Clears station and welder, folds the clock |
| start | Lead or supervisor of the department, or the assigned welder within their station limits (WORKS_ON) | assigned | Starts the clock |
| submit | WORKS_ON | assigned, in_progress | Stops the clock. Signature "Submit work to the supervisor" |
| return_to_station | Supervisor of the department (DEPT_SUP) | review | Reason |
| send_to_qc | DEPT_SUP | review | Signature "Send to Q.C.". Opens the inspection |
| route_from_review | DEPT_SUP | review | Signature "Route to another department". Closes the stage and opens one in the target department |
| send_back_to_warehouse | DEPT_LEAD | ready | Reason |
| return_to_clerical | DEPT_LEAD | ready, only when wh_skipped | Reason |
A worker who is not the assigned welder cannot start or submit. Neither can the assigned welder when their station limits leave out the packet's station. Every step sends `expect: {status, dept}`, so a stale screen gets 409 `stale_step` "Someone else already took this step."
### Assign checks
ASSIGN checks the station and the welder on the server, at assign time:
| Check | Refusal |
|---|---|
| The station is an active station of the packet's department | 422 `invalid_station` |
| The welder is an active user with a shop role (worker, lead or supervisor) in the packet's department | 422 `invalid_welder` "Pick a welder in this department." |
| A worker with station limits (no lead or supervisor role in the department) is limited to the chosen station | 422 `station_limit` "{name} is limited to {station names}." |
| A `stamp` in the body matches the stamp pattern | 422 `invalid_stamp` |
The stamp in the body overrides the user's `welder_stamp` for this assignment. Without it, the user's stamp is used. The packet and the stage row both get the station, the welder and the stamp. `GET /api/departments/{code}/people` gives the assign sheet its welder list, with each person's level and station limits.
### The lead's review
A lead holds a packet in review (see "Custody"), but return_to_station, send_to_qc and route_from_review are the supervisor's. The server refuses them to a lead with 403 `forbidden`. The screen shows the lead the review read-only: "A supervisor for 03 Weld Parts reviews it and sends it on."
### Preconditions on the shop steps
| 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 | 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. |
| return_to_station, send_back_to_warehouse, return_to_clerical | reason_given (input) | a reason of at least 8 characters after cleaning | Give a reason of at least 8 characters. |
| route_from_review | target_dept_given (input) | a shop department different from the packet's | Choose a department. / Choose a different department. |
welds_recorded counts only the current department's welds. After a route, the new department needs a weld of its own before it can submit. The welds from earlier departments are carried marks (see [SCHEMA.md](SCHEMA.md), "Carried marks").
### Change-order hold
An open change order holds these steps: WH_RELEASE, SUBMIT, SEND_TO_QC, ROUTE_FROM_REVIEW, QC_ACCEPT, QC_ACCEPT_AND_ROUTE and FILE. It does not hold ASSIGN, START, the clock or the edits, so the shop keeps working while the departments verify. A change order is open when it is approved with departments still to verify, or pending with hold on (`v_open_cos`).
The hold text (the `co_hold` refusal and the `blocked` reason):
| Change order | Text |
|---|---|
| Pending, with hold | `CO-2: Clerical is reviewing a change from VISUAL. Held until Clerical approves or rejects it.` |
| Approved, departments pending | `Change order CO-2 must be verified first by: 03 Weld Parts, 09 Hydro.` |
With several open change orders, the texts are joined by a space, one sentence each. The id is `CO-<co_no>` everywhere.
### Change-order verification
CO_VERIFY is the supervisor of the department named in the request: a shop Supervisor for a shop code, the WH or Q.C. Supervisor for WH or QC. A lead cannot verify (403 `forbidden`). Clerical never verifies. Each verification needs a signature ("Verify a change order for the department") and is an insert into `co_verifications`. A second one for the same department is 409 `already_verified`. A department the change order does not need is 422 `invalid_dept`. A verification cannot be withdrawn.
The required departments on approval are the departments the change touches, without Clerical and PM. A minor change order needs none.
### The clock
CLOCK is the holder's in the shop statuses. The ops are `start`, `pause` and `resume`. An op in the wrong state is 409 `clock_state`. The server keeps the time with the database clock. No request sets the elapsed time. START starts the clock when it is not running. SUBMIT stops it. UNASSIGN and PULL_BACK fold the running time in, so the clock freezes. See [SCHEMA.md](SCHEMA.md), "The clock".
### Marks on the drawing
Welds (EDIT_WELDS), heat tags (EDIT_HEATS) and the heat table (EDIT_HEAT_TABLE) follow the rule table: the holder in the shop statuses, and for heat tags and the heat table also the warehouse while it holds the packet. The server hands out `W<n>` and `H<n>` from the packet counters under the packet row lock.
A VOID sheet refuses marks. A new weld or heat tag on a voided page, or a move of one already there, is 422 `void_page` "This sheet is VOID. Mark the current sheet." The marks on a void page stay and are shown, but cannot be edited.
### Heat map borrowing
A packet borrows a sibling's heat map when all of these hold (the old heatSrc):
- It has no heat map of its own: no heat table placement and no heat tag.
- It has no raw material of its own: every BOM line is N/A or a sub-assembly, or the BOM is empty.
- It has a drawing.
The sibling is a live packet in the same unit, with a different id and the same drawing (compared upper-case with white space removed), that has a heat map. The same rev is preferred, then the oldest. The read model gives it as `heat_src`. It is read-only: nothing on it can be written through the borrowing packet. The screen says "Heat map from <op> on this unit (same drawing). Record heats there."
### 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.
### Presence
The packet page shows who else has the packet open. Presence comes over SSE and carries the user id, display name, screen and packet id only.
### History texts
The `action` and `note` columns of `packet_log` for each shop write. A department is shown as code and name (`03 Weld Parts`), except in the verify text, which uses the name only. The station name is as stored (`Stall 3`).
| Write | action | note |
|---|---|---|
| assign | `Sent to Stall 3` | `Andy Welder [75]` (the stamp in brackets when set) |
| unassign | `Taken off Stall 3` | |
| pull_back | `Pulled back from Stall 3` | |
| start | `Work started` | `Stall 3` |
| submit | `Submitted to supervisor` | `4 welds · 1.5 h` |
| return_to_station | `Returned to Stall 3` | the reason |
| send_to_qc | `Sent to Q.C.` | `Spool at Bay 2 · note` |
| route_from_review | `Routed to 09 Hydro` | `Supervisor, Q.C. skipped · from 03 Weld Parts` |
| send_back_to_warehouse | `Sent back to the warehouse` | the reason |
| return_to_clerical | `Sent back to Clerical` | the reason |
| Weld added | `Weld W4 marked` | `Fillet 5/16" · items 1 + 2 · L662001 #1` |
| Weld edited | `Weld W4 changed` | the changed fields, `type Groove · items 1 + 3` |
| Weld moved (position only) | `Weld W4 moved` | |
| Weld removed | `Weld W4 removed` | the reason |
| Heat tag | `Heat tag H2 placed`, `Heat tag H2 changed`, `Heat tag H2 moved`, `Heat tag H2 removed` | `item 3` |
| Heat table placed | `Heat table placed on drawing` | the page's dwg, else `sheet 2` |
| Heat table moved | `Heat table moved` | the same |
| Heat table resized | `Heat table resized` | `A+` or `A-` and the new size, `A+ 1.15` |
| Heat table removed | `Heat table removed from drawing` | the same |
| Heat table copied on a change order | `Heat table copied to new sheet` | `from the VOID copy, check its position on the new revision` |
| Checklist | `Checked: <item text>`, `Unchecked: <item text>` | |
| Stage fields | `Weld record changed` | the changed fields, `filler ER70S-6 · filler lot 1234` |
| Clock | `Clock started`, `Paused`, `Resumed` | |
| Change order issued | `Change order CO-2 issued` | |
| Change order approved | `Change order CO-2 approved` | |
| Change order rejected | `Change order CO-2 rejected` | the reason |
| Change order hold | `Change order CO-2 holds the packet`, `Change order CO-2 no longer holds the packet` | |
| Change order verified | `Change order CO-2 verified by Weld Parts` | `note · still waiting on Hydro`, or `note · change order fully verified` |
The submit hours are the stage's `clock_elapsed_ms` after the stop, with one decimal.
## 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.
@@ -352,7 +488,7 @@ The old policy was looser than the old screens in places. The rebuild follows th
| Clerical "lead" actions (approve an M-Files rev mismatch, move back from VISUAL review) were open to any Clerical member with a lead flag. | REV_APPROVE and moving back from VISUAL review need Clerical Supervisor (PS-Clerical-Supervisor). | There is no Clerical lead AD group. | Yes |
| A packet routed to Shipping had no path to filed. | Complete packets routed to Shipping get the same Clerical close and file path. Only the Data Book route needs the VISUAL close before filing. | Shipping packets were stuck at complete. | Yes |
| Anyone could set the data book date, even on a filed packet. | SET_DATABOOK_AT is Clerical or Q.C. only (open question 17). It stays allowed on a filed packet. | The data book date is a Clerical and Q.C. record. | Yes |
| Change-order verifications were stored in the packet document, with no rule against changing them later. | Verifications are insert-only and cannot be withdrawn (open question 18). | A signed verification is a record. | Yes |
| Change-order verifications were stored in the packet document, with no rule against changing them later. | Verifications need a signature, are insert-only and cannot be withdrawn (open question 18). | A signed verification is a record. | Yes |
| The old policy did not look at the BOM when the warehouse released a packet. | WH_RELEASE needs every BOM line not marked n/a to be pulled or to have an open short. | A packet should reach the shop with its material, or with the shorts on record. | Yes |
| The old screen asked for a heat number on every pulled line before release, but the policy did not check it. | WH_RELEASE also needs a heat on every pulled line that issued pieces, unless the line is N/A or a sub-assembly (`heats_recorded`), checked on the server. | Traceability is the point of the pull. The screen alone could be skipped. | Yes |
| Hold reasons and short reasons were any text the screen sent. | Both are closed sets checked on the server. "Other" on a hold needs a note. | The board and the shorts table group and report on the reason, so it must be one of the list. | Yes |
@@ -364,3 +500,7 @@ The old policy was looser than the old screens in places. The rebuild follows th
| Approving a different M-Files rev was open to a Clerical lead. | rev_approve needs Clerical Supervisor, with a reason and a signature. | There is no Clerical lead AD group, and the approval overrides VISUAL. | Yes |
| The Sync on a Clerical PC skipped removed packets ("removed here, not re-made"), but only as the browser saw them. | A removed packet's key is never recreated, whatever VISUAL says. The match runs on the server across live and removed rows, in the feed's transaction. | A removal is a decision. The feed does not undo it. | No |
| The Sync saved through the same packet document that held the shop's work, so nothing but the code kept it off the shop's fields. | The feed never writes shop-side tables. Packets past draft only get a change order, observed VISUAL state and drawing flags. | The shop's record is the shop's. | No |
| The assign sheet only offered welders of the department and filtered the stations by the welder's limits. The policy checked neither. | ASSIGN checks on the server that the welder has a shop role in the packet's department, and that a limited worker's station is one of their stations. | The picker alone could be skipped. | Yes |
| The old screen asked for a weld of the current department before submit, but the policy did not check welds. | welds_recorded counts the welds of the packet's current department, on the server. After a route the new department marks its own. | Each department records its own welds. | Yes |
| A mark could be placed on a VOID sheet. | A VOID sheet refuses new marks and moves (422 `void_page`). The marks already there stay. | Marks belong on the current revision. | Yes |
| The station clock ran in the browser, and the elapsed time was saved from the browser. | The server keeps the clock with the database time. No request sets the elapsed time. | The clock is a record. A browser clock can be wrong or changed. | No |