Adds 8 new Step Kinds (Receiving, Electroclean, Strike, Salt Spray, Adhesion Test, Hardness Test, Packaging, Replenishment) with industry- standard default measurements. Adds 4 new input types (photo, multi_point_thickness, bath_chemistry_panel, ph). Beefs up existing kinds (cleaning, etch, plate, bake, ship, etc.) with bath ID, photos, multi-point thickness, signatures. Per-recipe configurability: each recipe step can disable, rename, retarget, reorder prompts; add custom prompts; toggle entire-step data collection. Library is the smart default; recipe is final say. Office-to-operator instructions: relabel as Default Operator Instructions in the library; per-recipe override surfaced in the simple recipe editor; falls back to library default at runtime when recipe override is empty. Battle test plan covers 18 assertions end-to-end on entech. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21 KiB
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
- Cover the full plating workflow with appropriate Step Kinds and audit-grade default measurements.
- Add specialised input types (
photo,multi_point_thickness,bath_chemistry_panel,ph) that match real shop instrumentation. - 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.
- Make office→operator instructions visible and editable directly in the simple recipe editor, with library default + per-recipe override.
- Wire all of the above through to runtime (
fp.job.stepinput wizard, tablet QC checklist, CoC chronological report) so recorded values are captured for audit. - 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:
- Disable a prompt — toggle
collectoff (preserves history; not deleted) - Override target range — edit
target_min/target_maxper recipe - Rename a prompt — edit
name - Reorder prompts — drag handle on
sequence - Mark required per recipe — flip
requiredindependently of library - Add custom prompt — new row with
template_input_id = False - Disable entire step's data collection — master
collect_measurements = Falseon the recipe node - Reset to library defaults — re-sync
input_ids+descriptionfrom linked library template, preserve custom rows wheretemplate_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:
- Tablet shop-floor card — instructions panel above input prompts (expandable)
- Backend Mark-Done wizard — same panel at top of input form
- 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:
- Filter rendered inputs to
collect == Trueonly. - Render branches for new input types:
photo→ thumbnail<img>fromir.attachment, fallback to[photo: filename]if attachment missingmulti_point_thickness→R1: x, R2: y, R3: z, R4: a, R5: b → avg Mbath_chemistry_panel→ 4 labelled rows (pH, conc, temp, bath ID)ph→pH X.XX
- 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
- Default
collect=Trueon all existingfusion.plating.process.node.input_idsrows. - Default
collect_measurements=Trueon all existingfusion.plating.process.noderows. - For each existing
fp.step.template, re-runaction_seed_default_inputs(idempotent — adds new defaults without clobbering manual edits). - Backfill
template_input_idon recipe-node inputs by name-matching against the linked library template's inputs (best-effort; rows that don't match stay asFalseand 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
- Recipe node with empty
description— runtime renders the linked library template'sdescription(fall-through). If both empty, instructions section is hidden in the operator wizard. - 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). - Photo input on operation measurement (new) vs transition input (existing) — both kinds always store the image as an
ir.attachmentlinked to thefp.job.step.moverow (never as inline base64 in theinput_dataJSON, to keep the payload small and indexable). Distinguish operation vs transition bykindfield on the prompt row. CoC report iterates both kinds and renders thumbnails. - 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). - 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.
- Reset to library defaults — preserves rows where
template_input_id = False(recipe-author-added custom prompts). Re-enablescollect=Trueon rows wheretemplate_input_idis set. Logs to chatter with diff summary. - Library template renamed — recipe nodes already snapshotted;
template_input_idlink survives the rename. "Reset to defaults" pulls the renamed prompt back. - Library prompt deleted — recipe nodes'
template_input_idbecomes 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). - Existing recipes upgrading — the migration sets
collect=Trueeverywhere, 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