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>
53 KiB
Sub 12 — Simple Recipe Editor + Step Library + Tablet Move/Rack + Reports
Status: design ready for implementation planning Date: 2026-04-27 Sliced into 3 sequential sub-projects: 12a → 12b → 12c Companion file: Steelhead screen inventory — 24 screenshots logged with field-by-field notes that drove these decisions.
1. Why this sub-project exists
Two facts forced the design:
-
The customer has two operator personas with opposite preferences. "Tree-loving" engineers like our existing OWL tree editor (
fusion.plating.process.node, hierarchical recipe → sub-process → operation → step, drag-drop tree). "Simple-loving" foremen find the tree intimidating and want a flat ordered drag-drop list with a step library on the side. Forcing one persona to use the other's tool blocks adoption. -
The customer is migrating off Steelhead and brought 24 screenshots showing what their team is used to: a flat step library + a 2-column drag-drop recipe builder + Move-Parts / Move-Rack / Stop-Timer dialogs at the tablet + a chronological CoC traveller PDF. Their authoring UX is simpler than ours; their runtime UX is roughly equivalent but has gaps we can improve on (every blocker should have an inline resolution button).
Plus: the existing tree editor + 205+ live fp.job records + 1800+ fp.job.step records + the entire shopfloor runtime + the S14 predecessor lock + the S19 Fischerscope merge + the Sub 11 MRP cutout — all already shipped, all working — must keep working unchanged.
The design satisfies both personas without forking the data model. Same recipe data, two editor views, same tablet runtime + reports.
2. Locked decisions (Q1–Q8 from the brainstorming session)
| Q | Decision |
|---|---|
| Q1 — Editor strategy | Hybrid. Keep the existing OWL tree editor, build the simple editor alongside. Both edit the same recipes. |
| Q2 — Data model fork | No fork. Both editors operate on the same fusion.plating.process.node records. Edits in either editor update the same recipe. |
| Q3 — Step library | New model fp.step.template as a dedicated reusable step library. Surfaced under Plating → Configuration → Step Library. |
| Q4 — Library import semantics | Snapshot copy. Dragging a library step into a recipe creates a fresh fusion.plating.process.node with the template's fields copied in. Editing the library template later does not mutate recipes already built. source_template_id carries the trace. |
| Q5 — Recipe templates ("starter recipes") | is_template Boolean on the existing recipe node. A recipe flagged as a template appears in the simple editor's "Import starter from template" dropdown. Importing snapshot-copies all child nodes. |
| Q6 — What's on a step template | Mirror Steelhead screen 1 + Advanced expander with high-value extras. Visible by default: Title, Stations, Operation Measurements, Instructions, Require QA. Advanced: icon, time/temp targets, voltage, viscosity, material callout, predecessor lock, rack/transition flags, transition inputs. |
| Q7 — Editor toggle | Per-recipe preferred_editor (tree/simple/auto) + company-level default_recipe_editor setting + header buttons "Open in Tree Editor" / "Open in Simple Editor". Authoring lead can build in tree, hand off to a foreman who edits in simple. |
| Q8 — Slicing strategy | Three sequential sub-projects (12a → 12b → 12c). Each independently shippable, each closes with a smoke test on entech. |
3. Architecture overview
┌────────────────────┐
Tree Editor (existing OWL) ─────reads/writes──▶│ │
│ Recipe data: │
Simple Editor (12a, new OWL) ─────reads/writes──▶│ fusion.plating. │
│ process.node │
Step Library (12a, new model) ─────snapshots─────▶│ (hierarchical, │
│ _parent_store) │
│ │
Recipe Template (12a, is_template)─────snapshots─────▶│ + new fields: │
│ is_template │
│ source_template_id│
│ tank_ids │
│ target ranges, │
│ units, kind, │
│ transition flags │
└─────────┬──────────┘
│
▼
Same job-creation flow
(no runtime change)
│
▼
┌────────────────────┐
Tablet (existing OWL) ─────operates on───▶│ fp.job.step │
+ Move Parts / Move Rack (12b) │ + new fields: │
+ Rack Parts sub-dialog (12b) │ current_rack_id │
+ Stop Timer dialog (12b) │ is_racked │
+ Soft/Hard block UX (12b) │ qty_at_step_* │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ fp.job.step.move │
│ (12b, NEW) │
│ fp.rack (12b, NEW)│
│ fp.labor.timer │
│ (12b, NEW) │
└─────────┬──────────┘
│
▼
Operator Traveller PDF (12c) ◀─renders from ──── recipe-order step list
Customer CoC PDF (12c) ◀─renders from ──── chronological move log
Labor History screen (12c) ◀─lists ─────────── fp.labor.timer
Hard rule preserved: every change is additive at the data layer. No FK drops, no model deletions, no ACL relaxations. Tree editor + every existing battle-test scenario + the Sub 11 MRP cutout + the Sub 12 (Quality / RMA) work all keep working unchanged.
4. Sub 12a — Simple Recipe Editor + Step Library
4.1 Scope
Recipe authoring only. No runtime/tablet/report changes. Estimated 4 days.
Customer outcome: authors can build / edit / clone recipes via a flat drag-drop editor with a step library on the side. They can flag any recipe as a starter template and import its full step list into a new recipe (snapshot copy). Tree editor untouched.
4.2 Data model
New model: fp.step.template (the reusable step library)
| Field | Type | Notes |
|---|---|---|
name |
Char, required, translate | "Solvent Clean" |
code |
Char | optional short code, auto-uppercased |
description |
Html | rich-text instructions / WI reference |
icon |
Selection | reuses the 24-icon list from fusion.plating.process.node |
tank_ids |
M2M fusion.plating.tank |
allowed stations ("Stations" column in screen 1) |
process_type_id |
M2O fusion.plating.process.type |
bath / chemistry tie |
material_callout |
Char | "MID PHOS" — short string for traveller print; defaults to process_type_id.name |
time_min_target |
Float | lower bound (time_unit-aware) |
time_max_target |
Float | upper bound |
time_unit |
Selection (sec / min / hr) |
default min |
temp_min_target |
Float | lower bound (temp_unit-aware) |
temp_max_target |
Float | upper bound |
temp_unit |
Selection (F / C) |
default F |
voltage_target |
Float (optional) | electrolytic |
viscosity_target |
Float (optional) | bath quality |
requires_signoff |
Boolean | "Require QA" |
requires_predecessor_done |
Boolean | S14 lock support |
requires_rack_assignment |
Boolean | step-type flag → triggers Rack Parts sub-dialog at runtime |
requires_transition_form |
Boolean | step-type flag → opens transition form before Mark Done |
input_template_ids |
O2M fp.step.template.input |
operation measurements |
transition_input_ids |
O2M fp.step.template.transition.input |
compliance fields collected at move-time |
default_kind |
Selection | cleaning / etch / rinse / plate / bake / inspect / racking / derack / mask / demask / dry / wbf_test / final_inspect / ship / gating — drives sane-defaults seeding |
active, sequence, company_id |
(standard) |
New model: fp.step.template.input — operation measurements (recorded during a step)
| Field | Type | Notes |
|---|---|---|
name |
Char, required | e.g. "Soak Clean Time" |
template_id |
M2O fp.step.template |
parent |
input_type |
Selection | text / number / boolean / selection / date / signature / time_hms / time_seconds / temperature / thickness / pass_fail (typed inputs auto-format on the report) |
target_min |
Float | structured target lower bound |
target_max |
Float | structured target upper bound |
target_unit |
Char | "min" / "ºF" / "A" / "FT2" / "in" |
required |
Boolean | hard-block sign-off if blank |
hint |
Char | inline help |
selection_options |
Text | comma-separated when input_type='selection' |
sequence |
Integer | render order |
New model: fp.step.template.transition.input — compliance fields collected when leaving a step
| Field | Type | Notes |
|---|---|---|
name |
Char, required | "Customer WO #" / "Photo Evidence" / "Scrap Reason" |
template_id |
M2O fp.step.template |
parent |
input_type |
Selection | text / number / boolean / selection / date / signature / photo / location_picker / customer_wo |
required |
Boolean | hard-block move if blank |
hint |
Char | |
selection_options |
Text | |
sequence |
Integer | |
compliance_tag |
Selection | none / as9100 / nadcap / cgp / nuclear — drives audit report filter |
Changes to existing fusion.plating.process.node (all additive — zero impact on tree editor / runtime)
| Field | Type | Notes |
|---|---|---|
is_template |
Boolean, default False | marks a recipe (when node_type='recipe') as a starter template |
source_template_id |
M2O fp.step.template, optional, indexed |
snapshot trace; set when a node is created by dragging a library step in |
tank_ids |
M2M fusion.plating.tank |
mirrors what library steps carry |
material_callout |
Char | mirrors library |
time_min_target, time_max_target, time_unit |
(mirrors library) | |
temp_min_target, temp_max_target, temp_unit |
(mirrors library) | |
voltage_target, viscosity_target |
(mirrors library) | |
requires_rack_assignment, requires_transition_form |
Boolean | mirrors library |
default_kind |
Selection | mirrors library; auto-set when imported |
transition_input_ids |
O2M to existing fusion.plating.process.node.input (filtered by new kind field) |
one model, two roles |
preferred_editor |
Selection (tree / simple / auto) |
per-recipe editor choice (only meaningful when node_type='recipe') |
Changes to fusion.plating.process.node.input:
- Add
kindSelection (step_input/transition_input), defaultstep_input(existing rows backfill via post_init_hook). - Add the same target-range fields as
fp.step.template.input.
New: res.config.settings.default_recipe_editor — Selection (tree / simple), default tree. Drives "New Recipe" button's editor choice.
4.3 Simple Recipe Editor UI (OWL client action)
Registered as fp_simple_recipe_editor in web.client_actions. Single-page, full-screen, tablet-friendly.
┌──────────────────────────────────────────────────────────────────────┐
│ ← Back Recipe: ENP-ALUM-BASIC [Tree Editor] [Save] [More ▾]│
├──────────────────────────────────────────────────────────────────────┤
│ Title [ ENP-ALUM-BASIC ] │
│ Code [ ENP_ALUM ] │
│ Coating Config [ Electroless Nickel Mid-Phos ▾ ] │
│ Part [ — none — ▾ ] │
│ ☐ Use as starter template │
│ │
│ Import starter from template: [ Select template ▾ ] [Import] │
├──────────────────────────────────────────────────────────────────────┤
│ ┌─ Selected (drag to reorder) ─────────┐ ┌─ Step Library ──────────┐ │
│ │ ⠿ 1. Part Verification [Edit ▾] │ │ 🔍 [Search…] │ │
│ │ ⠿ 2. Solvent Clean SP-1 [Edit ▾] │ │ Acid Dip │ │
│ │ ⠿ 3. Soak Clean SP-1 [Edit ▾] │ │ Bake │ │
│ │ ⠿ 4. Rinse SP-2 [Edit ▾] │ │ E-Nickel Plate │ │
│ │ ... │ │ ... │ │
│ │ [+ Add Inline Step] │ │ [+ New Library Step] │ │
│ └──────────────────────────────────────┘ └──────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
Behavior:
- Drag from Library → Selected: snapshot-copies the library step into the recipe as a new
fusion.plating.process.node(node_type=step,source_template_idset, all author-defined fields copied, sane-defaultinput_template_idscopied, alltransition_input_idscopied). - Drag within Selected: reorders by updating
sequence. - Drag out / X button: removes the step from the recipe (unlinks the node).
- Station picker per Selected row: dropdown of the step's
tank_idsM2M. - Edit ▾ per row: expands inline panel with Title, Stations, Operation Measurements, Instructions, Require QA + an Advanced expander (icon, time/temp targets, voltage, viscosity, material callout, requires_predecessor_done, requires_rack_assignment, requires_transition_form, transition inputs).
- + Add Inline Step: creates a one-off step in the recipe without touching the library.
- + New Library Step: side panel to author a new
fp.step.template. - Import starter from template: dropdown of
fusion.plating.process.nodewhereis_template=True AND node_type='recipe'. Snapshot-copies all child steps preservingsequence. Confirms before replacing existing steps. - [Tree Editor] button: switches to the existing tree editor on the same recipe.
- Auto-save every 5s when dirty + explicit Save.
- Mobile/tablet responsive: <900px width stacks columns.
Search/filter: case-insensitive substring match against name + code + description (plain-text).
Drag-drop: HTML5 native dragstart/dragend, reuses helpers from existing tree editor.
Soft-validation for predecessor lock: a Selected row with requires_predecessor_done=True placed before its predecessor highlights amber with a tooltip. Doesn't block save (S14 enforces at runtime), informs the author.
4.4 Backend controller endpoints
New file: fusion_plating/controllers/simple_recipe_controller.py. JSONRPC routes:
| Route | Purpose |
|---|---|
POST /fp/simple_recipe/load |
recipe header + ordered step list |
POST /fp/simple_recipe/library/list |
all fp.step.template (search-filtered, company-scoped) |
POST /fp/simple_recipe/library/create |
new template |
POST /fp/simple_recipe/library/write |
update |
POST /fp/simple_recipe/library/delete |
unlink (soft if any node references it via source_template_id, hard otherwise) |
POST /fp/simple_recipe/step/insert |
insert (from library or blank) at position N |
POST /fp/simple_recipe/step/write |
inline edit |
POST /fp/simple_recipe/step/remove |
unlink from recipe |
POST /fp/simple_recipe/step/reorder |
bulk sequence update after drag-drop |
POST /fp/simple_recipe/template/list |
recipes where is_template=True (for Import dropdown) |
POST /fp/simple_recipe/template/import |
snapshot-copy all child nodes of a template recipe |
All endpoints honor company multi-tenancy + ACL (group_fusion_plating_supervisor for write, _operator for read).
4.5 Recipe form integration
On fusion.plating.process.node form view (when node_type='recipe'):
- Header buttons: Open in Simple Editor + Open in Tree Editor.
- New "Editor Preference" Selection field (
tree/simple/auto). - Clicking a recipe in the menu list routes through
preferred_editor(falls back to companydefault_recipe_editorifauto). - "Use as starter template" checkbox surfaces
is_template(visible to supervisors only).
Menu integration:
- Plating → Operations → Process Recipes — existing list view; clicks route through preferred editor.
- Plating → Configuration → Step Library — NEW; CRUD on
fp.step.template.
4.6 Sane-default input seeding per default_kind
default_kind |
Suggested input_template_ids |
|---|---|
cleaning |
Actual Time (time_seconds, "sec") + Actual Temperature (temperature, "°F") |
etch |
Actual Time + Actual Temperature |
rinse |
(none — sign-off only) |
plate |
Actual Time (time_hms, "min") + Actual Temperature + Plating Thickness (thickness, "in") |
bake |
Time In (text "HH:MM") + Time Out (text "HH:MM") + Actual Temperature |
racking |
Actual Qty (number, "each") |
derack |
Actual Qty |
inspect |
PASS/FAIL (pass_fail) |
final_inspect |
Outgoing Part Count Verified (boolean) + Qty Accepted (number, "each") + Qty Rejected (number, "each") + Actual Coating Thickness (thickness, "in") + Pass/Fail |
wbf_test |
PASS/FAIL |
mask |
Actual Qty |
demask |
(none) |
dry |
(none) |
ship |
Outgoing Qty (number, "each") |
gating |
(none) |
Implemented via server method fp.step.template._seed_default_inputs(self), idempotent, exposed as a "Seed Defaults" button on the library form.
4.7 Migration / install
Module: extend fusion_plating core (no new module). Bump to 19.0.10.0.0.
post_init_hook for 12a:
- Backfills
kind='step_input'on all existingfusion.plating.process.node.inputrows. - Seeds
fp.step.templatewith 18 starter templates copied from the existingENP-ALUM-BASICrecipe's child steps (Soak Clean, Rinse, Etch, Desmut, Zincate, Strip Zincate, Electroclean, Acid Dip, Water Break Test, Issue Panels, Racking, E-Nickel Plate, Hot Rinse, Drying, De-rack, Inspection, Final Inspection, Shipping). Each getsdefault_kindset + sane-default inputs seeded. Idempotent — won't re-seed if anyfp.step.templaterows already exist.
4.8 Verification (smoke test on entech staging)
- Install module → step library auto-seeds 18 templates.
- Plating → Configuration → Step Library — confirm 18 entries with sane-default inputs.
- Plating → Operations → Process Recipes → New Recipe → Simple Editor.
- Drag 5 library steps in, reorder via drag-drop, pick stations.
- Save, close, reopen — data persists, sequence correct.
- Click [Tree Editor] — same recipe opens in tree editor with all 5 steps under root. Edit step 3's name in tree editor, save, return to Simple Editor — change visible.
- Mark a recipe as template, build a new recipe, "Import starter from template" — all steps copy in, snapshot.
- Edit the original library step's name → previously-imported recipe steps DO NOT change (snapshot decoupling).
- Run
bt_s2_*battle tests on a job built from a Simple-Editor recipe — confirm runtime unaffected.
5. Sub 12b — Move Parts / Move Rack Dialogs + Tablet Transition Capture
5.1 Scope
Tablet UX + transition-time data capture. Uses 12a's authored data; no new recipe-authoring UI. Estimated 3–4 days.
Customer outcome: operators on the tablet get the Steelhead-style Move Parts / Move Rack flow with author-defined compliance prompts, station picker, photo evidence, and the soft/hard block UX — but with our improvement: every blocker has a clickable resolution button.
5.2 Data model
New: fp.rack — physical rack registry
| Field | Type | Notes |
|---|---|---|
name |
Char, required | "Rack 3" |
code |
Char, required | "R-03" |
qr_code |
Char | scannable; defaults to FP-RACK:<code> |
state |
Selection | empty / loading / loaded / in_use / awaiting_unrack / out_of_service |
current_part_count |
Integer (compute) | sum of part-batches currently on rack |
current_job_step_id |
M2O fp.job.step (compute) |
current location |
current_tank_id |
M2O fusion.plating.tank (compute) |
derived from current step |
tag_ids |
M2M fp.rack.tag |
rack labels |
facility_id, work_center_id |
M2O | location |
material |
Selection (steel / titanium / polypro / pvc / plastic / other) |
construction |
capacity_count |
Integer | max parts (soft warn) |
notes |
Text | maintenance / damage notes |
active, company_id |
(standard) |
New: fp.rack.tag — rack labels (M2M tag registry)
| Field | Type | Notes |
|---|---|---|
name |
Char, required | "Rush" / "Customer-Amphenol" / "Hold-for-QC" |
color |
Integer | kanban color |
New: fp.job.step.move — chain-of-custody transition log (one row per move)
| Field | Type | Notes |
|---|---|---|
name |
Char, sequence FP/MOVE/YYYY/NNNN |
|
job_id |
M2O fp.job |
|
from_step_id |
M2O fp.job.step |
source |
to_step_id |
M2O fp.job.step |
destination |
from_tank_id |
M2O fusion.plating.tank |
derived |
to_tank_id |
M2O fusion.plating.tank |
operator's choice on multi-station node |
transfer_type |
Selection | step / hold / scrap / rework / split / return |
qty_moved |
Integer | partial-qty supported |
qty_available_at_move |
Integer | snapshot |
to_location |
Selection (global / quarantine / staging_a / staging_b / shipping_dock / scrap_bin) |
|
photo_evidence_id |
M2O ir.attachment |
inline-captured photo |
customer_wo_count |
Integer | optional |
rack_id |
M2O fp.rack |
populated on rack-aware moves |
unrack_after_move |
Boolean | for derack steps |
moved_by_user_id |
M2O res.users |
|
move_datetime |
Datetime | |
transition_input_value_ids |
O2M fp.job.step.move.input.value |
compliance values captured |
chatter |
mail.thread | yes |
New: fp.job.step.move.input.value — recorded transition-input values
| Field | Type | Notes |
|---|---|---|
move_id |
M2O fp.job.step.move |
parent |
template_input_id |
M2O fp.step.template.transition.input |
what was asked (template-level) |
node_input_id |
M2O fusion.plating.process.node.input |
snapshot of the authored prompt at job-creation time |
value_text |
Char | for text/selection |
value_number |
Float | for number |
value_boolean |
Boolean | for boolean |
value_date |
Datetime | for date |
value_attachment_id |
M2O ir.attachment |
for photo/signature |
New: fp.labor.timer — persistent labor timer (lifted from screens 9, 10)
| Field | Type | Notes |
|---|---|---|
name |
Char, sequence FP/TIMER/YYYY/NNNN |
|
user_id |
M2O res.users |
operator |
job_id |
M2O fp.job |
|
step_id |
M2O fp.job.step |
step at start |
state |
Selection | running / paused / stopped / reconciled |
started_at, last_paused_at, stopped_at |
Datetime | |
total_paused_duration |
Float (compute) | sum of pauses |
accrued_seconds |
Integer (compute) | live for running, frozen otherwise |
billed_hrs / billed_min / billed_sec |
Integer | reconciled, default = accrued, editable on stop |
billed_pct |
Float (compute) | billed / accrued |
product_id |
M2O product.product |
optional split-target product |
notes |
Text | |
chatter |
mail.thread | yes |
Lifecycle: running → paused → running → stopped → reconciled. Stop Timer dialog (screen 10) opens on stop and lets the operator reconcile billed hrs/min/sec + optional product split (creating sibling timer rows).
Changes to existing fp.job.step:
| Field | Type | Notes |
|---|---|---|
move_ids |
O2M fp.job.step.move (inverse from_step_id) |
history of moves out of this step |
current_rack_id |
M2O fp.rack |
snapshot of rack on this step (when racked) |
is_racked |
Boolean (compute, stored) | current_rack_id != False |
qty_at_step_start |
Integer | sum of incoming move qty |
qty_at_step_finish |
Integer | sum of outgoing move qty |
Changes to fp.job:
| Field | Type | Notes |
|---|---|---|
qty_received |
Integer | from screen 16 traveller header |
qty_visual_inspection_rejects |
Integer | |
qty_rework |
Integer | |
special_requirements |
Text | from customer spec |
active_timer_ids |
O2M fp.labor.timer (inverse job_id, filtered by state) |
for live displays |
5.3 Move Parts dialog
Trigger: operator taps Move Parts on a part-batch row in the tablet, OR scans a part QR while at a step.
┌────────────────────────────────────────────────────────┐
│ Move Parts │
├────────────────────────────────────────────────────────┤
│ Part Count [ 49 ] Available: 49 │
│ Part Number TEST225451 (link) │
│ From Node Bake (link) │
│ From Station Bake (link) ← shown if applicable│
│ Transfer Type [ Step ▾ ] │
│ To Node Adhesion Testing (link) │
│ To Station [ Adhesion Testing ▾ ] ← shown if multi │
│ To Location [ Global ▾ ] 📷 ← inline camera │
│ │
│ ── Compliance Prompts (author-defined) ── │
│ • Customer WO # [ 731830 ] │
│ • Photo Evidence [ Attach 📷 ] │
│ • Scrap Reason [ none ▾ ] │
│ │
│ ─── Blockers ────────────────────────────────── │
│ ⚠ Additional Spec Measurements required. │
│ [ RECORD MEASUREMENTS ] ← OUR IMPROVEMENT │
│ │
│ Billed Labor ⏱ [Reset All Edits] │
│ Kris Pathinather Timer: 7s (100% billed) │
│ WO #4521 PN: TEST225451 Qty: 49 0 hrs 0 min 7 sec │
│ │
│ [ Cancel ] [ MOVE (49) ] │
└────────────────────────────────────────────────────────┘
Behavior:
- System-derived top section: Part Count (editable, max=available), Part Number, From Node, From Station (only if source step has a tank), Transfer Type, To Node, To Station (only if destination step's
tank_idscount > 1), To Location. - Camera button next to To Location: launches
getUserMediaon tablet → captures photo → uploads asir.attachment→ links to the move. - Compliance Prompts section: renders the destination step's
transition_input_ids(snapshot from authored template).required=Trueprompts hard-block MOVE. - Blockers section (NEW pattern, our improvement over Steelhead): a list of resolvable issues, each with an inline action button:
- "Spec measurements required" → opens spec input dialog inline → re-evaluates → clears.
- "Parts not racked" → opens Rack Parts sub-dialog (5.4).
- "Predecessor not done" (S14) → opens predecessor step's checklist.
- "Operation measurements missing" → opens operation input form on source step.
- MOVE button state: enabled only when all hard-blockers cleared + all required transition inputs filled. Disabled state shows tooltip listing blockers (improvement over Steelhead's silent disabled state).
- Billed Labor section: surfaces the operator's active
fp.labor.timerfor this WO with editable hrs/min/sec. - MOVE click: creates
fp.job.step.move, copies transition-input values, advances the part-batch, stops the active timer, advancesqty_doneon source step +qty_at_step_starton dest step.
5.4 Rack Parts sub-dialog
Trigger: from Move Parts when destination has requires_rack_assignment=True and operator hasn't picked a rack → "RACK PARTS" button.
┌────────────────────────────────────────────┐
│ Rack Parts [QR scan]│
├────────────────────────────────────────────┤
│ To Rack [ Search Racks… ▾ ] │
│ │
│ Part Number Unit Amount │
│ TEST225451 [ Count ▾] [ 49 ] Count │
│ on WO 4521 (49) │
│ │
│ Billed Labor ⏱ │
│ │
│ [Cancel] [Save] [Save + Print] │
└────────────────────────────────────────────┘
- Rack picker filters to
state='empty'by default; "show all" toggle bypasses. - QR scan button: tablet camera + parses
FP-RACK:<code>→ auto-fills "To Rack". - Save: marks rack
state='loaded', setscurrent_job_step_id, returns to Move Parts dialog with rack populated, blocker cleared. - Save + Print: same + prints rack travel ticket.
- Unit + Amount: defaults
Count+ Move Parts' Part Count. Editable for partial racking.
5.5 Move Rack dialog
Trigger: operator taps MOVE RACK on a rack row in the tablet.
┌────────────────────────────────────────────────────────┐
│ Move Rack: tyut │
├────────────────────────────────────────────────────────┤
│ Rack Labels [ Rush ✕ ] [ + ] │
│ Parts │
│ • 49 TEST225451 Parts on WO 4521 │
│ • 1 TEST225451 Parts on WO 4521 │
│ │
│ Type [ Step ▾ ] │
│ To Node [ Soak Clean (SP-1) ▾ greyed ] │
│ To Station [ Soak Clean (SP-1) ▾ ] │
│ │
│ Billed Labor ⏱ [Reset All Edits] │
│ Kris Pathinather Timer: 6s (100% billed) │
│ WO #4521 PN: TEST225451 Qty: 49 0 hrs 0 min 6 sec │
│ WO #4521 PN: TEST225451 Qty: 1 0 hrs 0 min 0 sec │
│ │
│ [Cancel] [Save] │
└────────────────────────────────────────────────────────┘
- Rack name in title from
fp.rack.name. - Parts list read-only.
- To Node auto-derived (greyed); To Station shown only if multi-station.
- Per-batch billed-labor split for each batch.
- Save atomically creates one
fp.job.step.moveper batch, all linked by the samerack_id.
5.6 Stop User Labor Timer dialog
Trigger: operator taps the timer pause icon on the tablet without moving parts.
Mirror screen 10. Reconcile billed hrs/min/sec + optional product split. Footer: Cancel / Save / Save & Start New Timer.
5.7 Tablet runtime guards
Per-batch row in tablet station view:
current_rack_idset → MOVE PARTS button disabled / greyed with tooltip "Racked — use Move Rack instead."current_rack_idempty → MOVE PARTS enabled.
Plant overview gets two panes (mirror screen 12):
- Top: Racks with
MOVE RACKper row + bulk UNRACK MULTIPLE. - Bottom: Parts with
MOVE PARTSper row (greyed when racked) + + ADD NEW PARTS + filters + search.
Soft/hard block protocol (our protocol — improves on Steelhead):
- Amber banner + button enabled = soft (proceed with audit).
- Amber banner + button disabled + tooltip listing blockers = hard.
- Every blocker carries an inline resolution button that opens a form to resolve without leaving the dialog.
- Dialog re-evaluates blockers reactively after each resolution.
5.8 Backend controller endpoints
Extend fusion_plating_jobs/controllers/tablet_controller.py:
| Route | Purpose |
|---|---|
POST /fp/tablet/move_parts/preview |
dialog payload (system fields + author prompts + blockers) |
POST /fp/tablet/move_parts/commit |
creates fp.job.step.move, advances qty, handles timer |
POST /fp/tablet/move_rack/preview |
multi-batch dialog payload |
POST /fp/tablet/move_rack/commit |
atomic multi-batch move tied to a rack |
POST /fp/tablet/rack_parts/commit |
assigns parts to a rack |
POST /fp/tablet/rack_parts/print |
prints rack travel ticket |
POST /fp/tablet/labor_timer/start |
start |
POST /fp/tablet/labor_timer/pause |
pause |
POST /fp/tablet/labor_timer/resume |
resume |
POST /fp/tablet/labor_timer/stop |
stop and open reconciliation |
POST /fp/tablet/labor_timer/reconcile |
save reconciled billed time + optional product split |
ACL: group_fusion_plating_operator minimum.
5.9 Migration / install
Same module: extend fusion_plating core. Bump to 19.0.10.1.0.
post_init_hook for 12b:
- Seeds
fp.rack.tagwith 4 starter tags: "Rush", "Hold for QC", "Damaged", "Customer Sample". - Backfills
fp.job.step.qty_at_step_startfrom existingqty_donechain (idempotent).
No data destruction. No FK drops.
5.10 Verification (smoke test on entech staging)
- Open tablet, scan a part QR → tablet shows the part-batch row at its current step.
- Tap
Move Parts→ dialog opens with system fields populated, dest step's authored transition prompts rendered. - Try MOVE with required prompt blank → button disabled, tooltip lists the blank prompt.
- Fill prompt → MOVE re-enables.
- Move to a step with
requires_rack_assignment=True→ amber blocker + RACK PARTS button. - Click RACK PARTS → sub-dialog → pick rack → save → blocker clears, MOVE re-enables.
- Complete MOVE →
fp.job.step.moverow created, part-batch advanced, rack state updated. - Confirm tablet now shows MOVE PARTS button greyed out + MOVE RACK shown on the rack row.
- Tap MOVE RACK → dialog with all batches → save → all advance atomically.
- Tap timer pause → Stop Timer dialog → reconcile billed time → save → state moves to
reconciled. - Run
bt_s1_*throughbt_s17_*battle tests — confirm no regressions on existing flows.
6. Sub 12c — Reports + Persistent Labor Audit
6.1 Scope
Two PDF templates + a labor history screen. No new model surface — uses 12a + 12b data. Estimated 3–4 days.
Customer outcome:
- Operator Traveller PDF: recipe-order, paper-style A4 landscape. Equivalent to Amphenol paper sheets (screens 16–18).
- Customer CoC Traveller PDF: chronological audit, branded, Nadcap stamped. Equivalent to Steelhead's CoC output (screens 19–24).
- Labor History screen: surfaces
fp.labor.timerfor billing audit, payroll reconciliation, and "who-was-on-what-when" forensics.
6.2 Operator Traveller Report
File: fusion_plating_jobs/report/report_fp_job_traveller_v2.xml
Layout: A4 landscape, multi-page, table-driven.
Replaces: existing report_fp_job_traveller.xml (S5/S18 minimal portrait — kept as fallback).
Page structure mirrors Amphenol paper sheets:
- Header (every page): logo, WO# + barcode (Code 128), Date In, Due Date, Type, Order #, P.O. #, Customer + address.
- Item Information (page 1): Part #, Rev., Mat., Catg., S/N, Item-Name / Process Description, Qty Rec., Vis Insp, Rework, Special Requirements, Stamp/Date.
- Process-Sheet Header (page 1): recipe name, sub-process name, category, special req.
- Step Table (continues page-to-page): Step | Tank | Operation + Actual | Instruction | Unit | Material | Voltage | Viscosity | Time(min) | Temp | Stamp | Date.
- Footer (every page): WO# + Page n of total.
Data source: walks fp.job.step ordered by sequence (recipe order). Each row pulls from the step's authored fields (target ranges, units, material callout) + leaves blank lines for the operator to pencil in actuals.
ir.actions.report: action_report_fp_job_traveller_v2, paperformat A4 landscape. Smart button on fp.job form: "Print Operator Traveller".
6.3 Customer CoC Traveller Report
File: fusion_plating_certificates/report/report_fp_certificate_coc_v2.xml
Layout: A4 portrait, multi-page, table-driven.
Extends: existing fp.certificate flow (S18/S19) — replaces minimal CoC body with chronological audit body.
Page structure mirrors Steelhead's CoC (screens 19–24):
- Header (page 1): Part Number, Description, Quantity, WO#, PO#, Packing List, Date, Specification(s).
- Body: chronological list of step transitions, each rendered as:
- Step heading:
<step.name> (<tank.code>). - "Part Number / Moved By / Time" line.
- If the step has captured input values, a 5-column table: Name | Description | Target | Actual | Recorded By.
- Step heading:
- Last page: 2-column sign-off block (left = signed image + name, right = cert statement + comments). Footer: Nadcap logo + ENTECH logo + "Cert Created At: " + page n/total.
Critical improvements over Steelhead:
- Target column is its own column (Steelhead embeds "(5-10 min.)" in the input name).
- Out-of-range Actual values colour-coded red, in-range green.
- Heading-only steps (Ready For X, gating) render as compact 1-line transitions — no empty tables.
- Multi-day jobs: each transition carries its own datetime; the report walks chronologically.
- Signature image from
fp.certificate.signoff_user_id.x_fc_signature. - Configurable per-company: Nadcap logo + brand logo as
res.company.x_fc_nadcap_logo+x_fc_company_brand_logo(fall back to Fusion Plating logo).
Data source: walks fp.job.step.move records ordered by move_datetime, NOT fp.job.step records ordered by sequence. Chain-of-custody view auditors expect.
For each move:
- Render heading:
<from_step.name> (<from_tank.code>)or<to_step.name> (<to_tank.code>). - Render "Part Number / Moved By / Time".
- If destination step had
input_template_idsfilled at move time, render the measurement table.
ir.actions.report: action_report_fp_certificate_coc_v2, paperformat A4 portrait. Smart button on fp.certificate form: "Generate Customer CoC PDF".
Existing CoC merge with Fischerscope thickness PDF (S19) is preserved — _fp_render_and_attach_pdf keeps appending the Fischerscope page.
6.4 Labor History Screen
Menu: Plating → Operations → Labor History.
View: list view on fp.labor.timer, grouped by user_id then job_id.
Columns: Operator | Job | Step | State (badge) | Started | Stopped | Accrued (HH:MM:SS) | Billed | Billed % (bar) | Product.
Filters: My timers / Today / This week / Reconciled / Pending reconciliation / By operator / By customer.
Group-by: Operator / Job / Customer / Date.
Form view: read-only timer history with chatter for operator notes. Manager-only fields:
billed_hrs / billed_min / billed_seceditable (audit-logged).- "Re-open for re-reconciliation" button — moves a
reconciledtimer back tostopped.
ACL:
- Operator: read own timers, write own running/paused/stopped timers.
- Supervisor: read all team timers, write reconciliations.
- Manager: full edit including re-open.
6.5 Backend support
Extend fusion_plating_certificates/models/fp_certificate.py:
- New method
_fp_build_chronological_payload(self)returns the ordered list of moves + measurement values for the QWeb template. - Existing
_fp_render_and_attach_pdfcalls it and assembles the multi-page PDF. - Existing Fischerscope merge logic (S19) untouched.
Extend fusion_plating_jobs/models/fp_job.py:
- Property
traveller_v2_step_payloadreturns ordered step list with target ranges + author-defined inputs for the operator traveller QWeb template.
No new endpoints.
6.6 Migration / install
Bumps:
fusion_plating→19.0.10.2.0fusion_plating_jobs→19.0.7.0.0fusion_plating_certificates→19.0.6.0.0
post_init_hook for 12c:
- Creates
paperformat.fp_a4_landscape_traveller. - Sets
fp.certificate.report_template_id = action_report_fp_certificate_coc_v2so existing certs auto-use the new layout. Oldreport_fp_certificate_coc.xmlstays in place as fallback. - Bumps
fp.certificate.versionfield on existing rows (drives "regenerate PDF" prompt). - Adds
res.company.x_fc_nadcap_logo+x_fc_company_brand_logoplaceholders.
6.7 Verification (smoke test on entech staging)
- Open an in-flight job. Click Print Operator Traveller → A4 landscape PDF renders with header, item info, process-sheet header, step table with target ranges + blank actual lines + tank codes.
- Verify Code 128 barcode on header reads correctly with a phone scanner.
- Take a completed job (ENP-ALUM-BASIC, ~25 transitions). Open its
fp.certificate. Click Generate Customer CoC PDF → portrait PDF renders chronologically with WO header, transitions in time order, per-step measurement tables with Target + Actual columns, sign-off block at end with signature + Nadcap + ENTECH logos. - For a step where actual was out-of-spec (soak clean ran 12 min, target 4-6) → confirm Actual cell renders red.
- Run
bt_s19_fischer_merge.py→ confirm Fischerscope PDF appends as page N+1. - Plating → Operations → Labor History → see all timers from smoke-test jobs. Group by Operator → expand → see hrs/min/sec breakdown. Verify reconciliation form opens for a
stoppedtimer. - Run all existing battle tests (
bt_s1throughbt_s17) — no regressions.
6.8 Things to NOT do in 12c
- Don't replace
report_fp_certificate_coc.xml(legacy) — keep as fallback. - Don't touch
fp.certificate.action_issueflow — only the rendering layer changes. - Don't add new model fields — 12a + 12b shipped everything we need.
- Don't try to merge Operator Traveller and Customer CoC into one report — different audiences, different layouts.
- Don't bake the cert statement into the CoC template — read from
fp.certificate.cert_statementso it stays per-customer configurable.
7. Cross-cutting concerns
7.1 Manager-bypass context flags (preserved)
The existing manager-bypass context-flag protocol stays as-is. New flags added by 12b:
| Flag | Skips |
|---|---|
fp_skip_transition_form=True |
required transition input check on Move Parts |
fp_skip_rack_assignment=True |
rack-assignment check on requires_rack_assignment steps |
All bypasses post to chatter with user name for audit (consistent with existing flags).
7.2 ACL changes
group_fusion_plating_supervisorgets write onfp.step.template+fp.rack+fp.rack.tag.group_fusion_plating_operatorgets read onfp.step.template+fp.rack, write onfp.job.step.move+fp.labor.timer(own only).group_fusion_plating_managergets full access on all new models, plus the Re-open Reconciled Timer action.
7.3 Multi-company
All new models carry company_id. All new controllers honor request.env.company. Step library is company-scoped by default — a multi-shop customer authoring a recipe in Shop A doesn't see Shop B's library. A "shared library" cross-company toggle is out of scope (YAGNI).
7.4 Performance
fp.step.templateis a small table (< 1000 rows expected).fp.rackis small (< 100 rows expected).fp.job.step.movewill be the biggest new table (~5–10 rows per job × 1000s of jobs/year = 50k rows/year). All key queries index on(job_id, move_datetime).fp.labor.timersimilar volume. Index on(user_id, state, started_at).- Simple Editor's
library/listendpoint paginates server-side at 50 rows + supports search (no client-side filter on full library).
7.5 Backwards compatibility
- Tree editor: 100% unchanged.
- Existing
ENP-ALUM-BASICrecipe: keeps working, retroactively getsis_template=Trueif customer wants (via post-install patch). - Existing battle tests S1–S17 + S18/S19 cert flow: all keep working.
- Existing CoC report: stays as fallback. New CoC report is opt-in per customer.
- Existing tablet flows: keep working. New Move dialogs are opt-in via
requires_transition_formflag on step templates (default False = legacy one-tap behavior).
8. Build order (single-session executable checklist)
- Read this design + the screen inventory.
- Confirm Sub 11 (MRP cutout) + Sub 12 quality-native work + Subs 1–10 fine-tuning all shipped. Check
fusion_platingversion ≥19.0.9.3.0(after the tank state-control work shipped this session). - Sub 12a (recipe authoring):
- Bump
fusion_plating/__manifest__.pyto19.0.10.0.0. - Add models:
fp.step.template+fp.step.template.input+fp.step.template.transition.input. - Extend
fusion.plating.process.nodewith the additive fields. - Extend
fusion.plating.process.node.inputwithkind+ target-range fields. - Build OWL
fp_simple_recipe_editorclient action + SCSS + XML template. - Build
simple_recipe_controller.pyJSONRPC endpoints. - Recipe form: header buttons +
preferred_editorfield +is_templatecheckbox. - Menu: Plating → Configuration → Step Library.
res.config.settings.default_recipe_editorfield.post_init_hook: backfillkind='step_input'+ seed 18 starter library templates from ENP-ALUM-BASIC.- Smoke test → deploy → verify on entech.
- Bump
- Sub 12b (tablet move/rack/timer):
- Bump
fusion_plating/__manifest__.pyto19.0.10.1.0. - Add models:
fp.rack+fp.rack.tag+fp.job.step.move+fp.job.step.move.input.value+fp.labor.timer. - Extend
fp.job.step+fp.jobwith the new fields. - Build OWL Move Parts dialog + Move Rack dialog + Rack Parts sub-dialog + Stop Timer dialog (extend tablet OWL).
- Extend tablet plant-overview pane: Racks section + Parts section.
- Build
tablet_controller.pyextension: 11 new endpoints. - Implement runtime guards (rack-vs-parts duality, soft/hard block).
post_init_hook: seed 4 starter rack tags + backfillqty_at_step_start.- Smoke test → deploy → verify on entech.
- Run
bt_s1_*throughbt_s17_*— confirm no regressions.
- Bump
- Sub 12c (reports + labor history):
- Bump
fusion_plating→19.0.10.2.0,fusion_plating_jobs→19.0.7.0.0,fusion_plating_certificates→19.0.6.0.0. - Build
report_fp_job_traveller_v2.xml+ir.actions.report+paperformat.fp_a4_landscape_traveller+ smart button onfp.job. - Build
report_fp_certificate_coc_v2.xml+ extend_fp_render_and_attach_pdfto use chronological payload + extend_fp_build_chronological_payload. - Build Labor History screen: list + form + filters + group-by + ACL.
- Add
res.company.x_fc_nadcap_logo+x_fc_company_brand_logoplaceholders. post_init_hook: paperformat creation + cert template wiring.- Smoke test → deploy → verify on entech.
- Run
bt_s19_fischer_merge.py— confirm Fischerscope PDF appends.
- Bump
Each sub-project deploys independently with its own version bump and -u command. If 12b reveals issues, 12a stays shipped. If 12c reveals issues, 12a and 12b stay shipped.
9. Things to NOT do (cross-cutting)
- Don't touch the existing tree editor, its OWL file, or its 7 endpoints. Sub 12a's simple editor lives alongside, not on top.
- Don't introduce a new module. All work extends existing modules:
fusion_plating,fusion_plating_jobs,fusion_plating_certificates. - Don't fork the recipe data model. Both editors operate on the same
fusion.plating.process.noderecords. - Don't make library imports live references. Every drag-drop creates an independent snapshot. Editing the library never mutates an in-flight recipe.
- Don't break the S14 predecessor lock, S15 bake gate, S17 scrap auto-hold, S18 cert flow, or S19 Fischerscope merge. All of these continue to fire on jobs created from Simple Editor recipes.
- Don't auto-install
requires_rack_assignment=Trueorrequires_transition_form=Trueon existing step templates. New flags default to False so existing tablet flows are unaffected. Customer opts in step-by-step. - Don't hardcode any company branding in PDFs. Logos read from
res.companyconfigurable fields with sensible fallbacks. - Don't introduce
'mrp'or'quality_control'as a manifest dep. Sub 11 and Sub 12-quality removed them; this work doesn't bring them back.
10. Open items deferred to later sub-projects
- Spec measurements as a standalone authoring surface (per part × step rule library) — surfaced by screen 15's hard-block warning. Belongs to a future "QC Spec Library" sub-project. For now, hard-block resolution opens whatever the existing spec input form is.
- Multi-day audit chain on multi-shift jobs — chain-of-custody report renders correctly today. Future work: shift-aware grouping.
- Bulk operations in Step Library (batch-edit station list across multiple steps) — out of scope for 12a. Customer can edit one at a time.
- Step library import/export as YAML/JSON — useful for cross-environment promotion but out of scope. Customer copies templates by re-creating in target env.
- First-off / last-off QC — already deferred from earlier sub-projects (S28-style); still deferred.
- VEC machine auto-ingest — already deferred; still deferred.
- Spec measurement dialog UI for hard-block resolution — built in a future sub-project. For now the resolution button opens the existing spec form.
11. References
- Steelhead screen inventory — 24 screenshots, field-by-field
- Existing tree editor:
fusion_plating/static/src/js/recipe_tree_editor.js(649 lines),recipe_controller.py(367 lines) - Existing process node model:
fusion_plating/models/fp_process_node.py(531 lines) - Battle test scenarios driving constraints: S5, S6, S7, S14, S15, S17, S18, S19, S20 in
CLAUDE.md - Sub 11 cutout (MRP removal) decisions in
CLAUDE.md§ Sub 11 - Sub 12 native quality work decisions in
CLAUDE.md§ Sub 12