# Step Library Expansion + Per-Recipe Configurability + Audit Coverage **Date:** 2026-04-29 **Modules:** `fusion_plating` (core), `fusion_plating_jobs`, `fusion_plating_reports` **Status:** Approved by user, ready for implementation plan ## Problem The plating-shop step library (`fp.step.template`) covers ~60% of a real plating workflow but is missing several common steps (Receiving, Electroclean, Strike, Salt Spray, Adhesion Test, Hardness Test, Packaging, Tank Replenishment). Existing kinds have light default measurement seeding — many industry-standard fields (Bath ID, current density, photo evidence, multi-point thickness) aren't authored by default, so audit-required values often get skipped. The recipe author also can't customise step measurements at the recipe level — they can only edit the library template, which affects every recipe that step appears in. There's no per-recipe override for prompts, target ranges, or instructions. The office team writes per-step instructions that operators read at runtime; this channel exists but is hidden in a tab and isn't surfaced in the simple recipe editor for inline editing. ## Goals 1. Cover the full plating workflow with appropriate Step Kinds and audit-grade default measurements. 2. Add specialised input types (`photo`, `multi_point_thickness`, `bath_chemistry_panel`, `ph`) that match real shop instrumentation. 3. Make every prompt **per-recipe configurable** — recipe authors can disable, rename, retarget, reorder, or add custom prompts on each recipe step without touching the library. 4. Make office→operator instructions visible and editable directly in the simple recipe editor, with library default + per-recipe override. 5. Wire all of the above through to runtime (`fp.job.step` input wizard, tablet QC checklist, CoC chronological report) so recorded values are captured for audit. 6. Battle-test end-to-end after deploy. ## Non-Goals - IoT/sensor auto-fill of measurements (read pH probe / bath chemistry panel directly from instruments) — separate project. - Customer-portal display of recorded measurements — separate. - Step Kinds beyond the 8 added here (deburring, anodize seal, abrasive blasting) — same pattern, easy to add later. - Replacing the existing tablet OWL QC checklist — extend only. - Per-customer or per-part override of step measurements — separate concern (parts already carry their own recipe variant). ## Architecture Map ``` fp.step.template (library — office authors here) ├── description (Html, "Default Operator Instructions") ├── default_kind (Selection, drives DEFAULT_INPUTS_BY_KIND) ├── input_template_ids → fp.step.template.input └── transition_input_ids → fp.step.template.transition.input ↓ [snapshot copy on drag into recipe] fusion.plating.process.node (recipe step — recipe authors override here) ├── description (Html, per-recipe override; empty = fall back to library) ├── collect_measurements (Boolean, master switch — NEW) └── input_ids → fusion.plating.process.node.input ├── collect (Boolean, per-prompt opt-out — NEW) ├── template_input_id (m2o — NEW, traceability for "reset to defaults") └── (existing fields: name, input_type, target_min/max, target_unit, required, hint, sequence, kind) ↓ [MO/job creation copies recipe nodes] fp.job.step (work order step — operator records here) ↓ [Mark Done → input wizard fires, FILTERED to collect=True only] fp.job.step.input.wizard.line ↓ [operator types values → commit] fp.job.step.move (transfer_type='step', json input data) ↓ [QWeb render] report_coc_chronological.xml (audit document — only collect=True inputs) ``` ## New Step Kinds Added to `fp.step.template.default_kind` Selection and `DEFAULT_INPUTS_BY_KIND`: | Kind code | Label | Default Operation Measurements | |---|---|---| | `receiving` | Receiving / Incoming Inspection | Qty Received, Qty Rejected, Customer PO# Verified (boolean), Packing Slip #, Condition Notes, Damage Photo (photo, optional), Inspector Initials (signature) | | `electroclean` | Electroclean | Time, Temperature, Amperage (A), Voltage (V), Current Density (ASF), Polarity (selection: anodic/cathodic/periodic), Bath ID | | `strike` | Strike (Wood's Nickel / Activation) | Time, Temperature, Amperage (A), Voltage (V), Current Density (ASF), Bath ID | | `salt_spray` | Salt Spray / Corrosion Test | Test Duration (hr), Result (pass_fail), Red Rust %, White Corrosion %, Lab Report (photo) | | `adhesion_test` | Adhesion Test | Test Method (selection: bend/tape/burnish/file), Result (pass_fail), Photo of Coupon (photo) | | `hardness_test` | Hardness Test (HV / HK / HRC) | Test Load (gf), Reading 1/2/3 (multi_point_thickness), Average (number), Equipment ID, Last Calibration Date | | `packaging` | Packaging / Pre-Ship | Packaging Type (selection: VCI bag/bubble wrap/separator paper/custom crate), Qty Per Package, Package Count, Cert Package Included (boolean), Customer-Supplied Packaging (boolean) | | `replenishment` | Tank Replenishment | Bath ID, Chemistry Added (text), pH Before (ph), pH After (ph), Concentration Before, Concentration After, Operator Initials (signature) | ## Beefed-Up Defaults on Existing Kinds Idempotently appended via `action_seed_default_inputs` (skips if name already present): | Existing Kind | New seeded inputs | |---|---| | `racking` | Rack ID, Masking Applied (boolean), Photo of Racked Load (photo) | | `derack` | Mask Removal Method (selection), Residue Check (pass_fail) | | `mask` | Mask Material (selection: Microshield/latex tape/vinyl plugs/wax), Photo (photo) | | `demask` | Residue Check (pass_fail), Surface Condition (selection: clean/marks/needs rework) | | `cleaning` | Bath ID, Ultrasonic On (boolean), Titration Done (boolean) | | `etch` | Acid Concentration (% or g/L), Bath ID, HE Risk Flag (boolean) | | `rinse` | Rinse Type (selection: cascade/spray/DI/city), Conductivity µS/cm, Time | | `plate` | Bath ID, pH (ph), Bath Concentration (g/L), Current Density ASF (electroplate), Multi-Point Thickness (multi_point_thickness) | | `bake` | Oven ID, Chart Recorder File (photo) | | `inspect` | Defect Type (selection), Thickness Sample, Photo, Inspector Signature | | `final_inspect` | Defect Categorization (selection), Dimensional Verification (pass_fail), Surface Finish Ra, Inspector Signature | | `wbf_test` | Retest Count, Photo on FAIL (photo) | | `dry` | Dry Method (selection), Time, Temperature | | `ship` | Carrier (selection), Tracking #, BoL #, Photo of Sealed Shipment | ## New Input Types Added to `fp.step.template.input.input_type`, `fusion.plating.process.node.input.input_type` (the recipe-step input model), and `fp.job.step.input.wizard.line.input_type` (runtime wizard): | Type code | Label | Renders as | |---|---|---| | `photo` | Photo | Binary widget with `image` rendering — capture or upload one image | | `multi_point_thickness` | Multi-Point Thickness (avg) | Five number inputs (R1–R5) + auto-computed average; min/max bounds applied to average | | `bath_chemistry_panel` | Bath Chemistry Panel | Bundle: pH + concentration + temperature + bath ID — saves clicks for plate steps | | `ph` | pH | Number input clamped to 0–14, two-decimal precision | `signature` already exists; will be auto-seeded on more kinds via the new defaults. ## Per-Recipe Configurability — Model Changes | Field | Lives on | Type | Default | Purpose | |---|---|---|---|---| | `collect` | `fusion.plating.process.node.input_ids` row | Boolean | `True` | Per-prompt opt-out. Wizard / report filter to `collect=True` only | | `template_input_id` | same | Many2one to `fp.step.template.input` | (snapshot link) | Traceability — "this prompt was seeded from library template X". Powers "reset to library defaults" | | `collect_measurements` | `fusion.plating.process.node` | Boolean | `True` | Master switch. When `False`, runtime skips the input wizard entirely for this step | Existing fields on `fusion.plating.process.node.input_ids` (`name`, `input_type`, `target_min`, `target_max`, `target_unit`, `required`, `hint`, `sequence`, `selection_options`) are already editable — recipe author can rename / retarget / reorder freely without touching the library. ## Per-Recipe Configurability — Recipe Author Capabilities Each recipe step exposes: 1. **Disable a prompt** — toggle `collect` off (preserves history; not deleted) 2. **Override target range** — edit `target_min` / `target_max` per recipe 3. **Rename a prompt** — edit `name` 4. **Reorder prompts** — drag handle on `sequence` 5. **Mark required per recipe** — flip `required` independently of library 6. **Add custom prompt** — new row with `template_input_id = False` 7. **Disable entire step's data collection** — master `collect_measurements = False` on the recipe node 8. **Reset to library defaults** — re-sync `input_ids` + `description` from linked library template, preserve custom rows where `template_input_id = False` ## Operator Instructions (Office → Floor) `fp.step.template.description` (Html) — relabel UI to **"Default Operator Instructions"** with tooltip *"Standing instructions the office gives operators for this step. Snapshot-copied onto every recipe that uses this step. Recipe authors can override per recipe."* `fusion.plating.process.node.description` (existing, already snapshot-copied) is the per-recipe override. Empty recipe-node `description` falls back to library at runtime so blank means "use library default", not "show nothing". Runtime visibility — three places: 1. **Tablet shop-floor card** — instructions panel above input prompts (expandable) 2. **Backend Mark-Done wizard** — same panel at top of input form 3. **Printed traveller / WO sheet** — instructions section under each step heading ## Simple Recipe Editor — UI Changes Each step card gains two expansions: ``` ┌─ Plate ENP ───────────────────────────────────────┐ │ Plate ENP @ Tank #3 · 30 min · 180-200°F │ │ │ │ ▸ 📋 Instructions (using library default) │ │ ▸ ⚙ Measurements (5/5 collected) │ └────────────────────────────────────────────────────┘ ``` ### Instructions expansion - Source toggle: **"Library default"** vs **"Custom for this recipe"** - Rich-text editor showing current text (library default if recipe override empty) - **[Use library default]** / **[Save override]** buttons - Badge on step card: **"📋 default"** vs **"📋 custom"** ### Measurements expansion - Master toggle: **"Collect measurements at this step"** - Editable list of prompts with: drag handle, collect checkbox, name, input type, target range, unit, required toggle - **[+ Add custom prompt]** to append a recipe-only prompt - **[Reset to library defaults]** button - Badge on step card: **"5/5 collected"** (green), **"3/5 collected"** (amber), **"No measurements"** (grey) ## "Add Common Audit Fields" One-Click Action New button on `fp.step.template` form: `action_add_common_audit_fields`. Idempotently appends: - Operator Initials (signature, required) - Bath ID (text) - Photo on Failure (photo, optional, hint="upload only if failure observed") - Equipment ID (text) Skip rows whose name already exists. Logs to chatter. ## Runtime Wiring | Component | Change | |---|---| | `fp.job.step.input.wizard` `default_get` | Filter `recipe_node.input_ids` to `kind == 'step_input' AND collect == True`. If `recipe_node.collect_measurements == False`, return empty `line_ids`. | | `fp.job.step.input.wizard.line.input_type` | Mirror the 4 new input types so the wizard form can render per-type widgets | | `fp.job.step.input.wizard_views.xml` | Conditional widgets per input type (photo → image binary; multi_point_thickness → 5-cell row + avg; bath_chemistry_panel → 4-cell group; ph → number with [0,14]) | | `fusion_plating_shopfloor` tablet QC checklist OWL | Same per-type rendering on tablet | | `fp.job.step.move` | No schema change — JSON `input_data` field already accepts arrays/dicts; just confirm encoding round-trips for the new types | | Snapshot-copy logic (library → recipe node) | Verify `template_input_id` link is set; verify all fields including new types copy verbatim | | `_fp_recipe_to_job` (MO/job creation) | Verify `collect`, `collect_measurements`, `template_input_id` carry through to `fp.job.step.recipe_node_id` chain | ## CoC / Audit Report Rendering `fusion_plating_reports/views/report_coc_chronological.xml`: 1. Filter rendered inputs to `collect == True` only. 2. Render branches for new input types: - `photo` → thumbnail `` from `ir.attachment`, fallback to `[photo: filename]` if attachment missing - `multi_point_thickness` → `R1: x, R2: y, R3: z, R4: a, R5: b → avg M` - `bath_chemistry_panel` → 4 labelled rows (pH, conc, temp, bath ID) - `ph` → `pH X.XX` 3. Skip prompts where the recipe author opted out (`collect=False`) — don't render them as "(not collected)" since auditors only care about what WAS measured. ## Migration Single migration script: `fusion_plating/migrations/19.0.18.7.0/post-migrate.py` 1. Default `collect=True` on all existing `fusion.plating.process.node.input_ids` rows. 2. Default `collect_measurements=True` on all existing `fusion.plating.process.node` rows. 3. For each existing `fp.step.template`, re-run `action_seed_default_inputs` (idempotent — adds new defaults without clobbering manual edits). 4. Backfill `template_input_id` on recipe-node inputs by name-matching against the linked library template's inputs (best-effort; rows that don't match stay as `False` and become "custom prompts" for purposes of reset-to-defaults). Seed data file: `fusion_plating/data/fp_step_template_data.xml` (`noupdate="1"`) — one example template per new Step Kind so users see populated entries on upgrade. ## Edge Cases 1. **Recipe node with empty `description`** — runtime renders the linked library template's `description` (fall-through). If both empty, instructions section is hidden in the operator wizard. 2. **Recipe author opts out of all prompts but leaves `collect_measurements=True`** — wizard fires with zero rows; UX gives "no inputs to record, mark done?" confirmation. Same as today's behaviour for kinds with no defaults (rinse, dry, gating). 3. **Photo input on operation measurement (new) vs transition input (existing)** — both kinds always store the image as an `ir.attachment` linked to the `fp.job.step.move` row (never as inline base64 in the `input_data` JSON, to keep the payload small and indexable). Distinguish operation vs transition by `kind` field on the prompt row. CoC report iterates both kinds and renders thumbnails. 4. **Multi-point thickness validation** — at least one reading required when `required=True`; average computed over **non-empty cells only** (empty = "didn't measure that point"); when the computed average falls outside `[target_min, target_max]` the wizard shows a non-blocking warning banner before commit (operator can override and proceed, but the out-of-band condition is recorded on the move for QC review). 5. **Bath chemistry panel — partial fill** — auditor wants to know which fields were captured; all four fields stored in the JSON payload as separate keys. Empty values render as "—" in the CoC. 6. **Reset to library defaults** — preserves rows where `template_input_id = False` (recipe-author-added custom prompts). Re-enables `collect=True` on rows where `template_input_id` is set. Logs to chatter with diff summary. 7. **Library template renamed** — recipe nodes already snapshotted; `template_input_id` link survives the rename. "Reset to defaults" pulls the renamed prompt back. 8. **Library prompt deleted** — recipe nodes' `template_input_id` becomes a dangling reference; reset-to-defaults treats those as "deleted upstream, leave as recipe custom" (don't drop them — recipe author may still want them). 9. **Existing recipes upgrading** — the migration sets `collect=True` everywhere, so behaviour is unchanged for existing data. Only newly-disabled prompts (post-upgrade) suppress at runtime. ## Battle Test Plan Build `fusion_plating/scripts/bt_step_library_audit.py` exercising every change. Same `bt_s*.py` pattern as `fusion_plating_quality/scripts/`. Runs in odoo-shell against the live entech DB after deploy. Reports PASS / FAIL / SKIP per assertion, summary counts at the end. | # | Assertion | |---|---| | 1 | Every new Step Kind has at least 1 seed template loaded | | 2 | Every Step Kind (new + existing) yields the expected default inputs after `action_seed_default_inputs` runs on a fresh template | | 3 | Library → recipe drag snapshot-copies every input field including the 4 new types | | 4 | `template_input_id` link is set on snapshot-copied inputs | | 5 | Recipe → job step inheritance preserves `collect`, `collect_measurements`, custom prompts | | 6 | Operator wizard at runtime filters to `collect=True` only | | 7 | Master `collect_measurements=False` skips the wizard entirely | | 8 | Adding a custom prompt to a recipe node (no `template_input_id`) survives reset-to-defaults | | 9 | Reset-to-defaults re-enables opted-out library prompts and pulls in newly-added library prompts | | 10 | Each new input type stores and round-trips through `fp.job.step.move.input_data` (JSON) | | 11 | Multi-point thickness average computed correctly (incl. edge case of 1 reading) | | 12 | Photo attachment lands in `ir.attachment` and links to the move row | | 13 | CoC chronological report renders each new input type without errors | | 14 | CoC excludes `collect=False` rows | | 15 | Operator instructions render at runtime — recipe override beats library default; empty override falls back to library | | 16 | "Add Common Audit Fields" one-click is idempotent (run twice, no duplicates) | | 17 | `action_seed_default_inputs` is idempotent after manual edits (user-edited rows survive re-seed) | | 18 | `description` on recipe node clears correctly via "Use library default" toggle | ## Files Touched | File | Change | |---|---| | `fusion_plating/models/fp_step_template.py` | Extend `default_kind` Selection, extend `DEFAULT_INPUTS_BY_KIND`, add `action_add_common_audit_fields` | | `fusion_plating/models/fp_step_template_input.py` | Add 4 input types to selection | | `fusion_plating/models/fp_process_node.py` | Add `collect_measurements` on node; add `collect` + `template_input_id` on inline input model; mirror new input types | | `fusion_plating/models/fp_process_node_inherit.py` | If applicable for filtering helpers | | `fusion_plating/views/fp_step_template_views.xml` | Add "Add Common Audit Fields" button; relabel `description` to "Default Operator Instructions" | | `fusion_plating/data/fp_step_template_data.xml` | NEW — seed templates for the 8 new kinds | | `fusion_plating/migrations/19.0.18.7.0/post-migrate.py` | NEW — backfill `collect`, `collect_measurements`, `template_input_id`; re-run `action_seed_default_inputs` | | `fusion_plating/static/src/js/simple_recipe_editor.js` | Render Instructions + Measurements expansions; collect badge; reset-to-defaults action | | `fusion_plating/static/src/xml/simple_recipe_editor.xml` | Templates for new affordances | | `fusion_plating/static/src/scss/simple_recipe_editor.scss` | Styles for expansions and badges | | `fusion_plating/controllers/simple_recipe_controller.py` | New endpoints: `/fp/simple_recipe/step/toggle_collect`, `/fp/simple_recipe/step/edit_input`, `/fp/simple_recipe/step/edit_instructions`, `/fp/simple_recipe/step/reset_to_library` | | `fusion_plating_jobs/wizards/fp_job_step_input_wizard.py` | Filter to `collect=True` only; mirror new input types | | `fusion_plating_jobs/wizards/fp_job_step_input_wizard_views.xml` | Conditional widgets per input type | | `fusion_plating_shopfloor/static/src/...` (tablet QC checklist OWL) | Per-type rendering on tablet | | `fusion_plating_reports/views/report_coc_chronological.xml` | Render branches for new types; filter to `collect=True` | | `fusion_plating/scripts/bt_step_library_audit.py` | NEW — battle-test script | | `fusion_plating/__manifest__.py` | Bump version to `19.0.18.7.0` | | `fusion_plating_jobs/__manifest__.py` | Bump version | | `fusion_plating_reports/__manifest__.py` | Bump version | ## Effort Estimate 6–8 working days. Roughly 60% backend (models, migrations, wizard filtering, CoC, battle test) and 40% frontend (OWL editor expansions, per-input-type widgets in wizard + tablet). ## Out of Scope (Defer) - IoT/sensor auto-fill of measurements - Customer portal display of recorded values - Step Kinds beyond the 8 listed (deburring, anodize seal, abrasive blasting) - Replacing the existing tablet QC checklist OWL component - Per-customer or per-part override of step measurements - Notification/email when audit-required values are missing post-completion