Files
Odoo-Modules/fusion_plating/docs/superpowers/specs/2026-04-27-simple-recipe-editor-steelhead-screens.md
gsinghpal 4e4ca2c9da docs(sub12): simple recipe editor + library + tablet move/rack + reports
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>
2026-04-27 19:23:25 -04:00

54 KiB
Raw Permalink Blame History

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

  1. 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.input is for.

  2. 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).

  3. Camera icon next to To Location = inline photo capture, attached to the transition log. Implies the runtime needs a photo input type that can either upload a file or trigger device camera (mobile / tablet).

  4. 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).

  5. 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 78)

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 48

  1. 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.
  2. Move button label reflects qty: MOVE (49) vs MOVE (1). Operator confidence cue — they see exactly how many parts they're committing to move before tapping.

  3. 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 913

  1. 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.timer with states: running / paused / stopped / reconciled.

  2. 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_id on the move-controller decision.

  3. 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 on fp.rack directly when we build that registry.

  4. 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.

  5. "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_ids M2M 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 1415

  1. 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.

  2. 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.
  3. Spec measurements vs operation measurements vs transition inputs: three distinct concepts now visible:

    • Operation measurements (input_template_ids on fp.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.

  4. 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 / FAIL selection.
  • Inspection (thickness measurement): Actual thickness (mils) + PASS / FAIL.
  • Water break test: PASS / FAIL selection.
  • 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:

  1. Page 1: header + Item Information block + Process-Sheet header + first 68 steps.
  2. Pages 2..N: continuation rows for the remaining steps.
  3. 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 1618

  1. 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 a fp.step.template.input definition.
  2. 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.

  3. 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) on process.node — needs to extend to min/max per step.

  4. 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.spec model — needs to be pulled onto the traveller print.

  5. 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 1922 — 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:

  1. Ready for Incoming Inspection — Moved By: Riya Bhatt, Time: Sep 18, 2025 07:22:31 AM
  2. Ready For Plating — Moved By: Kris Pathinather, Time: Sep 18, 2025 07:25:00 AM
  3. 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 45 — 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)

  1. 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.

  2. 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.
  3. 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.

  4. 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).

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. Footer branding — "Nadcap Accredited (Administered by PRI)" logo + ENTECH company logo. Per-page. Page numbering "n/total". Plus "Cert Created At: ". Our existing fp.certificate PDF 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 1922

  1. 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.
  2. 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).

  3. 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 description on the step template — we just render it once at the input table header instead of per-row. Saves report space.

  4. Inputs can be multi-valued per step — ElectroClean has 4 inputs, E-Nickel Plate has 3, Soak Clean has 2. Our input_template_ids One2many already handles this. Confirmed by real data.

  5. "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.

  6. Move-By trail is the operator chain-of-custody. CRITICAL for aerospace / Nadcap. Our existing fp.job.step.signoff_user_id

    • date_finished already 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 Accepted integer + Outgoing Part Count Verified boolean).
  • "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.

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 2324

  1. Final inspection captures the disposition split — Qty Accepted / Qty Rejected as integer fields at the last QC step. These should appear as input_template_ids on a "Final Inspection" library step (sane defaults). Plus a redundant "final coating thickness" reading. We model these as standard operation-measurement inputs.

  2. 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.

  3. 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.