spec(step-library): full plating workflow coverage + per-recipe configurability + audit
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>
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
# 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 `<img>` 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
|
||||
Reference in New Issue
Block a user