Three fixes from user feedback:
1. Chatter posting raw HTML
_AUDIT_BODY in migration 19.0.18.8.0 was a plain str with <p>
tags. message_post escaped it for safety, so the chatter pill
rendered '<p><strong>...</strong></p>' literally to the recipe
author. Wrapped in markupsafe.Markup so Odoo recognises it as
safe HTML. Going forward: ANY message_post body containing HTML
tags MUST be wrapped in Markup() — most callers already do this,
the migration script was the outlier.
2. Library template editor showed raw <p> tags
onOpenLibraryEdit was JSON-cloning the payload directly without
running description through the existing _htmlToText helper that
the per-step editor uses. Added the conversion. Save path
(onSaveLibraryEditor + library_save) already wraps via
_textToHtml so storage stays HTML-compatible.
3. Per-step inline form was missing critical fields — user had to
delete + re-add a step to change Type/workflow trigger/parallel/signoff
onToggleEdit now also captures default_kind, triggers_workflow_state_id,
parallel_start, requires_signoff into the edit state. onSaveStep
sends them in the write vals. Added _fpResetStepEdit helper to
keep open/cancel/save reset paths in sync.
New per-step form has:
* Step Type (Default Kind) dropdown — drives workflow milestone
triggers + step-kind routing (e.g. contract_review opens QA-005)
* Triggers Workflow State dropdown (Sub 14) — per-step override
* Parallel Start checkbox (Sub 13)
* Require QA Sign-off checkbox
step_write controller endpoint also gained a field whitelist —
was previously accepting any vals dict from the client (security
hole + opaque to maintainers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups on the workflow state work:
1) Form layout
The "How triggers combine" help text was crammed into a 2-column
group, taking ~25% of the available width. Pulled it out of the
group and rendered as a full-width <div class="alert alert-info">
below the trigger fields. Same fix applied to Notes — uses a
<separator> + bare <field> for full sheet width.
2) Simple Recipe Editor support
The trigger field was only exposed in the Tree Editor. Added it
to the Simple Editor's inline library form too:
* fp.step.template.triggers_workflow_state_id (new Many2one) —
per-template default, snapshot-copied to recipe nodes when
dropped into a recipe (added to _SNAPSHOT_FIELDS).
* /fp/simple_recipe/workflow_states/list — new endpoint to feed
the dropdown. Soft-fails when fusion_plating_jobs isn't
installed (returns []).
* Library editor JS — _fpEnsureWorkflowStatesLoaded helper
caches the catalog on first open (create + edit paths both
warm it). Save vals carry the trigger id.
* Library editor XML — dropdown rendered after the flag
checkboxes. Hidden when the catalog is empty so the form
doesn't show a useless "— None —" pick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback: operators kept asking why their work order said "Step 10"
for the first row. The 10-spacing was originally there to allow midpoint
inserts (insert sequence 15 between 10 and 20 without renumbering).
Tradeoff is operator confusion, and recipe authors rarely insert in the
middle anyway. Switching to 1-based contiguous sequences.
Files changed (every step-sequence allocation in the codebase):
fusion_plating_jobs/models/fp_job.py
_generate_steps_from_recipe — seq_counter starts at 1, increments by 1.
This is the path that builds fp.job.step records, so new jobs now show
Step 1, 2, 3, ... in the work order.
fusion_plating_bridge_mrp/models/mrp_production.py
Same change for the legacy MRP bridge so customers still on
mrp.production also get 1-based numbering.
fusion_plating/controllers/recipe_controller.py
- create_node: max_seq + 1
- reorder_nodes: idx + 1
- swap renumber: i (was i * 10)
- paste-import renumber: i (was i * 10)
- move_node: max_seq + 1
- _copy_subtree (recipe duplicate/import): i (was i * 10)
fusion_plating/controllers/simple_recipe_controller.py
- _sequence_for_position rewritten — always renumbers siblings to
keep them contiguous. Returns pos + 1 for the inserted node.
Old code used midpoint-with-fallback-to-renumber (10/20/30 spacing).
- step_reorder: i (was i * 10)
- library_input_add + step_add_input: existing_max + 1
What this DOESN'T do
Existing fp.job.step records keep their old sequences (10, 20, ...).
Re-confirm the SO to spawn a fresh job if you want the clean 1-based
numbering on a current test job. No data migration — we're in dev
and the user explicitly said test data is disposable.
What this DOES do
Every NEW job created from this commit forward shows Step 1, 2, 3, ...
Every NEW recipe step inserted via the simple editor / tree editor
also gets sequence 1, 2, 3, ...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two coherent feature drops shipping together because their fp_job_step
edits overlap. Both target operator workflow correctness.
## Sub 13 — Sequential step enforcement (recipe + per-step)
Background:
Investigation on WH/JOB/00339 showed operators starting Incoming
Inspection while Contract Review was still in_progress. Audit:
98.7% of recipe operations system-wide had requires_predecessor_done
= false (the legacy per-step opt-in defaults off, recipe authors
rarely tick the box).
Architecture:
Recipe-level toggle + per-step opt-out (Option A from /investigate).
* fusion.plating.process.node.enforce_sequential — Boolean on the
recipe root. Default True. When True, every operation under this
recipe waits for earlier-sequence steps to finish before it can
start.
* fusion.plating.process.node.parallel_start — Boolean on operation
nodes. When True, this step bypasses the sequential gate (e.g.
paperwork or QA review that runs alongside production).
* Mirrored on fp.step.template (parallel_start) so library steps
carry the flag into snapshots.
* fp.job.enforce_sequential — related from recipe_id. Snapshotted
at job creation so a recipe author flipping the recipe's flag
AFTER job generation does NOT change behaviour mid-run.
* fp.job.step.parallel_start — related from recipe_node_id.
* Decision matrix (encapsulated in
fp.job.step._fp_should_block_predecessors):
recipe.enforce_sequential | step.parallel_start | step.req_pred_done | block?
--------------------------|---------------------|--------------------|------
True | False | any | YES
True | True | any | no
False | any | True | YES
False | any | False | no
* Manager bypass via context fp_skip_predecessor_check=True (existing).
Runtime gates:
* fp.job.step.button_start — calls _fp_should_block_predecessors;
raises UserError naming the blocking earlier step(s).
* fp.job.step.can_start — computed Boolean for view-side disable.
* Move wizard predecessor check
(fusion_plating_shopfloor/controllers/move_controller.py) — uses
the same helper so tablet + backend behave identically.
UI surface:
* Recipe form (fp_process_node_views.xml) — enforce_sequential
toggle on recipe root, parallel_start checkbox on operations.
* Step template form — parallel_start checkbox.
* Simple Recipe Editor (inline library form) — Parallel Start
checkbox + legacy flag demoted with muted styling + supervisor
group gate.
* Recipe Tree Editor (properties panel) — both flags exposed,
only-show on the right node_type.
* Controllers updated to allowlist + payload the new fields.
Migration:
fusion_plating/migrations/19.0.18.12.0/post-migrate.py — sets
enforce_sequential = TRUE on every existing recipe-root node.
Idempotent. User confirmed dev-stage data, so retroactive flip
is safe (no production jobs to disrupt).
Tests:
TestSequentialEnforcement (10 tests) covering:
* sequential mode blocks out-of-order start
* first step always startable
* predecessor finish/skip unlocks next
* parallel_start opts out of gate
* free-flow mode bypasses gate
* legacy requires_predecessor_done still honoured in free-flow
* manager bypass via context
* can_start compute reflects state correctly
* library template parallel_start snapshots into recipe-node
## Sub 12e — Record Inputs Wizard v3 (card layout, dark-mode aware)
Background:
v2 wizard was a 17-column wide editable table. Operators got lost
finding which value column applied to their row's type, horizontal
scroll required on tablets, composite types crammed into one row.
New layout:
* Each measurement renders as a stacked card (CSS Grid + display
transformation on the existing list widget — preserves inline
editing, no JS rewrite).
* Card header: prompt name (large, bold) + type/unit pills.
* Card body: ONLY the value widget for this row's type
(number / boolean / date / text / photo / multi-point / panel).
* Composite types (multi-point thickness 5x reading + avg, bath
panel 4 fields) get inline sub-grid inside the card.
* Empty state ("no measurement prompts") with friendly CTA.
Dark mode:
* SCSS branches at compile time on $o-webclient-color-scheme
(per fusion-plating/CLAUDE.md note).
* Tokens: 7 surface colours + 4 ink levels with light/dark hex
pairs, all behind var(--fp-*) custom properties for per-deploy
override.
* Registered in BOTH web.assets_backend AND web.assets_web_dark
so each bundle compiles its own palette.
Tablet polish:
@media (max-width: 900px) — collapse meta below prompt + bump
numeric input min-height to 56px.
Defensive:
* v2 view kept in the XML file (instant rollback by changing one
view_id ref).
* `:has(.o_invisible_modifier)` rule drops empty cells out of the
grid so Odoo's invisible="..." doesn't punch holes in layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bucket 1 — Generation bug fix
- post-migrate.py for 19.0.18.8.0 promotes flat 'step' children of
recipes to 'operation' so fp.job._generate_steps() picks them up.
Filter is narrow: only direct children of node_type='recipe' get
flipped, tree-editor sub-steps (parent.node_type='operation') are
untouched. Idempotent. Posts an audit chatter note on each affected
recipe.
- Simple Editor controller hardcodes node_type='operation' on insert
+ snapshot-import path so future recipes start correct.
Bucket 2 — Inline library authoring
- 6 new JSONRPC routes (/fp/simple_recipe/library/load + save +
seed_defaults + input/{add,write,remove}, /fp/simple_recipe/tank/list).
- + New Step button in the right pane opens an inline form with name /
kind / icon / instructions / stations / flags / prompts table.
- Pencil icon on each library row reopens the same form prefilled.
- Step Kind picker leads with 'Generic — no automatic behaviour'.
- 'Seed defaults from kind' calls action_seed_default_inputs server-side
for kinds that have curated default prompts.
Bucket 3 — Back nav
- '← Recipes' button in the header (or '← Part' when opened from
Process Composer) mirrors recipe_tree_editor.js, with
clearBreadcrumbs:true to avoid stack pollution.
Verified on entech: LGPS1104's 19 'step' children now show as
'operation', migration chatter note posted on the recipe, asset cache
busted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Plating Jobs list was sorted priority-desc, deadline-asc, id-desc — which
mixed Done (closed) jobs in with Confirmed (open) jobs and made the list look
chaotic to managers. Done jobs from weeks ago surfaced above active work.
Two changes:
1. New stored compute fp.job.state_priority (Integer, indexed) ranks states
by managerial relevance: in_progress=0, confirmed=1, draft=2, on_hold=3,
done=4, cancelled=5. _order now leads with state_priority asc, then
priority desc, then date_deadline asc, then id desc. Active work bubbles
to the top automatically.
2. Plating Jobs action defaults to a new 'Open' filter
(state not in done, cancelled). Managers see only active work by default;
they untick the filter to see history. Added On Hold + Cancelled filters
too for full state coverage.
Verified on entech: top 10 jobs are now all in_progress, sorted by deadline
ascending. Existing 26-row list goes from chaotic to focused.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Battle-tested complete workflow on entech: ABC Manufacturing + Anodize
recipe (id=136) cloned to part-variant (id=1775) → SO S00276 confirmed →
fp.job 1234 with 17 steps → recorded 56 measurement values exercising all
13 input types (incl. all 4 new types) → CoC chronological report renders
69KB with all values incl. photo thumbnails.
Bugs found and fixed:
1. fp.process.node.input_ids missing copy=True — when a master recipe
was cloned per-part (the standard variant pattern), the operator
prompts on each step did NOT get copied to the variant. Result: jobs
built from variants ran with zero prompts even though the master had
them. Fixed: input_ids now copy=True so cloning auto-duplicates.
2. CoC chronological template read dest.input_ids where dest is
fp.job.step. Steps don't carry input_ids — that field lives on the
recipe node. Result: AttributeError aborted the entire CoC render.
Fixed: walk via dest.recipe_node_id.input_ids; preserves the existing
collect=True filter.
3. CoC chronological template used hasattr() in a t-value expression.
QWeb's expression engine doesn't expose Python builtins, raised
KeyError: 'hasattr'. Fixed: use 'collect' in i._fields instead.
Also enhanced photo rendering in CoC: was just "[Attachment]" placeholder;
now renders an actual <img> thumbnail (max 80px tall) plus the filename.
Battle-test script saved to fusion_plating/scripts/bt_e2e_anodize_v2.py
for re-runs / regression testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements 2026-04-29-step-library-audit-design.md. Bumps fusion_plating
to 19.0.18.7.0, fusion_plating_jobs to 19.0.8.12.0, fusion_plating_reports
to 19.0.10.2.0.
LIBRARY EXPANSION
- 8 new Step Kinds: Receiving, Electroclean, Strike, Salt Spray,
Adhesion Test, Hardness Test, Packaging, Tank Replenishment
- 4 new input types: photo, multi_point_thickness, bath_chemistry_panel, ph
- DEFAULT_INPUTS_BY_KIND rewritten to seed audit-grade prompts on every
kind (bath IDs, photos, multi-point thickness, signatures, etc.)
- + Common Audit Fields one-click button on the library template form
- Default Operator Instructions relabel + alert callout
PER-RECIPE CONFIGURABILITY
- collect (Boolean) per recipe-step input prompt — opt out without delete
- collect_measurements (Boolean) master switch on recipe step — when off,
wizard skips entirely
- template_input_id (Many2one) traceability link from recipe to library
- Recipe-step backend form view exposes the new fields with handle drag,
toggle, target range, and library-source column
RUNTIME WIRING
- Step input wizard filters node.input_ids to step_input AND collect=True;
short-circuits on collect_measurements=False
- New input types: photo (image widget + ir.attachment), multi-point
thickness (5 readings + auto avg, skips empty cells), bath chemistry
panel (pH/conc/temp/bath bundle), pH (0-14 numeric)
- Composite values JSON-serialized into value_text; photo via attachment
CoC REPORT
- Filters captured prompts to collect=True only
- Renders new input types with appropriate format
MIGRATION (post-migrate.py for 19.0.18.7.0)
- Backfills collect=True on recipe-step inputs
- Backfills collect_measurements=True on recipe steps
- Re-runs action_seed_default_inputs on every existing template
(idempotent, preserves user edits)
- Backfills template_input_id by name-matching against source library
template (handles JSONB vs varchar name columns)
SEED DATA
- 8 example templates (one per new kind) in fp_step_template_data.xml
with noupdate=1
BATTLE TEST
- bt_step_library_audit.py: 29 assertions all PASS on entech
OWL EDITOR EXTENSION DEFERRED
- The simple recipe editor's per-step Instructions/Measurements
expansions were not implemented in this pass; users configure via the
backend recipe-step form. Track follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two distinct entities were both labelled 'Work Centre' (US/UK spelling
the only differentiator — confusing). Renamed by purpose, model IDs
unchanged so all 12+9 existing cross-refs keep working:
fusion.plating.work.center → 'Production Line'
Physical shop-layout grouping that owns tanks. Examples: 'Line 1 —
EN', 'Anodize Line', 'Prep Bay'. Has tank_ids (O2M),
supported_process_ids (M2M), capacity_per_day. The thing tanks live
in.
fp.work.centre → 'Routing Station'
Per-job-step routing entity (post-Sub-11 mrp.workcenter replacement).
Has 'kind' selection (wet_line / bake / mask / rack / inspect),
cost_per_hour for fp.job.step rollup, default_bath_id +
default_tank_id for release-ready validation. The thing a job step
routes through.
Conceptually a Production Line CONTAINS many Routing Stations (e.g.
'EN Line' production line has wet-line, bake, inspect routing
stations on it).
Updated:
- _description on both models
- string= on the name fields
- list/form/search view strings
- act_window names ('Production Lines' / 'Routing Stations')
- menu items in fp_menu.xml + fp_jobs_menu.xml
- doc comments in both model files explaining the distinction
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three targeted gates so operators no longer see admin/audit views:
- KPIs (menu_fp_dashboard) → supervisor+. Operators don't need
dashboards; their tablet shows what they need to do next.
- Move Log (menu_fp_job_step_move) → supervisor+. Operators see
their own moves on the tablet; this top-level menu is the
audit-of-everyone-else view.
- Replenishment Suggestions (menu_fp_replenishment_suggestions) →
supervisor+. Purchasing decision, not operator concern.
Other top-levels were already correctly gated:
- Sales / Configurator → estimator
- Shipping & Receiving → group_fp_receiving
- Compliance hub → supervisor+
- Configuration → manager
- Shop Floor / Quality → operator (correctly visible to floor staff)
- Operations parent stays open; child menus enforce per-action gates
Net effect: a fresh operator now sees ~5 top-level menus instead of
the previous ~10. Supervisors see ~8. Managers see all.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fp_rack_tag_views.xml (and several other view files) reference the
new Phase-2 Configuration sub-folder menus (menu_fp_config_*) defined
in fp_menu.xml. Odoo's data loader is strictly sequential within a
module, so fp_menu.xml must come before any file that references
its bucket xmlids.
Caught by entech upgrade (ParseError on rack-tag menuitem).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 collapses the Plating app's 17 top-level menus down to 6
domains (Sales, Operations, Receiving & Shipping, Quality,
Compliance, Configuration) so users no longer scroll a 17-item
sidebar to find one thing.
Re-parented (no XML id changes — bookmarks still work):
- fusion_plating_compliance.menu_fp_compliance_root
→ menu_fp_compliance_hub (renamed 'General')
- fusion_plating_safety.menu_fp_safety_root
→ menu_fp_compliance_hub (renamed 'Safety / WHMIS')
- fusion_plating_aerospace.menu_fp_aerospace
→ menu_fp_compliance_hub (renamed 'Aerospace (AS9100 / Nadcap)')
- fusion_plating_nuclear.menu_fp_nuclear
→ menu_fp_compliance_hub (renamed 'Nuclear (CSA N299 / CNSC)')
- fusion_plating_cgp.menu_fp_cgp
→ menu_fp_compliance_hub (renamed 'Controlled Goods (CGP)')
- fusion_plating_certificates.menu_fp_certificates
→ menu_fp_quality (Certs are a Quality output, not a separate
top-level concern)
- fusion_plating_bridge_maintenance.menu_fp_maintenance
→ menu_fp_operations
- fusion_plating.menu_fp_job_step_move (Move Log)
→ menu_fp_operations
- fusion_plating.menu_fp_job_step_timelog (Labor History)
→ menu_fp_operations
The new menu_fp_compliance_hub is supervisor-gated; underlying
verticals retain their own group locks (CGP officer, etc.).
Settings menu remains manager-gated through inheritance from
menu_fp_config (already in place).
NEW — Plating landing-page resolver:
- ir.actions.act_window.x_fc_pickable_landing (Boolean tag for
curated picklist; flagged on Sale Orders, Quotations, Process
Recipes for Phase 1; more in Phase 2)
- res.company.x_fc_default_landing_action_id (admin sets fallback)
- res.users.x_fc_plating_landing_action_id (per-user override)
- ir.actions.server action_fp_resolve_plating_landing — picks
user → company → Sale Orders fallback at click time
- menu_fp_root rewired to call the resolver
- User profile + Settings tabs surface the dropdowns
Configurator's earlier menu_fp_root override (action_fp_sale_orders
direct) removed — core's resolver now owns the routing.
Versions bumped: fusion_plating 19.0.11.0.0, configurator
19.0.17.16.0, plus 7 dependent modules patched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the per-row 'highlight whole row' feedback (operator
couldn't tell whether the new step would land before or after the
hovered row) with a precise insertion-point indicator.
How it works:
- Each row's onDragOver computes ev.clientY vs row midpoint.
Above midpoint → insertion index = rowIndex (BEFORE).
Below midpoint → insertion index = rowIndex + 1 (AFTER).
- An <o_fp_drop_indicator> div lives BEFORE the first row and
AFTER every row. When state.dragOverIndex matches that slot's
index, the div expands from height:0 to a 2.25rem dashed-green
reservation strip with a ghost-preview chip ('↓ insert here →
<icon> <step name>').
- onDragStart captures the dragged step/template's name + icon
into state.dragPreviewLabel/Icon for the chip text.
- Smooth 80ms height/margin transition so the line glides between
slots as the cursor moves rather than blinking.
- Trailing dropzone retains its existing 'Drop here to add at end'
styling. Empty list shows 'Drag a library step here to start'.
- onDrop reads from state.dragOverIndex (set by the most-recent
onDragOver) so we drop at the simulated position exactly.
- onDragLeave guards against child-element flicker via
relatedTarget contains() check.
- onDragEnd clears state.dragPreviewLabel/Icon so a half-completed
drag (cancelled by Esc) doesn't leave the chip stuck on screen.
fusion_plating → 19.0.10.4.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gap 1 — Rack Travel Ticket PDF (Sub 12b's Save+Print 404):
+ report_fp_rack_travel.xml in fusion_plating_reports — A5 landscape
single page, big rack name, Code 128 of FP-RACK:<name>, tag chips,
contained part-batches table.
+ ir.actions.report bound to fusion.plating.rack so it appears in
the rack form's Print menu too.
+ Sub 12b's rack_parts_dialog.js Save+Print URL fixed to use the
standard /report/pdf/<xmlid>/<id> route.
Gap 2 — Per-customer cert statement:
+ res.company.x_fc_default_cert_statement (company-level fallback).
+ res.partner.x_fc_cert_statement (per-customer override).
+ Surfaced on the partner form under the existing Cert + Document
Routing block.
+ Chronological CoC body resolves: customer override → company
default → hardcoded AS9100/ISO 9001 boilerplate. Three-tier
fallback so existing certs without overrides keep working.
Gap 3 — Chronological CoC 'Actual' column:
+ Build a captured_values_by_input dict from the move's
transition_input_value_ids (Sub 12b captures these on every
Move Parts commit).
+ Render typed Actual: text → as-is, number → with target unit,
boolean → PASS/FAIL, date → formatted, attachment → '[Attachment]'
placeholder.
+ Falls back to prompts from the destination step's step_input list
when no values were captured (still useful as audit-of-what-was-
asked even if blank).
Version bumps:
fusion_plating → 19.0.10.3.0
fusion_plating_reports → 19.0.10.1.0
fusion_plating_certificates → 19.0.5.3.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fusion_plating → 19.0.10.2.0 (Labor History views)
fusion_plating_jobs → 19.0.7.0.0 (Operator Traveller v2)
fusion_plating_reports → 19.0.10.0.0 (Chronological CoC body)
Adds data entries for the 2 new XML files (timelog views + coc
chronological).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fusion_plating → 19.0.10.1.0
fusion_plating_shopfloor → 19.0.25.0.0
Adds data entries for fp_rack_tag_views.xml + fp_job_step_move_views.xml.
Adds 4 OWL dialogs + their templates + shared SCSS to the shopfloor
backend asset bundle (loaded after the existing manager_dashboard.js).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Adds views/fp_step_template_views.xml to data list (after process_node_views).
- Adds simple_recipe_editor.{js,xml,scss} to web.assets_backend bundle.
- res_config_settings_views.xml + post_init_hook already wired — extend in place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fusion_plating: tank field labels (Code → Tank Number, Tank → Tank Name)
+ state-control header buttons (Mark Empty/Filled/In Use/Draining/
Maintenance/Out of Service) with chatter audit logging.
- fusion_plating_configurator: Plating app default landing screen = Sale
Orders, while keeping menu name as 'Plating'.
- fusion_plating_jobs: SO smart-button label 'Plating Jobs' → 'WO'.
Already deployed and verified on entech earlier in the session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- I1: Lock time_log_ids list inside step Audit tab to read-only
(no create/edit/delete on the nested list). Audit timelog rows
are produced exclusively by button_start / button_finish; if a
manager could hand-edit them, cost_total rollups would silently
drift.
- I2: Add explicit list view (decoration on state) and search view
(filters by state/kind, group_by state/work_centre/job) for
fp.job.step. The Steps (Admin) menu was using Odoo's default
auto-list with no filter, which would be unusable after a few
weeks of step accumulation. Action now references the search
view explicitly.
Manifest 19.0.8.7.0 -> 19.0.8.7.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manager-only views during Phase 1 — operator UI rebuilt in Phase 6.
Top-level menu 'Plating Jobs (new)' (seq=47) groups the three
new act_window actions (Jobs, Steps Admin, Work Centres) so the
foundational models can be exercised through the UI without
touching the operator-facing menus that still serve mrp.production
and mrp.workorder.
Job form has Steps/Source/Costs notebook tabs. Step form has
Equipment/Plating Spec/Audit/Instructions tabs (Audit shows
the time log rows from Task 1.7). Search filters by state,
priority, partner, facility.
Manifest 19.0.8.6.1 → 19.0.8.7.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- I1: Add supervisor ACL row on fp.job.step.timelog. The other
job-related models all have a 3-tier (operator/supervisor/
manager) pattern; timelog had been operator+manager only with
no comment explaining why. Adding the supervisor row for
consistency — supervisors can write timelogs the same as
operators.
- I2: Capture fields.Datetime.now() once in button_start and
reuse for both the step's first-start audit timestamp and the
new timelog's date_started. Mirrors button_finish's pattern.
Eliminates microsecond drift between two related timestamps.
- M6: Update stale comment in fp_job_step.py that said
'duration_actual will be sum of timelog rows once Task 1.7
lands' — Task 1.7 has landed; replace with current behaviour.
Manifest 19.0.8.6.0 → 19.0.8.6.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each button_start opens a fresh timelog row; button_finish closes
the open row and recomputes step.duration_actual as the sum of all
interval durations. Replicates Odoo MRP's mrp.workorder.time_ids
granularity natively (no mrp dep).
Schema: step_id (M2O cascade), user_id, date_started,
date_finished, duration_minutes (computed, stored).
ACLs: operator get create permission on timelogs because
button_start creates them.
Tests: test_start_creates_timelog (asserts the log row exists,
date_finished is False, user_id is the current user) and
test_finish_closes_timelog (asserts log gets date_finished, has a
non-negative duration, and step.duration_actual matches).
Manifest 19.0.8.5.1 -> 19.0.8.6.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- I1: Replace 'Task 1.6' markers in stub method comments and
NotImplementedError messages with forward-looking phrasing.
Task 1.6 is what just shipped (the field expansion); the action
stubs are deferred to an unspecified future task. Stale markers
would have confused future readers/operators.
- I2: Add test_cost_total_recomputes_when_rate_changes — insurance
test that verifies @api.depends('cost_per_hour') triggers through
the related-from-work_centre chain. Catches future Odoo upgrades
that break related-depends.
Manifest 19.0.8.5.0 → 19.0.8.5.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Equipment: bath_id, tank_id, rack_id (all in core). oven_id deferred
to a bridge module — fusion.plating.bake.oven lives in shopfloor and
core can't depend on it. masking_material_id deferred too — model
fusion.plating.masking.material does not yet exist anywhere; will be
added when the masking model lands.
Audit: signoff_user_id (readonly), facility_id (related from
work_centre_id, stored).
Plating spec: thickness_target, thickness_uom (um/mil/in),
dwell_time_minutes, bake_setpoint_temp, bake_actual_duration,
bake_chart_recorder_ref (Nadcap audit trail).
Recipe-related: requires_signoff, auto_complete, is_manual,
customer_visible (all related from recipe_node_id, stored, so
operator sees current values without re-querying process.node).
Cost rollup: cost_per_hour related from work_centre_id, cost_total
computed (duration_actual / 60 x rate), currency_id related too.
Full rollup-from-timelogs lands in Task 1.7.
Tests cover: facility_id related-field, thickness_uom default,
cost_total zero/non-zero paths.
Manifest 19.0.8.4.1 -> 19.0.8.5.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- I2: Add TODO comment block + stub button_pause/button_skip/
button_cancel that raise NotImplementedError. Makes the missing
state-machine paths explicit instead of invisible gaps. Future
Task 1.6 wires the real implementations; shop-floor buttons in
Task 1.8 can already point to the right method names.
- I3: button_finish now preserves first-finish audit timestamp
via 'if not step.date_finished:' guard, mirroring button_start.
Future rework flow that re-opens a step won't lose the original
finish data. The duration_actual rollup landing in Task 1.7 will
use timelog rows for the most-recent interval if needed.
- I4: step_count and step_done_count are now store=True so list
views and stat buttons (Task 1.8) don't recompute across all
job rows on every render. step_progress_pct and current_step_id
stay non-stored - they're cheap derivatives. Split compute methods
so stored + non-stored fields don't share one method (Odoo flags
the mix as inconsistent and recomputes stored fields whenever the
non-stored one is read, defeating the perf gain).
Manifest 19.0.8.4.0 -> 19.0.8.4.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-operation model replacing mrp.workorder for plating. Each step
instantiates from a recipe operation node (recipe_node_id link).
Container/step nodes from the recipe template are rendered at view
time via that link — they don't get rows here. See spec §5.2
Option A.
7-state machine: pending → ready → in_progress → done, plus
paused, skipped, cancelled. button_start/button_finish enforce
the transitions.
Job header gets step_ids + step_count, step_done_count,
step_progress_pct, current_step_id (computed from steps).
Equipment, audit fields, plating-spec fields, time logs, and
release-ready validation come in Tasks 1.6 and 1.7.
Manifest 19.0.8.3.1 → 19.0.8.4.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move NOTE comment from between @api.depends and the function
into a proper docstring on _compute_costs.
- Make currency_id required=True (default still in place — only
a programmatic write would null it, but Monetary fields need a
non-null anchor).
- Add test_current_location_for_confirmed to cover the title-case
fallback branch in _compute_current_location.
Manifest 19.0.8.3.0 → 19.0.8.3.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scope was reduced from spec's full §5.1 list because 6 of the 10
plating-specific fields point to models in dependent modules
(configurator, quality, portal, logistics, bridge_mrp). Adding
those Many2ones in core would invert the dependency graph. They
move to their owning modules via _inherit = 'fp.job' and get
re-bundled by fusion_plating_jobs in Phase 2.
This commit lands the core-safe subset:
- sale_order_line_ids (sale_management is in core depends)
- recipe_id, start_at_node_id (process.node is in core)
- invoice_ids (account is reachable via sale_management → sale)
- Cost rollup: quoted_revenue / actual_cost / margin / margin_pct
with placeholder compute (actual_cost = 0 until Task 1.5 wires
fp.job.step.cost_total)
- current_location stub (full Bath/Oven rendering in Task 1.6)
Tests cover the cost-rollup math and the current_location stub.
Spec §5.1 has been re-tabulated with explicit 'Module' column.
Manifest 19.0.8.2.1 → 19.0.8.3.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Sequence: add noupdate="1" to fp_job_sequences.xml. Without it,
every module update resets number_next to 1, corrupting the live
job-number stream. Matches fp_sequence_data.xml convention.
- action_cancel now raises UserError on an already-cancelled job
instead of silently rewriting state. Audit-grade traceability
expects explicit failures.
- Added TODO marker for action_hold / action_resume /
action_revert_to_confirmed so future authors don't bypass the
state-machine guards.
- Tests: added cannot_cancel_done (covers the dead-code UserError
branch) and cannot_cancel_already_cancelled.
Manifest version bumped 19.0.8.2.0 -> 19.0.8.2.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Header model replacing mrp.production. mail.thread for chatter,
priority/state/deadline tracking, sequence WH/JOB/00001+. Tests
cover create, confirm, cancel, and forbidden double-confirm.
State machine:
draft -> confirmed -> in_progress -> done
v ^
cancelled (rework reverts here)
on_hold can be entered from confirmed or in_progress.
Step relations come in Task 1.5; SO/recipe/portal/cost extension
fields come in Task 1.4.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ACL: use group_fusion_plating_operator for read-only tier instead of
base.group_user, matching the established hierarchy in the rest of
ir.model.access.csv
- Test: rename test_facility_required_for_active_centre →
test_facility_optional_at_create. Old name claimed the opposite of
what the assertion checks.
- Manifest version bumped 19.0.8.1.0 → 19.0.8.1.1.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces mrp.workcenter for plating. Domain-specific kinds
(wet_line/bake/mask/rack/inspect/other) drive release-ready
validation on steps. ACLs follow existing supervisor/manager
hierarchy.
Note: spec listed default_oven_id Many2one to fusion.plating.oven
but that model does not exist in core (the bake-oven model
fusion.plating.bake.oven lives in fusion_plating_shopfloor, which
core cannot depend on). Field omitted; the bridge that adds
fp.job/fp.job.step can re-introduce it via _inherit later.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two user-reported gaps:
1. The Process Recipes list was about to be flooded by part-scoped
clones as soon as parts started carrying customised processes —
thousands of clones would bury the handful of real shared
templates (General Processing, Anodize, etc.).
Fix: the main Process Recipes action now narrows to
part_catalog_id = False so shared templates stay alone in that
view. A sibling menu "Part Processes" (Plating → Operations →
Part Processes) shows the inverse list — every part-cloned
process, grouped by part — so admins can audit clones without
cluttering the templates list.
Search view gains two toggle filters (Shared Templates /
Part-Scoped) and a "Group by Part" option. The node list gains
an optional "Part" column.
Split across modules: core owns the base search / tree / action
(unchanged); configurator owns all the part_catalog_id-dependent
pieces (filter extensions, list column, narrower domain,
"Part Processes" action + menu). Keeps the dependency direction
clean — configurator always depends on core, never the other way.
2. Added a "Process" smart button to the part form's button box.
Shows either a green check (composed) or "None" (not yet
composed) and opens the part-scoped Composer on click. Gives
users one-tap access from any part form without hunting through
the Process tab.
fusion_plating → 19.0.8.0.0
fusion_plating_configurator → 19.0.13.1.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback: "Disabled" was confusing; it looked like the step
was turned off entirely when in fact the semantic meant "every job
runs this, cannot be removed". Aligning labels with Steelhead's
own terminology and the mental model the shop floor already uses:
Python Selection values (unchanged: disabled / opt_out / opt_in)
disabled → "Required"
opt_out → "Opt-Out (included by default, can be removed per job)"
opt_in → "Opt-In (excluded by default, can be added per job)"
Tree-editor side panel inline labels match, plus a short helper
line under the dropdown:
"Required — every job runs this step.
Opt-Out — ships included, estimator can remove per job.
Opt-In — ships excluded, estimator can add per job."
Field string also flipped from "Opt In/Out" to "Step Usage" — the
new header reads closer to what the field actually controls.
Column order also flipped so the Opt-Out option appears above
Opt-In — matches the frequency in real recipes (most optional
steps are included by default and sometimes skipped, not the other
way around).
fusion_plating → 19.0.7.4.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related gaps:
1. loadTree() only auto-expanded nodes when expandedNodes was
completely empty — that was "first-load only" behaviour. After
an import, the subsequent loadTree() found the map populated
and skipped the expand pass entirely, so every freshly imported
node came in collapsed ("2 hidden"). Users had to click each
one open by hand.
Fix: iterate the whole tree on every load and default any node
whose id isn't yet in expandedNodes to true. Nodes the user
explicitly collapsed stay that way (their id IS in the map,
set to false).
2. Added "Expand all" and "Collapse all" buttons to the tree-
editor header so operators can get a full view (or a tight
overview) without clicking node by node. Collapse all keeps
the recipe root expanded — otherwise the canvas goes blank.
fusion_plating → 19.0.7.3.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The import feature appended every imported node to the end of the
target recipe. That's wrong for the common case — General Processing
has Shipping as its last operation, so importing an Electroless
Nickel pack should land BEFORE Shipping, not after it. The user
would otherwise have to click Move Up dozens of times.
Controller: /fp/recipe/node/import_children now accepts
insert_before_id:
null/missing → append at end (default, unchanged)
0 → insert at the start
<positive id> → insert right before that top-level child
Implementation reorders target's top-level children in one pass
after Phase 1 creates the copies (placeholder sequence=0). Phase 2
splits existing vs. new, finds the anchor index in the existing
list, and reassigns sequences 10/20/30/... across the merged list.
Collisions on the old max_seq-based append strategy are eliminated.
JS: state.importInsertBefore drives a new "Insert:" dropdown in the
toolbar with options:
— At the end — (default)
— At the start —
Before <each top-level child name>
Smoke on entech (3-case): insert-before-middle, insert-at-start,
insert-at-end all produce the expected ordering.
fusion_plating → 19.0.7.2.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs reported on the tree editor after the move/import feature
shipped:
1. Card titles truncated to "Contrac…" because .o_fp_re_title had
white-space: nowrap + text-overflow: ellipsis. Swapped to
white-space: normal + overflow-wrap: anywhere so long names
wrap onto multiple lines inside the card. Widened card max-
width 380→460px and bumped min-width 240→260px so wrapped
titles have room.
2. Import-children was flattening the tree — all operations AND
their step children landed at the top level instead of staying
nested under their operations.
Root cause: src_node.copy({'parent_id': new_parent.id, ...})
on a _parent_store model behaved unpredictably — in some runs
the override in copy_vals didn't stick and child recursion
ended up with a wrong parent_id. Rewrote _copy_subtree to use
copy_data() + Node.create() so parent_id is set explicitly and
child_ids / parent_path are stripped (we recurse ourselves).
Smoke verified on entech: General Processing (1 root + 5 ops
+ 7 steps = 13 nodes) imports with hierarchy bit-identical to
source.
fusion_plating → 19.0.7.1.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two gaps closed on the recipe tree editor based on user feedback:
1. Explicit Move Up / Move Down buttons on every non-recipe node
row, driven by a new POST /fp/recipe/node/move_sibling endpoint.
DnD already existed but couldn't reliably move a node above a
sibling when the drop zone overlapped the node being dragged.
The button-based flow sidesteps that entirely and makes "nudge
one slot" a single click.
2. Inline "Import from recipe" toolbar that appears when the
Import button is clicked in the header. User picks a source
recipe from a dropdown (POST /fp/recipe/list, excludes the
current one), toggles "Skip duplicate names", and clicks
Import. POST /fp/recipe/node/import_children deep-copies every
top-level child of the source under the current recipe,
preserving the sub-tree structure. Dedupe is on by default so
re-running the import on an already-populated recipe is a
no-op; users who want to merge identical-named branches can
untick the checkbox.
Controller endpoints:
- /fp/recipe/list (list recipe roots for the picker)
- /fp/recipe/node/move_sibling (swap with neighbour by direction)
- /fp/recipe/node/import_children (deep-copy subtree with dedupe)
Smoke verified on entech: 5-child recipe imported cleanly, dedupe
blocks re-import, sequence swap works.
fusion_plating → 19.0.7.0.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two separate issues were stacking up on the breadcrumb trail:
1. The composer was launching the tree editor with the name
"Process Composer — …" — identical to its own label. The
breadcrumb trail ended up showing "Process Composer / Process
Composer" because both pages claimed the same name. Renamed the
tree-editor instance to "Process Editor — …" so the two pages
read distinctly.
2. Every "Back to part" click pushed a new entry onto the
breadcrumb stack instead of resetting. After one round-trip the
trail looked like "… / Composer / Editor / Part"; after two it
was "… / Composer / Editor / Part / Composer / Editor / Part".
Added clearBreadcrumbs: true to both back paths (composer's
backToPart and tree-editor's onBackToList) so a RETURN action
actually resets the stack.
fusion_plating → 19.0.6.2.0
fusion_plating_configurator → 19.0.12.4.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the Recipe Tree Editor is opened from the part-scoped Process
Composer (Sub 3), the composer already passes part_id via the
action context. The editor was ignoring it and routing onBackToList
to the generic Recipes list, stranding the user away from the part
they came from.
Capture ctx.part_id at onMounted, expose a state.fromPart flag, and
branch onBackToList: if part_id is set, open the fp.part.catalog
form; otherwise keep the current Recipes-list behaviour. XML button
label flips "Recipes" → "Part" accordingly so the user knows where
the button will take them.
fusion_plating → 19.0.6.1.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>