# 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](2026-04-27-simple-recipe-editor-steelhead-screens.md) — 24 screenshots logged with field-by-field notes that drove these decisions. --- ## 1. Why this sub-project exists Two facts forced the design: 1. **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. 2. **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 `kind` Selection (`step_input` / `transition_input`), default `step_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_id` set, all author-defined fields copied, sane-default `input_template_ids` copied, all `transition_input_ids` copied). - **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_ids` M2M. - **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.node` where `is_template=True AND node_type='recipe'`. Snapshot-copies all child steps preserving `sequence`. 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 company `default_recipe_editor` if `auto`). - "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 existing `fusion.plating.process.node.input` rows. - Seeds `fp.step.template` with **18 starter templates** copied from the existing `ENP-ALUM-BASIC` recipe'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 gets `default_kind` set + sane-default inputs seeded. Idempotent — won't re-seed if any `fp.step.template` rows already exist. ### 4.8 Verification (smoke test on entech staging) 1. Install module → step library auto-seeds 18 templates. 2. Plating → Configuration → Step Library — confirm 18 entries with sane-default inputs. 3. Plating → Operations → Process Recipes → New Recipe → Simple Editor. 4. Drag 5 library steps in, reorder via drag-drop, pick stations. 5. Save, close, reopen — data persists, sequence correct. 6. 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. 7. Mark a recipe as template, build a new recipe, "Import starter from template" — all steps copy in, snapshot. 8. Edit the original library step's name → previously-imported recipe steps DO NOT change (snapshot decoupling). 9. 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:` | | `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_ids` count > 1), To Location. - **Camera button next to To Location**: launches `getUserMedia` on tablet → captures photo → uploads as `ir.attachment` → links to the move. - **Compliance Prompts section**: renders the destination step's `transition_input_ids` (snapshot from authored template). `required=True` prompts 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.timer` for 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, advances `qty_done` on source step + `qty_at_step_start` on 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:` → auto-fills "To Rack". - **Save**: marks rack `state='loaded'`, sets `current_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.move` per batch, all linked by the same `rack_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_id` set → MOVE PARTS button **disabled / greyed** with tooltip "Racked — use Move Rack instead." - `current_rack_id` empty → MOVE PARTS enabled. **Plant overview gets two panes** (mirror screen 12): - Top: **Racks** with `MOVE RACK` per row + bulk **UNRACK MULTIPLE**. - Bottom: **Parts** with `MOVE PARTS` per 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.tag` with 4 starter tags: "Rush", "Hold for QC", "Damaged", "Customer Sample". - Backfills `fp.job.step.qty_at_step_start` from existing `qty_done` chain (idempotent). No data destruction. No FK drops. ### 5.10 Verification (smoke test on entech staging) 1. Open tablet, scan a part QR → tablet shows the part-batch row at its current step. 2. Tap `Move Parts` → dialog opens with system fields populated, dest step's authored transition prompts rendered. 3. Try MOVE with required prompt blank → button disabled, tooltip lists the blank prompt. 4. Fill prompt → MOVE re-enables. 5. Move to a step with `requires_rack_assignment=True` → amber blocker + RACK PARTS button. 6. Click RACK PARTS → sub-dialog → pick rack → save → blocker clears, MOVE re-enables. 7. Complete MOVE → `fp.job.step.move` row created, part-batch advanced, rack state updated. 8. Confirm tablet now shows MOVE PARTS button greyed out + MOVE RACK shown on the rack row. 9. Tap MOVE RACK → dialog with all batches → save → all advance atomically. 10. Tap timer pause → Stop Timer dialog → reconcile billed time → save → state moves to `reconciled`. 11. Run `bt_s1_*` through `bt_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.timer` for 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: ` ()`. - "Part Number / Moved By / Time" line. - If the step has captured input values, a 5-column table: **Name | Description | Target | Actual | Recorded By**. - 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**: 1. **Target column is its own column** (Steelhead embeds "(5-10 min.)" in the input name). 2. **Out-of-range Actual values colour-coded red**, in-range green. 3. **Heading-only steps** (Ready For X, gating) render as compact 1-line transitions — no empty tables. 4. **Multi-day jobs**: each transition carries its own datetime; the report walks chronologically. 5. **Signature image** from `fp.certificate.signoff_user_id.x_fc_signature`. 6. **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: ` ()` or ` ()`. - Render "Part Number / Moved By / Time". - If destination step had `input_template_ids` filled 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_sec` editable (audit-logged). - "Re-open for re-reconciliation" button — moves a `reconciled` timer back to `stopped`. **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_pdf` calls 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_payload` returns 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.0` - `fusion_plating_jobs` → `19.0.7.0.0` - `fusion_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_v2` so existing certs auto-use the new layout. Old `report_fp_certificate_coc.xml` stays in place as fallback. - Bumps `fp.certificate.version` field on existing rows (drives "regenerate PDF" prompt). - Adds `res.company.x_fc_nadcap_logo` + `x_fc_company_brand_logo` placeholders. ### 6.7 Verification (smoke test on entech staging) 1. 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. 2. Verify Code 128 barcode on header reads correctly with a phone scanner. 3. 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. 4. For a step where actual was out-of-spec (soak clean ran 12 min, target 4-6) → confirm Actual cell renders red. 5. Run `bt_s19_fischer_merge.py` → confirm Fischerscope PDF appends as page N+1. 6. 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 `stopped` timer. 7. Run all existing battle tests (`bt_s1` through `bt_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_issue` flow — 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_statement` so 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_supervisor` gets write on `fp.step.template` + `fp.rack` + `fp.rack.tag`. - `group_fusion_plating_operator` gets read on `fp.step.template` + `fp.rack`, write on `fp.job.step.move` + `fp.labor.timer` (own only). - `group_fusion_plating_manager` gets 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.template` is a small table (< 1000 rows expected). - `fp.rack` is small (< 100 rows expected). - `fp.job.step.move` will 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.timer` similar volume. Index on `(user_id, state, started_at)`. - Simple Editor's `library/list` endpoint 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-BASIC` recipe: keeps working, retroactively gets `is_template=True` if 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_form` flag on step templates (default False = legacy one-tap behavior). --- ## 8. Build order (single-session executable checklist) 1. Read this design + the [screen inventory](2026-04-27-simple-recipe-editor-steelhead-screens.md). 2. Confirm Sub 11 (MRP cutout) + Sub 12 quality-native work + Subs 1–10 fine-tuning all shipped. Check `fusion_plating` version ≥ `19.0.9.3.0` (after the tank state-control work shipped this session). 3. **Sub 12a** (recipe authoring): 1. Bump `fusion_plating/__manifest__.py` to `19.0.10.0.0`. 2. Add models: `fp.step.template` + `fp.step.template.input` + `fp.step.template.transition.input`. 3. Extend `fusion.plating.process.node` with the additive fields. 4. Extend `fusion.plating.process.node.input` with `kind` + target-range fields. 5. Build OWL `fp_simple_recipe_editor` client action + SCSS + XML template. 6. Build `simple_recipe_controller.py` JSONRPC endpoints. 7. Recipe form: header buttons + `preferred_editor` field + `is_template` checkbox. 8. Menu: Plating → Configuration → Step Library. 9. `res.config.settings.default_recipe_editor` field. 10. `post_init_hook`: backfill `kind='step_input'` + seed 18 starter library templates from ENP-ALUM-BASIC. 11. Smoke test → deploy → verify on entech. 4. **Sub 12b** (tablet move/rack/timer): 1. Bump `fusion_plating/__manifest__.py` to `19.0.10.1.0`. 2. Add models: `fp.rack` + `fp.rack.tag` + `fp.job.step.move` + `fp.job.step.move.input.value` + `fp.labor.timer`. 3. Extend `fp.job.step` + `fp.job` with the new fields. 4. Build OWL Move Parts dialog + Move Rack dialog + Rack Parts sub-dialog + Stop Timer dialog (extend tablet OWL). 5. Extend tablet plant-overview pane: Racks section + Parts section. 6. Build `tablet_controller.py` extension: 11 new endpoints. 7. Implement runtime guards (rack-vs-parts duality, soft/hard block). 8. `post_init_hook`: seed 4 starter rack tags + backfill `qty_at_step_start`. 9. Smoke test → deploy → verify on entech. 10. Run `bt_s1_*` through `bt_s17_*` — confirm no regressions. 5. **Sub 12c** (reports + labor history): 1. Bump `fusion_plating` → `19.0.10.2.0`, `fusion_plating_jobs` → `19.0.7.0.0`, `fusion_plating_certificates` → `19.0.6.0.0`. 2. Build `report_fp_job_traveller_v2.xml` + `ir.actions.report` + `paperformat.fp_a4_landscape_traveller` + smart button on `fp.job`. 3. Build `report_fp_certificate_coc_v2.xml` + extend `_fp_render_and_attach_pdf` to use chronological payload + extend `_fp_build_chronological_payload`. 4. Build Labor History screen: list + form + filters + group-by + ACL. 5. Add `res.company.x_fc_nadcap_logo` + `x_fc_company_brand_logo` placeholders. 6. `post_init_hook`: paperformat creation + cert template wiring. 7. Smoke test → deploy → verify on entech. 8. Run `bt_s19_fischer_merge.py` — confirm Fischerscope PDF appends. 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.node` records. - **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=True` or `requires_transition_form=True` on 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.company` configurable 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](2026-04-27-simple-recipe-editor-steelhead-screens.md) — 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