Three-part design (12a/12b/12c) for adding a flat drag-drop recipe editor alongside the existing tree editor, with a reusable step library, Steelhead-style Move Parts/Rack/Stop-Timer dialogs, and recipe-order + chronological CoC PDF reports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
54 KiB
Steelhead "Move Parts" Screen Inventory — Simple Recipe Editor
Working notes captured during brainstorming. Each screenshot the user
provides is logged here so we can fold the field requirements into the
final design. Do NOT lose these notes — they drive the data shape on
fp.step.template.transition.input and the eventual transition dialog
on the tablet.
Common dialog header (all screens)
- Title bar: "Move Parts"
- Cancel + red MOVE (n) button at footer (n = part count being moved)
Screenshot 1 — node→node move (no station)
| Field | Type | Example | Notes |
|---|---|---|---|
| Part Count | Integer (with stepper) | 1 | "Available: 1" hint shown beneath the input |
| Part Number | Read-only link | TEST225451 | resolves back to part record |
| From Node | Read-only link | Contract Review | current node operator is leaving |
| Transfer Type | Selection | Step | (other values likely: Step / Hold / Scrap / Return — TBD as more screens come in) |
| To Node | Read-only link | Ready for Incoming Inspection | destination node |
| To Location | Selection + camera icon | Global | location picker; camera icon = take photo evidence inline |
| Number of Customer WOs | Char | (blank) | optional; only present on this screen |
| Billed Labor | section with timer icon + "Reset All Edits" button | — | per-operator timer breakdown follows |
| Per-operator labor row | composite | Kris Pathinather, Timer Duration: 56s (100.0% billed), WO #4521 PN: TEST225451 Qty: 1, hrs/min/sec edit fields | reconcilable timer vs. billed split; the small icon top-right of the row appears to be "edit notes" |
Screenshot 2 — node→node move with station picker (between two real shop steps)
| Field | Type | Example | Notes |
|---|---|---|---|
| Part Count | Integer (stepper) | 49 | Available: 49 |
| Part Number | Read-only link | TEST225451 | |
| From Node | Read-only link | Ready for Incoming Inspection | |
| Transfer Type | Selection | Step | |
| To Node | Read-only link | Incoming Inspection | |
| To Station | Selection | Incoming Inspection | NEW field on this variant — appears when destination node has multiple stations to choose from. Defaults to a sensible value but is editable. |
| To Location | Selection + camera | Global | |
| Billed Labor | section | — | (not expanded on this screen — implies optional / collapsed by default when timer hasn't accrued) |
Screenshot 3 — second node→node move with From Station
| Field | Type | Example | Notes |
|---|---|---|---|
| Part Count | Integer (stepper) | 49 | Available: 49 |
| Part Number | Read-only link | TEST225451 | |
| From Node | Read-only link | Incoming Inspection | |
| From Station | Read-only link | Incoming Inspection | NEW — appears when the source node had a station selected previously |
| Transfer Type | Selection | Step | |
| To Node | Read-only link | Adhesion Test Coupon | |
| To Location | Selection + camera | Global | |
| Billed Labor | section with "Reset All Edits" button | — | |
| Per-operator labor row | composite | Kris Pathinather, Timer Duration: 4s (100.0% billed), WO #4521 PN: TEST225451 Qty: 49, hrs/min/sec edit fields | same shape as screenshot 1 |
Patterns emerging across screenshots so far
-
Variable field set per transition. Some moves show "Number of Customer WOs", some don't. Some show "To Station" / "From Station", some don't. The recipe author needs to be able to declare which fields appear on which transitions — that's what
fp.step.template.transition.inputis for. -
Read-only context fields are always present (Part Count, Part Number, From Node, To Node, From/To Station when applicable, To Location, Transfer Type). These are system-derived — the recipe author doesn't author them, they come from the runtime context. Our model only needs to capture the author-defined prompts (extra compliance fields).
-
Camera icon next to To Location = inline photo capture, attached to the transition log. Implies the runtime needs a
photoinput type that can either upload a file or trigger device camera (mobile / tablet). -
Billed Labor is a separate concern — it's a labor reconciliation widget, not a recipe-defined input. Operator can edit hrs/min/sec to reconcile timer vs. actual time billed. Per-operator row with Timer Duration + "100.0% billed" indicator. "Reset All Edits" button reverts all manual reconciliations to timer values. This is its own sub-system; goes outside
fp.step.template(it's a runtime feature, not authored on a recipe). -
Transfer Type values seen so far:
Step. More variants expected (Hold, Scrap, Return, Rework). Each variant likely has its own required field subset.
Screenshot 4 — Adhesion Test Coupon → Ready for racking (no station, no labor block)
| Field | Type | Example | Notes |
|---|---|---|---|
| Part Count | Integer (stepper) | 49 | Available: 49 |
| Part Number | Read-only link | TEST225451 | |
| From Node | Read-only link | Adhesion Test Coupon | |
| Transfer Type | Selection | Step | |
| To Node | Read-only link | Ready for racking | |
| To Location | Selection + camera | Global | |
| Billed Labor | section icon only | — | not expanded — implies no timer accrued on this leg |
Pattern: short transitions between QA-style intermediate nodes don't expand the labor reconciliation panel. Labor block expands only when a non-zero timer has accrued OR when an operator has a row to reconcile.
Screenshot 5 — Ready for racking → Racking (no station, no labor)
| Field | Type | Example | Notes |
|---|---|---|---|
| Part Count | Integer (stepper) | 49 | Available: 49 |
| Part Number | Read-only link | TEST225451 | |
| From Node | Read-only link | Ready for racking | |
| Transfer Type | Selection | Step | |
| To Node | Read-only link | Racking | |
| To Location | Selection + camera | Global | |
| Billed Labor | section icon only | — | collapsed |
Pattern: same as screenshot 4. "Ready for X" nodes are gating-only (parts wait there) — moving past one accrues no measurable labor.
Screenshot 6 — Racking → Ready For Plating + amber rack-required warning
| Field | Type | Example | Notes |
|---|---|---|---|
| Part Count | Integer (stepper) | 49 | Available: 49 |
| Part Number | Read-only link | TEST225451 | |
| From Node | Read-only link | Racking | |
| Transfer Type | Selection | Step | |
| To Node | Read-only link | Ready For Plating | |
| To Station | Selection | SP Line | destination has multiple plating lines; SP Line is the auto-pick |
| To Location | Selection + camera | Global | |
| WARNING BLOCK (amber, with ⚠ icon) | banner | "Parts are currently at a RACKING node and are not racked." | Soft block — informs operator the parts haven't been associated with a physical rack yet |
| Billed Labor | section icon only | — | collapsed |
| Footer | THREE buttons | Cancel · RACK PARTS (red secondary) · MOVE (49) (red primary) | RACK PARTS opens a separate Rack Parts dialog (screenshots 7–8) |
Critical pattern: a node's type (e.g. Racking) can require auxiliary records (rack assignment) BEFORE the move is allowed. The UI warns but doesn't hard-block — operator can still hit MOVE if they choose, presumably escalating an audit flag. RACK PARTS is the "resolve the warning" path.
This implies the recipe author needs a way to declare:
"This step's destination requires a rack assignment before move." — a node-type-level rule, not a transition-input rule.
→ NEW field on fp.step.template and fusion.plating.process.node:
requires_rack_assignment Boolean. When True and the operator hasn't
linked a rack to this batch of parts yet, the tablet shows the amber
warning + RACK PARTS button.
Screenshot 7 — Rack Parts dialog (overlay on top of Move Parts)
| Field | Type | Example | Notes |
|---|---|---|---|
| Title | "Rack Parts" with QR-scanner icon top-right | — | scanner icon = scan rack QR to auto-fill To Rack |
| To Rack | Search-and-select dropdown | (blank → "Search Racks…") | M2O picker against a rack registry |
| Per-line row | composite | TEST225451 on WO 4521 (49) — Unit: Count dropdown — Amount: 49 | shows the part being racked + qty in the chosen unit |
| Unit | Selection | Count | (other values likely: Count / Pieces / Lbs / Kg / Sheets — TBD) |
| Amount | Number | 49 | usually = Part Count from Move Parts but editable |
| Billed Labor | section icon only | — | collapsed; same widget as Move Parts |
| Footer | THREE buttons | Cancel · SAVE (disabled until To Rack set) · SAVE + PRINT (disabled until To Rack set) | SAVE + PRINT prints rack travel ticket / barcode |
Screenshot 8 — Rack Parts dropdown expanded
| Element | Notes |
|---|---|
| Searchable dropdown | Free-text "Search Racks…" filters list |
| Shows list of named racks: Rack 3, Rack 4, Rack 5, Rack 6, Rack 7, Rack 9, Rack 11 ... | Gaps in numbering (no Rack 8, no Rack 10) imply active filter — dropdown only shows racks currently empty / available. Numbers persist; full list is sparser than the index range. |
| Highlighted on hover | Rack 3 shown highlighted, indicating standard combobox UX |
Implication for our model: a fp.rack registry already has the
shape we need (name, active, state for empty/in-use). Need a
M2O on the racking transition log: rack_id. Selection is filtered
to state='empty' by default, with an override to show all.
The QR scanner icon implies a scan-to-fill flow on the tablet — same
mechanism we already use elsewhere (/fp/shopfloor/scan endpoint
resolves a scanned QR to a tank/job/etc.). For racks, we'd extend the
scan endpoint with a fp-rack:<id> token resolver.
Patterns updated after screens 4–8
-
Three transition-time prompt families now visible:
- Author-defined compliance prompts (per-step on
fp.step.template.transition.input) — variable per step. - Always-on context fields (Part Count, From/To Node, etc.) — system-derived, not authored.
- Step-type-driven side dialogs — declared by Boolean flags on
the step (e.g.
requires_rack_assignment) — open a separate mini-dialog (Rack Parts) before the main move can complete. Other likely flags:requires_bake_window,requires_qc_check,requires_signature. Each maps to an existing or new sub-dialog.
- Author-defined compliance prompts (per-step on
-
Move button label reflects qty:
MOVE (49)vsMOVE (1). Operator confidence cue — they see exactly how many parts they're committing to move before tapping. -
Soft-block vs hard-block UI language:
- Amber warning + clickable resolution button = soft block (operator may proceed with audit log).
- Red error + disabled MOVE = hard block (operator cannot proceed). Not yet seen in any screenshot, but pattern is implied by the amber design.
Screenshot 9 — Compact part-row card (paused timer)
| Element | Notes |
|---|---|
| Checkbox (left) | bulk-select for cross-row actions |
| Red paused icon (∥∥) | visual cue: timer is currently paused on this row |
| Inline summary | "1 TEST225451 | Rack 3 | Racking" — Qty + part link + rack link + node link, all clickable navigations |
| Sub-line | "Kris Pathinather: 27s" — operator name + accrued timer duration on this row |
Pattern: this is a Plant Overview / Tablet row — a per-batch position card showing a single chunk of parts at a single node, with the operator who currently owns its labor timer + its rack assignment inline. Clicking any blue link drills into that record.
Screenshot 10 — Stop User Labor Timer dialog
| Field | Type | Example | Notes |
|---|---|---|---|
| Title | "Stop User Labor Timer" | — | Distinct dialog from Move Parts; fires when operator pauses without moving |
| Billed Labor | section | Kris Pathinather, Timer Duration: 32s (100.0% billed) | with RESET ALL EDITS button |
| Per-row | composite | "WO #4521 PN: TEST225451 Qty: 1" + Product Select + hrs/min/sec inputs | NEW: Product dropdown — operator can split timer time onto multiple products. Defaults to current PN. |
| Footer | THREE buttons | Cancel · SAVE · SAVE & START NEW TIMER | Distinguished from Move Parts which is Cancel/MOVE — labor reconciliation is its own action |
Critical insight: labor reconciliation is a standalone flow, not embedded in Move Parts. Operator can stop the timer without moving parts. This implies our model needs:
- A persistent labor timer record (
fp.labor.timer?) per (operator × WO × part × node), independent of the move log. - A labor reconciliation action that closes a timer with a billed hrs/min/sec breakdown, optionally splitting across multiple products.
Screenshot 11 — Move Rack: tyut (full-rack move dialog)
| Field | Type | Example | Notes |
|---|---|---|---|
| Title | "Move Rack: tyut" | — | rack name (here "tyut") in the title — ties move to a specific rack record |
| Rack Labels | M2M with + button |
(empty) | tag rack with shop labels (priority colours, customer codes, etc.) |
| Parts section | static list of contained parts | "49 TEST225451 Parts on WO 4521" + "1 TEST225451 Parts on WO 4521" | shows ALL parts on the rack at once — moving a rack moves everything on it as one unit |
| Type | Selection | Step | same as Move Parts |
| To Node | Selection (read-only here, "Soak Clean (SP-1)") | Soak Clean (SP-1) | greyed-out — auto-derived from current step's recipe path |
| To Station | Selection | Soak Clean (SP-1) | editable; defaults to first compatible station |
| Billed Labor | section + RESET ALL EDITS | — | per-batch row breakdown: each contained part qty gets its own hrs/min/sec inputs |
| Per-row | composite (×2 in this rack) | "WO #4521 PN: TEST225451 Qty: 49" → 0/0/6 + "WO #4521 PN: TEST225451 Qty: 1" → 0/0/0 | individual labor split per chunk on the rack |
| Footer | TWO buttons | Cancel · SAVE | no separate MOVE button — SAVE commits the move |
Critical pattern: Move Rack is a rack-as-unit move, distinct from Move Parts (parts-as-unit). When parts are racked, they MUST move as a rack — Steelhead enforces this by not surfacing the per-part Move button (see screenshot 12: those buttons are disabled/greyed when racked). The recipe author doesn't author move-rack rules; they're runtime-enforced based on whether the parts are currently on a rack.
Screenshot 12 — Plant overview: Racks vs Parts panes (rack collapsed UI)
| Section | Notes |
|---|---|
| Racks header | with red "UNRACK MULTIPLE" button — bulk unrack action |
| Rack row | red ∥∥ pause icon · "50 Parts" · rack name "tyut" (Rack 3) · current node breadcrumb "Soak Clean (SP-1) / Soak Clean (SP-1)" · "Kris Pathinather: 14s" labor stamp · MOVE RACK primary button on the right |
| Parts header | with + ADD NEW PARTS button + filters (Part Number ▾, Part Account ▾) + search box "Search by PN or group" |
| Parts row(s) | qty + PN + rack + node breadcrumb + operator/timer + per-row buttons: MOVE PARTS (greyed out / disabled because racked) + QR icon + ribbon icon + ⋮ kebab + ⌄ expand |
Critical UI rule emerging: when parts are racked, the per-part Move Parts button greys out. The only way to move racked parts is via MOVE RACK. This is enforced by the disabled button state, not by error message. Operator UX is: "You can't accidentally move just some of these parts — they're racked together, move the rack."
This implies a runtime guard on fp.job.step (or wherever the move
controller lives): if rack_id is set on the part-batch, reject
move-parts calls and require move-rack.
Screenshot 13 — Move Rack: tyut (different destination, station picker shows non-default station)
| Field | Type | Example | Notes |
|---|---|---|---|
| Title | "Move Rack: tyut" | — | same dialog shape as screenshot 11 |
| Rack Labels | M2M + button | (empty) | |
| Parts list | static | "49 TEST225451 Parts on WO 4521" + "1 TEST225451 Parts on WO 4521" | same contents as 11 |
| Type | Selection | Step | |
| To Node | Selection (read-only) | Rinse (SP-2) | rack has advanced one step from screenshot 11 (Soak Clean → Rinse) |
| To Station | Selection | "Cold Water Rinse …" (truncated) | NEW: shows that one node can have multiple stations with descriptive names, not just SP-numbered codes. The SP-2 prefix is the tank code; "Cold Water Rinse" is the station's display name. |
| Billed Labor | section + RESET ALL EDITS | — | per-batch labor split as before; total Timer Duration: 10s |
| Footer | TWO buttons | Cancel · SAVE |
New insight: stations have both a code (SP-2) AND a friendly
name (Cold Water Rinse). Our model already has name + code on
fusion.plating.tank — confirming our existing design matches
Steelhead's naming model 1:1.
Patterns updated after screens 9–13
-
Labor timer is a first-class persistent entity, not a UI ephemeral. It has a stop-without-move flow (screen 10), it can be reset, it splits across products. Probably its own model
fp.labor.timerwith states:running / paused / stopped / reconciled. -
Rack-vs-parts move duality: parts-not-racked → Move Parts dialog; parts-racked → Move Rack dialog (MOVE PARTS greys out). Move Rack moves all rack contents at once with per-chunk labor breakdown. Need
rack_idon the move-controller decision. -
Rack Labels as a tagging surface — M2M against a tag-like registry. Not yet authored on
fp.step.template(it's a runtime rack metadata feature). Goes onfp.rackdirectly when we build that registry. -
Plant Overview shape: top section "Racks" with rack-level primary action (MOVE RACK) + bulk action (UNRACK MULTIPLE); bottom section "Parts" with per-part actions (greyed when racked) + filters + search. This is the layout the simple-mode customer probably also wants on their plant overview — but that's a separate sub-project from the recipe editor.
-
"To Node" vs "To Station" hierarchy is consistent: To Node is read-only (auto-derived from recipe sequence), To Station is editable (operator picks among compatible stations on that node). For our simple recipe editor, this means each step's
tank_idsM2M is the authoritative compatible-station list that the runtime uses to populate the To Station dropdown. The recipe author's job is to declare the ALLOWED set; the operator picks among them at run time.
Screenshot 14 — Move Rack: tyut → Ready For DeRack (no station picker)
| Field | Type | Example | Notes |
|---|---|---|---|
| Title | "Move Rack: tyut" | — | rack name in title |
| Rack Labels | M2M + button | (empty) | |
| Parts list | static | "49 TEST225451 Parts on WO 4521" + "1 TEST225451 Parts on WO 4521" | full rack contents |
| Type | Selection | Step | |
| To Node | Selection (read-only, greyed) | "Ready For DeRac…" (truncated; full = "Ready For DeRack") | gating-only node — no station to choose, no plating action |
| No To Station field | — | — | confirms: when destination is a single-station / gating node, the To Station row does not render |
| Billed Labor | section + RESET ALL EDITS | — | per-batch breakdown |
| Per-row labor | composite | "WO #4521 PN: TEST225451 Qty: 49" → 0/0/4 + "WO #4521 PN: TEST225451 Qty: 1" → 0/0/0 | |
| Footer | TWO buttons | Cancel · SAVE |
Pattern: gating nodes (anything starting with "Ready For…") are
single-station and skip the To Station selector. Move Rack and Move
Parts both honour this. Our model should treat any node with exactly
one tank in tank_ids (or none — implying "any global location") as a
node that hides the station picker.
Screenshot 15 — Move Parts with soft-block: missing spec measurements (MOVE button DISABLED)
| Field | Type | Example | Notes |
|---|---|---|---|
| Part Count | Integer (stepper) | 49 | Available: 49 |
| Part Number | Read-only link | TEST225451 | |
| From Node | Read-only link | Bake | |
| From Station | Read-only link | Bake | |
| Transfer Type | Selection | Step | |
| To Node | Read-only link | Adhesion Testing | |
| To Location | Selection + camera | Global | |
| WARNING BLOCK (amber, ⚠ icon) | banner | "Additional Spec Measurements are required for this part." | distinct from the rack-required warning |
| Billed Labor | section + RESET ALL EDITS | — | Timer Duration: 7s |
| Per-row labor | composite | "WO #4521 PN: TEST225451 Qty: 49" → 0/0/7 | |
| Footer | TWO buttons | Cancel · MOVE (49) (DISABLED / greyed) | HARD BLOCK — operator can't proceed until spec measurements are recorded |
Critical pattern — first hard block we've seen:
- Steelhead shows the same amber colour for both soft-block (rack warning) and hard-block (missing spec). The DIFFERENCE is the primary button state: soft-block leaves MOVE enabled (audit and proceed); hard-block greys MOVE out (must resolve first).
- Steelhead does NOT explain HOW to record the missing measurements in this dialog — the operator is left to figure out where to enter them. This is what the user means by "Steelhead is limited."
Where we improve over Steelhead:
- The amber banner should have a clickable resolution button like the rack warning's "RACK PARTS" button — e.g. "RECORD MEASUREMENTS" that opens the spec-measurement input dialog inline.
- After recording, the banner clears + MOVE re-enables. No screen hunting.
- Distinguish soft vs hard visually: amber for soft (proceed-with- audit), red for hard (must-resolve).
Patterns updated after screens 14–15
-
Gating nodes (Ready For …) are single-station / no-station nodes — UI hides the To Station selector for them. Our recipe editor's step-template form should let the author mark a step as "gating" (no tanks/stations needed) and the runtime auto-hides the station picker.
-
Soft-block vs hard-block protocol:
- Soft-block: amber banner + resolution button + MOVE stays enabled (rack-required warning, screen 6).
- Hard-block: amber banner + MOVE disabled until resolved (missing spec measurements, screen 15).
- Steelhead's gap: hard-block doesn't tell operator where to go to resolve. Our improvement: every blocker (hard or soft) gets an inline resolution button.
-
Spec measurements vs operation measurements vs transition inputs: three distinct concepts now visible:
- Operation measurements (
input_template_idsonfp.step.template) — recorded during a step, e.g. "Actual # of parts" mid-bake. - Transition inputs (
transition_input_ids) — recorded when leaving a step, e.g. "Customer WO #" or photo evidence. - Spec measurements (NEW from screen 15) — part-level specs that are required for the part itself across multiple steps, e.g. "thickness reading" required after Adhesion Testing. These don't belong on the step template — they belong on the part record (or on a part × step rule). The amber block fires when the part hasn't satisfied a spec rule.
→ Implies a separate authoring surface: per part (or per customer × part), a list of "required spec measurements" with their trigger node. Out of scope for this sub-project but worth flagging in the design as a hand-off point.
- Operation measurements (
-
MOVE button label DISABLED state: greyed background, greyed text, still shows count "MOVE (49)". Steelhead does NOT show a tooltip explaining why — another improvement opportunity for us.
Screenshots 16, 17, 18 — Paper Job Traveller (3-page WO #023633-1, Amphenol Canada, ENP-Aluminum)
These are the manual paper job travellers the client fills in by pen as the parts move. They're the gold-standard spec of what data flows through a real plating job today. Every column on these sheets is a candidate for digital capture in our recipe → step-template → runtime stack.
Header (repeats on every page)
| Column | Example | Where it lives in our model |
|---|---|---|
| WO # | 023633-1 | fp.job.name (already exists) |
| Barcode | (1D barcode of WO #) | fp.job.qr_code (already auto-generated; switch to 1D Code 128 for traveller print) |
| Date In | 29-11-2024 | fp.job.date_received (already exists, comes from fp.receiving) |
| Due Date | 11-12-2024 | fp.job.date_due (already exists, from SO commitment date) |
| Type | ENP-ALUMINUM | fp.job.coating_config_id.name (already exists) |
| Order No. | 023633 | fp.job.sale_order_id.name (already exists) |
| P.O. No. | 731830 | fp.job.sale_order_id.client_order_ref (already exists) |
| Customer | AMPHENOL CANADA + address + phone | fp.job.partner_id.* (already exists) |
| WO Generated By | RIYA | fp.job.create_uid.name (already exists) |
→ No new model fields needed for the header. Existing job fields cover everything. The traveller-print report just needs to pull them.
Item Information block (page 1, top)
| Column | Example | Where it lives |
|---|---|---|
| Item informations / Part # | VS-E0443220025 | fp.part.catalog.part_number (already exists) |
| Rev. | 1F | fp.part.catalog.revision (already exists) |
| Mat. (material) | 6061-T6511 | NEW field on fp.part.catalog: base_material Char (e.g. "6061-T6511 aluminum"). Currently we have implicit material via coating config — making it explicit per-part is small. |
| Catg. (category) | ENP-ALUMINUM | derived from fp.part.catalog.coating_config_id.name |
| S/N (serial #) | (blank for batch) | already exists as x_fc_serial_number (Sub 5) |
| Item-Name / Process Description | "SHELL RECEPTACLE / 01 - ELECTROLESS NICKEL PLATING PER E499-303-00-002 OF AMPHENOL SPEC # E499-303-00-XXX REV : 1F" | fp.part.catalog.name + fp.part.catalog.customer_facing_description (already exist; just need to merge in the report) |
| Qty Rec. | 5850 → 5839, 5835 (multiple counted lines) | NEW: traveller needs to show received qty AND per-stage running counts. Stages: Received → Inspected → Racked → Plated → Final-counted. Capture per-stage. |
| VIS INSP. | 0 (visual inspection rejects/holds during incoming) | NEW: integer column on fp.job for "qty rejected at incoming inspection" |
| Rework | (blank) | NEW: integer column for "qty sent to rework" |
| Special Requirements | "MID PHOS / PANEL THICKNESS: 0.0224"-0.0228" (PLATING THICKNESS: 0.0005"-0.0007") / BAKE @ 250 DEG F FOR 1 HOUR / *****RUN EACH LOAD WITH 3 TEST PANELS AND VERIFY THE PLATING THICKNESS USING XRF PRIOR TO REMOVING PARTS FROM THE TANK..." | NEW: fp.job.special_requirements Text (free-form). Today this lives in customer specs but isn't pulled onto the traveller. |
| Stamp + Date | initials "LY" + "06.12.24" | runtime sign-off — captured per-section by the inspector |
Process-Sheet header (page 1, middle)
| Column | Example | Where it lives |
|---|---|---|
| Process name 1 | "ENIP (A) BAKE (GENERIC)" | fp.job.process_node_id.name (root recipe) |
| Process name 2 | "ENIP (A) BAKE" | sub-process node name (already exists in tree) |
| Catg. | ELECTROLESS NICKEL | from coating config |
| Special Req. | (blank in this WO) | fp.job.special_requirements |
| Spec / Info block | (blank, reserved for sticker / inspector notes) | print-only blank |
Step rows (the heart of the traveller)
Each step row carries:
| Column | Type / format | Notes |
|---|---|---|
| Step # | Integer (1, 2, 3, ... 25) | already on our model as sequence (auto-numbered in the OWL editor) |
| Tank | Char code (A-1, A-2, A-13, blank) | comes from tank_ids M2M chosen at runtime → fp.job.step.tank_id |
| Operation | Operation name (e.g. "Soak clean", "Etch", "E-Nickel Plate", "Inspection") | fp.step.template.name |
| Operation actual-data column (multi-row, hand-written) | Free-form actuals filled in pen — varies per step. Examples: "Actual Qty: 5839", "Actual Time: 04 min", "Actual Temperature: 170 ºF", "Actual time: 01 min / Actual temperature: 190 ºF / Plating thickness: 0.0005"", "PASS: ✓ FAIL: ___", "Actual thickness: 0.00057" | THIS IS THE OPERATION-MEASUREMENT INPUT LIST. Each value the operator pencils in is a fp.step.template.input row. |
| Instruction | Free-form instructions tied to a Work Instruction reference | "Immerse parts in alkaline soak cleaner for 4-6 minutes @ 150-170 ºF as per WI 10.07." / "Verify plating thickness is as per customer requirements and as per WI 10.09" / "Verify the job traveler was filled out completely and the information is correct." |
| Unit | Char (each / Minutes / Seconds / mils / min / ºF / blank) | NEW: fp.step.template.unit Char or Selection. Currently NOT in our model. Operators need to know what unit the actual-time / actual-thickness etc. should be in. |
| Material | mostly N/A; some steps have "MID PHOS" written | this column is for chemistry callout when the step has a material-specific bath. Maps to fp.step.template.process_type_id (already exists) — the process type's name prints here. |
| Voltage | mostly N/A | electrolytic steps only. NEW: optional voltage_target Float on step template. |
| Viscosity | mostly N/A | bath-quality callout. NEW: optional viscosity_target Float. |
| Time (min) | range, e.g. "4 - 6" / "55 - 65" / "25 - 35" | the target range the operator must hit. NEW: time_min_target + time_max_target Float on step template. |
| Temp. | (mostly empty in target column; pencilled in actual) | the target range. NEW: temp_min_target + temp_max_target + temp_unit Selection (F/C). |
| Stamp | initials column | runtime per-step sign-off. Captured as fp.job.step.signoff_user_id + signoff datetime. |
| Date | dd.mm.yy format | per-step completion date (auto-stamped on Mark Done). Already exists as fp.job.step.date_finished. |
Concrete examples from the WO that show the data shape
| Step # | Tank | Operation | Targets (printed) | Actuals (handwritten) |
|---|---|---|---|---|
| 1 | — | Part verification and quantity check | unit=each | "Actual Qty: 5839" |
| 2 | — | Issue Panels | — | (no actual; just stamp/date) |
| 3 | — | Rack | — | "Actual Qty: 5839" |
| 4 | A-1 | Soak clean | 4-6 min @ 150-170 °F | "Actual time: 04 min, Actual Temperature: 170 °F" |
| 5 | A-2 | Rinse | — | (stamp only) |
| 6 | — | Water Break Test | — | "PASS: ✓" |
| 7 | A-3 | Etch | 55-65 sec | "Actual Time: 60 sec." |
| 9 | A-5 | Desmut | 55-65 sec | "Actual Time: 60 sec." |
| 11 | A-7 | Zincate | 25-35 sec | "Actual Time: 25 sec." |
| 13 | A-5 | Strip Zincate | 15-25 sec | "Actual Time: 25 sec." |
| 17 | A-13 | E-Nickel Plate | 185-190 °F | "Actual time: 01 min, Actual temperature: 190 °F, Plating thickness: 0.0005"" |
| 22 | — | Baking | (Time In / Time Out) °F | "Time In: 10:00, Time Out: 11:00, Actual temperature: 250 °F" |
| 23 | — | Inspection | mils | "Actual thickness: 0.00057, PASS: ✓" |
| 24 | — | Final Inspection | — | "Verify the job traveler was filled out completely…" |
| 25 | — | Shipping | — | "Actual Qty: 5839" |
What the traveller tells us about our data model gaps
Step template — NEW fields needed
| Field | Type | Reason |
|---|---|---|
unit |
Char or Selection | Print "each / Minutes / Seconds / mils / ºF / N/A" so operator knows units |
time_min_target |
Float | Lower bound of operation time |
time_max_target |
Float | Upper bound; runtime warns if outside |
time_unit |
Selection (sec / min / hr) |
how to interpret time fields |
temp_min_target |
Float | Lower bound of operation temperature |
temp_max_target |
Float | Upper bound |
temp_unit |
Selection (F / C) |
how to interpret temp fields |
voltage_target |
Float (optional) | electrolytic steps |
viscosity_target |
Float (optional) | bath-quality |
material_callout |
Char (optional) | "MID PHOS" — short string printed in the Material column. Defaults to process_type_id.name if not set. |
Step template input list (fp.step.template.input) — likely defaults
For the traveller to render a useful actual-data column, each step template should pre-populate its input list with the right entries:
- Cleaning / etch / desmut / zincate / acid steps:
Actual Time (sec or min)+ (optional)Actual Temperature (°F). - Plating step:
Actual time (min)+Actual temperature (°F)+Plating thickness ("). - Bake step:
Time In (HH:MM)+Time Out (HH:MM)+Actual temperature (°F). - Receiving / racking / shipping:
Actual Qty. - Inspection (visual):
PASS / FAILselection. - Inspection (thickness measurement):
Actual thickness (mils)+PASS / FAIL. - Water break test:
PASS / FAILselection. - Rinse / dry: no input — sign-off only.
These defaults come "out of the box" when the customer drops a library step into a recipe — no need for them to author the input list every time. They can override / add later.
Job-level NEW fields
| Field | Type | Reason |
|---|---|---|
qty_received |
Integer | "Qty Rec." column on traveller header |
qty_visual_inspection_rejects |
Integer | "VIS INSP." column |
qty_rework |
Integer | "Rework" column |
special_requirements |
Text | "Special Requirements" block (long free text from customer spec) |
qty_at_stage (computed One2many?) |
per-step running count | NEW — would let "Qty Rec.: 5850 → 5839 → 5835" auto-render on the traveller. Computed from fp.job.step.qty_done chain. |
Part catalog NEW fields
| Field | Type | Reason |
|---|---|---|
base_material |
Char | "6061-T6511 aluminum" — currently implicit |
Traveller report (new QWeb template)
fusion_plating_reports/report/report_fp_job_traveller_v2.xml — landscape A4 multi-page:
- Page 1: header + Item Information block + Process-Sheet header + first 6–8 steps.
- Pages 2..N: continuation rows for the remaining steps.
- Last page: Footer / final inspection / shipping rows + room for stamps / dates.
The current report_fp_job_traveller.xml (in fusion_plating_jobs,
shipped in S5/S18) is portrait and minimal. We rebuild it to match
this paper format.
What we'll auto-capture instead of pencilling in
| Pencilled today | Auto-captured by us |
|---|---|
| Actual Qty | comes from fp.job.qty_done chain (already exists) |
| Actual Time | timer on fp.job.step (already exists, S1/S2 stuff) |
| Actual Temperature | IoT sensor reading (already exists in fusion_iot/fusion_plating_iot/) — ties to step's tank, snapshot saved at sign-off |
| Plating thickness | Fischerscope auto-extract (already exists in fusion_plating_certificates/fp.thickness.reading, S19) |
| Time In / Time Out (bake) | bake-window record (already exists, S6/S15) |
| PASS/FAIL | QC checklist (already exists in fusion_plating_quality, S18/S19) |
| Stamp (initials) | per-step signoff_user_id (already exists) |
| Date | per-step date_finished (already exists) |
Most of the runtime capture machinery already exists. The gap is making the recipe editor expose target ranges + units + per-step input list defaults so the traveller can render the targets next to the actuals.
Patterns updated after screens 16–18
-
Paper traveller is the spec for what's authored on a step template. Every column on the paper sheet maps to either:
- an authored field on
fp.step.template(target range, unit, material callout, voltage, viscosity), OR - a runtime-captured field on
fp.job.step(actual time, actual temp, sign-off, date), OR - a runtime-captured input value (
fp.job.step.input.value) tied to afp.step.template.inputdefinition.
- an authored field on
-
Step "Actual" data is multi-field per step, not just one "actual" value. E.g. an etch step has Actual Time only; a plating step has Actual Time + Actual Temp + Plating Thickness; a bake has Time In + Time Out + Actual Temp. The input list per step template must be flexible enough to model this — which it already is via
input_template_ids. We just need sane defaults so the customer doesn't have to author them from scratch. -
Target ranges (e.g. 4-6 minutes, 150-170 °F) are first-class authored data. They drive: traveller print, runtime overrun warnings (S7), out-of-spec alerts on IoT readings (existing), and the operator UX cue ("you're at 7 min, target is 4-6 — log a deviation?"). Currently we have
estimated_duration(single value) onprocess.node— needs to extend to min/max per step. -
Customer-spec callout ("MID PHOS", "BAKE @ 250 DEG F FOR 1 HOUR", "RUN EACH LOAD WITH 3 TEST PANELS…") is per-job, not per-step. It's the job-header special-requirements field (free text, copied from customer spec library). Already covered by
fusion.plating.customer.specmodel — needs to be pulled onto the traveller print. -
Multi-pass through same tank (e.g. step 11 = A-7 Zincate, step 13 = A-5 Strip Zincate, step 15 = A-7 Zincate AGAIN). Our recipe model handles this fine — each step is its own node instance. Worth confirming the simple editor renders this visually (e.g. "Zincate" appears twice in the ordered list, each with its own SP-7 station tag). The screenshots from the customer's other system in the original brainstorm already showed this pattern (Primary Rinse appeared twice in the Selected list).
Screenshots 19–22 — Steelhead's auto-generated CoC traveller PDF (7-page report)
These pages are the digital output Steelhead produces after a job
completes — a multi-page PDF traveller showing the full chain of
custody, every step transition, every captured measurement, the
operator who recorded each value, and the timestamp. This is what
gets sent to the customer with the parts. Functionally equivalent to
our own fp.certificate flow + the new traveller report we plan to
build.
Critical context: this is the OUTPUT that Steelhead generates from its captured data. Everything on these pages is something we MUST be able to capture + render to match feature parity. Job:
- Customer: (printed at top, e.g. cert-style header)
- Part: 2144A6201-5 OUTER CYLINDER ASSY
- Description: "ELECTROLESS NICKEL PLATING & BAKE PER LGPS 1104G & IAW TECHNIQUE#: QA-016-36 REV.1, PLATING THICKNESS: 0.0019", HYDROGEN EMBRITTLEMENT RELIEF BAKING @ 375ºF FOR 23 HOURS"
- Quantity: 1
- WO#: 620, PO#: 980806214, Date: 2025-09-23
- Specification(s): "Electroless Nickel Plating"
- Footer: "Cert Created At: 2025-09-23", page #/total, Nadcap Accredited logo, ENTECH logo
Page 1 — Header + first 3 step transitions (no captured data yet)
Steps shown:
- Ready for Incoming Inspection — Moved By: Riya Bhatt, Time: Sep 18, 2025 07:22:31 AM
- Ready For Plating — Moved By: Kris Pathinather, Time: Sep 18, 2025 07:25:00 AM
- Soak Clean (S-3) — Moved By: Kris Pathinather, Time: Sep 18, 2025 07:31:26 AM
Pattern: each step heading shows Step Name (station code) —
"Soak Clean (S-3)" combines the step's name with the chosen station.
Below the heading, "Part Number" and "Moved By: Time:
" are always rendered. Then if the step has captured
measurements, a sub-table follows.
Page 2 — Step transitions with captured measurement tables
Each step that captured operator inputs renders a 4-column table: Name | Description | Value | Recorded By
Sample tables:
Soak Clean sub-table (between page 1 step heading and page 2 next step heading):
| Name | Description | Value | Recorded By |
|---|---|---|---|
| Soak Clean Time (5-10 min.) | (blank) | 00:05:22 | Kris Pathinather |
| Soak Clean Temp 165-195 (ºF) | (blank) | 170 | Kris Pathinather |
ElectroClean (S-3) sub-table:
| Name | Description | Value | Recorded By |
|---|---|---|---|
| ElectroClean Time 30-90 Seconds | (blank) | 00:00:55 | Kris Pathinather |
| ElectroClean Amperage (A) | (blank) | 280 | Kris Pathinather |
| Surface Area (FT2) | (blank) | 7 | Kris Pathinather |
| ElectroClean (SP-1) Temperature (ºF) | (blank) | 170 | Kris Pathinather |
Water Break Free Test sub-table (test-style measurement):
| Name | Description | Value | Recorded By |
|---|---|---|---|
| Water Break Free Test | "Perform water break test on parts as per WI 10.08. Observe parts for 30 – 60 seconds after removing from the soap rinse and observe if the production parts exhibit a water break free surface. If test fails, repeat from step Soak Clean." | PASS | Kris Pathinather |
Page 3 — Mid-job step transitions (HCl Activation, Rinses, Plating, Porosity)
Sample tables:
Acid Dip sub-table (HCl Activation step — the input description includes the WI reference):
| Name | Description | Value | Recorded By |
|---|---|---|---|
| Acid Dip Time | "Immerse parts in HCl tank for 20-40 seconds as per WI 10.07." | 00:00:30 | Kris Pathinather |
Electroless Nickel Plating (S-10) sub-table — KEY step, multiple measurements:
| Name | Description | Value | Recorded By |
|---|---|---|---|
| E-Nickel Plate Temp (187-193ºF) | (blank) | 190 | Kris Pathinather |
| Final Panel Thickness | (blank) | 0.0248 | Kris Pathinather |
| Actual Thickness (") | "Immerse parts with test panels in E-Nickel tank as per WI 10.07." | 0.0018 | Kris Pathinather |
Porosity Test — minimal heading-only entry with one inline "Results: PASS" text under the step name (instead of a table).
Pages 4–5 — More transitions (DeRacking, Ready for De-Masking, De-Masking, Ready for bake, Bake)
Heading-only step transitions (no captured data table):
- DeRacking — Moved By: Ryan Persaud, Time: Sep 18, 2025 02:16:45 PM
- Ready for De-Masking — Moved By: Ryan Persaud, Time: Sep 18, 2025 02:20 PM
- De-Masking — Moved By: Ryan Persaud, Time: Sep 18, 2025 02:21:05 PM
- Ready for bake — Moved By: Ryan Persaud, Time: Sep 18, 2025 02:44:52 PM
- Bake — Moved By: Ryan Persaud, Time: Sep 18, 2025 2:45:27 PM
Pattern: gating / no-input steps still render a heading + "Moved By + Time" but no measurement table. They contribute to the chain-of-custody trail without measurements.
Page 6/7 — Bake captured data + Adhesion Test + EN Final Inspection (final pass/fail)
Bake sub-table:
| Name | Description | Value | Recorded By |
|---|---|---|---|
| Hydrogen Embrittlement Time | (blank) | 23:48:19 | Ryan Persaud |
| Hydrogen Embrittlement Relief Temp (ºF) | (blank) | 375 | Ryan Persaud |
Post Plate Inspection (heading-only, gating)
- Moved By: Brett Kinzett, Time: Sep 22, 2025 08:22:01 AM
Final EN inspection sub-table — pass/fail summary:
| Name | Description | Value | Recorded By |
|---|---|---|---|
| Adhesion Test | (blank) | PASS | Brett Kinzett |
| EN Final Inspection | (blank) | PASS | Brett Kinzett |
What this PDF tells us (key insights)
-
Steelhead's CoC traveller is a chronological audit log, NOT a recipe traveller. It walks the actual transition history (not the authored recipe order) and prints captured data per step. Useful for AS9100 / Nadcap audit because it shows EXACTLY what happened, in time order, by whom.
-
Every captured input has 4 attributes:
- Name (e.g. "Soak Clean Time (5-10 min.)") — the input definition's display name. The target range is embedded in the name ("(5-10 min.)") rather than a separate column. This is a Steelhead UX choice — we should do better and put min/max in their own columns so the auditor can see authored vs actual side-by-side.
- Description (long-form WI reference) — pulled from the
step's
description(Html) field. Sometimes blank. Usually populated for inspection-type steps (where the operator needs the procedure text), not for routine measurements. - Value (the recorded value) — type varies: time HH:MM:SS, number (with implicit unit), PASS/FAIL, etc.
- Recorded By — operator's full name.
-
Time values are always in HH:MM:SS even for sub-minute readings ("00:00:55"). Steelhead uses one consistent time format. We should do the same on the report — it's noise for the operator entering the data ("how do I type 55 seconds?") but clean on the report.
-
Step heading has the station baked in — "Soak Clean (S-3)", "Electroless Nickel Plating (S-10)". This means our report needs to render the station code chosen at runtime, not just the step name. Already in our model (
fp.job.step.tank_id.code). -
Sub-tables only appear when there's captured data. Heading- only steps (Ready For X, gating nodes, racking moves) just show the chain-of-custody line. Our report should follow the same pattern — clean PDF with no empty tables.
-
Same input name with different runtime captures — "Rinse (S-11 / S-13)" is a single transition heading covering BOTH a rinse pass through tank S-11 AND tank S-13 (two physical rinse tanks operators dipped through in sequence). Steelhead collapses them into one heading. Our model has each as a separate step; we'd render two headings unless we add a "merge consecutive identical steps" report option. Decision later — for now, keep them separate.
-
Multi-day jobs are normal. The bake spans 23h 48min, post- plate inspection happens days later (Sep 19 / Sep 22). The traveller knows because each transition has its own timestamp. Our chain-of-custody log already captures this.
-
Two operators on the same job — Riya Bhatt does receiving, Kris Pathinather does the wet-line, Ryan Persaud handles masking/bake, Brett Kinzett does final inspection. The "Moved By" field changes per step, reflecting hand-offs. Already supported by our model.
-
Footer branding — "Nadcap Accredited (Administered by PRI)" logo + ENTECH company logo. Per-page. Page numbering "n/total". Plus "Cert Created At: ". Our existing
fp.certificatePDF flow can render this footer; we just need to add the Nadcap logo asset + company logo configurable.
What this means for our recipe editor + traveller report
Step template input list — render targets in their OWN columns
Steelhead embeds "(5-10 min.)" in the input name. We should split this into two authored fields:
| Field | Type | Notes |
|---|---|---|
target_min |
Float | Lower bound (e.g. 5) |
target_max |
Float | Upper bound (e.g. 10) |
target_unit |
Char | "min" / "ºF" / "A" / "FT2" / "in" / "%" / "(blank for pass-fail)" |
display_label |
Char (compute) | "Soak Clean Time" or "Soak Clean Temp" — clean name without the embedded range |
Report renders 5 columns per measurement table: Name | Description | Target | Actual | Recorded By
The Target column shows "5-10 min" (or "165-195 ºF" or "30-90 sec" auto-formatted from the three new fields).
Job traveller report = chronological audit, NOT step-order
Build the report to walk fp.job.step records ordered by
date_started (or date_finished), not by sequence. This matches
Steelhead's chain-of-custody ordering and is what auditors expect.
Heading format for each step transition
<Step Name> (<Tank code>) — already supported by our model. No
gap.
Time format
Use HH:MM:SS for all duration values on the report, even sub-minute. On entry (tablet input), accept "55 sec" or "00:00:55" — convert to canonical HH:MM:SS for storage.
Pass/Fail value rendering
For boolean inputs, render "PASS" / "FAIL" in caps. Already straightforward.
Step description field carries the WI reference
Already supported by our fp.step.template.description Html field.
The report just needs to render it stripped of HTML tags inline
(plain-text in the table cell).
Each operator's name comes from res.users.name
Our existing pattern. No gap.
Patterns updated after screens 19–22
-
The CoC PDF is a chronological audit, not a recipe-order print. Different report from the operator-facing traveller (which prints in recipe order so the operator knows what's next). We need both:
- Operator traveller (recipe-order, paper-style, A4 landscape, blank actual columns) — what Riya prints when Carlos starts a job.
- Customer CoC (chronological, captured-data, formal,
portrait, branded) — what gets attached to the cert and sent
to the customer.
Today we ship the CoC via
fp.certificate(S18/S19). Need to extend it to walk the chain-of-custody and render measurement sub-tables.
-
Target ranges should be authored as min/max + unit, not embedded in the name string. Steelhead embeds them which is a UX bug — auditors can't filter by "all jobs where Soak Clean Temp was out of range" because the range only exists as text. By splitting into structured fields, we get free queryability AND can colour-code the value cell on the report (green if in range, red if not).
-
Step description vs input description: Steelhead uses the "Description" column on the input table to show the WI reference. That's the step-level description repeated on the input row. Our model already has
descriptionon the step template — we just render it once at the input table header instead of per-row. Saves report space. -
Inputs can be multi-valued per step — ElectroClean has 4 inputs, E-Nickel Plate has 3, Soak Clean has 2. Our
input_template_idsOne2many already handles this. Confirmed by real data. -
"PASS" entries can be standalone (no table) — Porosity Test just prints "Results: PASS" inline, no input table. This is a Steelhead simplification. We should normalize: every step with a captured input renders a table, even if it's a single pass/fail row. Cleaner audit trail.
-
Move-By trail is the operator chain-of-custody. CRITICAL for aerospace / Nadcap. Our existing
fp.job.step.signoff_user_iddate_finishedalready provides this. The report just walks them in time order.
Screenshot 23 — Page 7/7 of CoC: Final Inspection / Packaging + Ready For Shipping
Step transitions on this page:
- Ready For Final Inspection / Packaging — Moved By: Brett Kinzett, Sep 22, 2025 08:42:22 AM (heading-only, gating)
- Final Inspection / Packaging — Moved By: Brett Kinzett, Sep 23, 2025 11:39:47 AM (heading + measurement table)
- Ready For Shipping — Moved By: Brett Kinzett, Sep 24, 2025 01:45:49 PM (heading-only, gating)
- (unnamed transitions) — Sep 24, 2025 02:03:29 PM and 02:06:16 PM
Final Inspection / Packaging sub-table:
| Name | Description | Value | Recorded By |
|---|---|---|---|
| Outgoing Part Count | (blank) | PASS | Brett Kinzett |
| Thickness Test Pass / Fail | (blank) | PASS | Brett Kinzett |
| Qty Accepted | (blank) | 1 | Brett Kinzett |
| Qty Rejected | (blank) | 0 | Brett Kinzett |
| Actual Coating Thickness - Final | (blank) | 0.0018 | Brett Kinzett |
Notes:
- "Outgoing Part Count" with value "PASS" is odd — looks like Steelhead conflated a count check with a pass/fail input. Our model should keep them separate (
Qty Acceptedinteger +Outgoing Part Count Verifiedboolean). - "Qty Accepted" + "Qty Rejected" are the final disposition split at packaging time. Non-negotiable for AS9100 shipment audit.
- "Actual Coating Thickness - Final" duplicates the mid-job thickness reading captured at E-Nickel Plate. Steelhead re-takes it at final inspection as a sanity-check before sign-off.
- The two unnamed transition rows at the bottom are likely "Shipped" / "Closed" gating moves whose headings got cut off the screenshot.
Screenshot 24 — Cert sign-off footer (last block on the CoC)
Two side-by-side panels:
Left panel — "Certified By:"
- Embedded signature image (handwritten "K Pathinathn" — Kris Pathinather signed)
- Printed name below: "Name: Kris Pathinather"
Right panel — "Certification Statement: Ref. WO# 620" (top half) + "Other Comments:" (bottom half), both blank-but-bordered for handwritten or template-rendered cert language.
This is the certificate of conformance attestation block — a
named individual takes legal responsibility for the cert. Required
for Nadcap, AS9100, CGP. Already supported by our existing
fp.certificate.signoff_user_id + signature image (S18). Just need
to surface the signed image + the printed name + the cert statement
in our PDF footer.
The "Certification Statement" body in our existing CoC is already authored on the certificate template — Steelhead leaves it blank in this screenshot suggesting the full statement was on a different page. We DO render it (boilerplate "We certify that the parts conform to specification…" language).
Patterns updated after screens 23–24
-
Final inspection captures the disposition split — Qty Accepted / Qty Rejected as integer fields at the last QC step. These should appear as
input_template_idson a "Final Inspection" library step (sane defaults). Plus a redundant "final coating thickness" reading. We model these as standard operation-measurement inputs. -
Certificate sign-off block is a 2-column footer: left = signature image + name, right = cert statement + other comments. Already supported by
fp.certificate(S18). Just confirm our PDF template arranges them this way. -
CoC renders gating "Shipped / Closed" transitions as chain-of-custody entries with no measurement tables — same treatment as "Ready For X" gating nodes.
Screenshot inventory closed (24 screenshots logged total)
All Steelhead screens captured. The data model + UI implications have been folded into the Pending-Questions section below. We can now resume the brainstorm with the full picture in hand.
Pending questions to ask user once screenshots are done
- What other Transfer Type values exist? (Hold / Scrap / Rework / Return / Splits?)
- Does Billed Labor show up on every screen or only certain ones?
- Are From Station / To Station always editable, or read-only when single-station nodes?
- What does the camera icon save against — the part record, the transition log, or the job?
- Is "Number of Customer WOs" a free-text count or a multi-select of existing customer POs?
- Is there a way to split a move (e.g. send 40 to next step, hold 9)? Steelhead's Part Count selector hints at it.