Files
Odoo-Modules/fusion_plating/docs/superpowers/specs/2026-04-27-sub12-simple-recipe-editor-design.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

828 lines
53 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (Q1Q8 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 34 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_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:<code>` → 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 34 days.**
Customer outcome:
- **Operator Traveller PDF**: recipe-order, paper-style A4 landscape. Equivalent to Amphenol paper sheets (screens 1618).
- **Customer CoC Traveller PDF**: chronological audit, branded, Nadcap stamped. Equivalent to Steelhead's CoC output (screens 1924).
- **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 1924):
- 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**.
- Last page: 2-column sign-off block (left = signed image + name, right = cert statement + comments). Footer: Nadcap logo + ENTECH logo + "Cert Created At: <date>" + 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: `<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_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 (~510 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 S1S17 + 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 110 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