Records full session work for future Claude Code sessions: Sub 12a — Simple Recipe Editor + Step Library 3 new models, additive fields on process.node + node.input, fp_simple_recipe_editor OWL action with drop-position simulator, 11 JSONRPC endpoints, snapshot semantics, post_init seeding. Sub 12b — Move Parts / Move Rack / Rack Parts / Stop Timer dialogs fp.rack.tag, fp.job.step.move + .input.value, racking_state on fusion.plating.rack (orthogonal to wear state), state machine on existing fp.job.step.timelog (no parallel labor model), 12 tablet endpoints, 4 OWL dialogs with fp-resolve-rack custom event, manager-bypass flags, plant overview Racks pane. Sub 12c — Reports + Labor History Operator Traveller v2 (A4 landscape, paper-style), chronological CoC body via fp.certificate.body_style + coc_body_router, Labor History views, gap-fix bundle (rack travel ticket PDF, per-customer cert statement 3-tier resolution, captured Actual values from move.input.value). Phase 1/2/3 — Menu reorganization Top-level: 17 → 6 (operator-visible). Industry verticals nested under Compliance hub. Move Log/Labor History/Maintenance under Operations. Certificates under Quality. Configuration: 36 flat → 7 themed folders + Settings sibling. Group-gating: KPIs/Move Log/Replenishment Suggestions → supervisor+. Operator now sees ~5 top-levels instead of ~10. Landing page resolver action_fp_resolve_plating_landing server action, user override → company default → Sale Orders fallback. x_fc_pickable_landing Boolean tag for curated picklist. Production Line / Routing Station rename fusion.plating.work.center → 'Production Line' (shop-layout, owns tanks). fp.work.centre → 'Routing Station' (per-step routing, cost-per-hour, mrp.workcenter replacement). Model IDs unchanged. Other ergonomics Tank field labels (Code → Tank Number, Tank → Tank Name) + state-control header buttons. SO smart-button 'Plating Jobs' → 'WO'. Default landing screen = Sale Orders. Drop-position simulator in Simple Recipe Editor. Updated 'Menu Structure' section near top of CLAUDE.md to reflect new 6-top-level layout + 7-folder Configuration grouping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
101 KiB
Fusion Plating — Claude Code Instructions
Project
Fusion Plating is a multi-module Odoo 19 ERP for electroless nickel plating and metal finishing shops. Built by Nexa Systems for EN Technologies (the client). Replaces Steelhead Software.
Module Structure (30 modules)
fusion_plating/ — Core: facilities, process types, tanks, baths, chemistry, recipes
fusion_plating_batch/ — Rack/barrel batch tracking (FpBatch, FpBatchChemistry)
fusion_plating_kpi/ — KPI definitions, daily auto-compute, dashboard views
fusion_plating_configurator/ — Quotation configurator, pricing engine, part catalog, 3D viewer
fusion_plating_receiving/ — Parts receiving, inspection, damage logging
fusion_plating_invoicing/ — Invoice strategies (deposit/progress/net/COD), account holds
fusion_plating_certificates/ — Certificate registry (CoC, thickness reports), Fischerscope data
fusion_plating_notifications/ — Auto-email engine, notification templates, audit log
fusion_plating_shopfloor/ — Tablet UI, plant overview kanban, process tree visualization
fusion_plating_portal/ — Customer portal + self-service configurator wizard
fusion_plating_reports/ — PDF reports (WO margin, discharge sample, CoC, etc.)
fusion_plating_compliance/ — Compliance framework, jurisdictions
fusion_plating_compliance_on/ — Ontario compliance reference data (data-only, no menus)
fusion_plating_compliance_tor/ — Toronto bylaw discharge limits (data-only, no menus)
fusion_plating_aerospace/ — AS9100 / Nadcap
fusion_plating_nuclear/ — CSA N299 / CNSC
fusion_plating_cgp/ — Controlled Goods Program
fusion_plating_safety/ — SDS, WHMIS, JHSC
fusion_plating_quality/ — QMS (NCR, CAPA, calibration)
fusion_plating_logistics/ — Pickup & delivery, chain of custody
fusion_plating_culture/ — Values / fundamentals (⚠️ RETIRED — do NOT auto-install)
fusion_plating_bridge_mrp/ — MRP integration (recipe→WO, portal job, work order priorities)
fusion_plating_bridge_sign/ — Digital signatures
fusion_plating_bridge_quality/ — Quality bridge
fusion_plating_bridge_documents/ — Odoo Documents integration (NCR, CAPA, FAIR, Doc Control)
fusion_plating_process_en/ — Electroless nickel process pack
fusion_plating_process_chrome/ — Chrome process pack
fusion_plating_process_anodize/ — Anodizing process pack
fusion_plating_process_black_oxide/ — Black oxide process pack
fusion_tasks/ — Local delivery dispatch (GPS, maps, driver scheduling)
Menu Structure (Plating App)
Updated 2026-04-28 — Phase 1/2/3 menu reorg consolidated 17 top-levels down to 6 (operator-visible). Industry verticals (Safety/Aerospace/Nuclear/CGP) moved INSIDE a new Compliance hub. Configuration regrouped into 7 themed folders. See the "Phase 1 / 2 / 3 — Menu reorganization" section near the bottom of this file for the full record.
The Plating app (menu_fp_root, seq 46) opens via the landing-page resolver (action_fp_resolve_plating_landing) — user override → company default → Sale Orders fallback.
Top-level menus (manager view):
| Seq | Menu | Module(s) | Visibility |
|---|---|---|---|
| 5 | Sales & Quoting | fusion_plating_configurator + portal | estimator + supervisor |
| 8 | Configurator | fusion_plating_configurator | estimator |
| 12 | Shop Floor | fusion_plating_shopfloor | operator |
| 15 | Receiving & Shipping | fusion_plating_receiving + logistics | receiving role |
| 18 | Operations | fusion_plating (core) | open (children gate per-action) |
| 30 | Quality | fusion_plating_quality + certificates | operator |
| 50 | Compliance (hub) | fusion_plating + 5 vertical modules | supervisor+ |
| 85 | KPIs | fusion_plating_kpi | supervisor+ |
| 90 | Configuration | fusion_plating + many | manager-only |
Children re-parented in Phase 1:
- Operations now contains: Process Recipes, Baths, Chemistry Logs, Tanks, Racks & Fixtures, Maintenance (was top-level), Move Log (was top-level, supervisor+), Labor History (was top-level), Replenishment Suggestions (supervisor+).
- Quality now contains: Holds, NCRs, CAPAs, RMAs, FAIR, Audits, Doc Control, Certificates (was top-level).
- Compliance hub now contains: General, Safety / WHMIS, Aerospace (AS9100 / Nadcap), Nuclear (CSA N299 / CNSC), Controlled Goods (CGP).
Configuration's 7 themed folders (manager-only by inheritance from menu_fp_config):
- Shop Setup — Facilities, Production Lines (was "Work Centers"), Routing Stations (was "Work Centres"), Process Categories, Process Types, Bake Ovens, Shopfloor Stations, Vehicles
- Recipes & Steps — Step Library, QC Checklist Templates, Quality Points
- Materials & Tanks — Bath Parameters, Replenishment Rules, Chemicals, Rack Tags, Calibration Equipment, Calibration Events
- Workforce — Operator Certifications, Shop Roles, Training Types, Quality Teams
- Quality & Documents — Customer Specs, Approved Vendor List, Quality Tags / Reasons / Stages, N299 Levels, Notification Templates, Notification Log
- Pricing & Billing — Invoice Strategy Defaults, Account Holds
- Reference Data — Value Sets, Value Rotations Plus Settings (sequence 1, sibling above the 7 folders).
Field Service (fusion_tasks) still has its own standalone root app (seq 45). Same task actions also accessible under Plating → Receiving & Shipping.
Culture (seq 80) — RETIRED, uninstalled on entech; the menu still defines itself in repo but doesn't appear on the live system.
Key rules:
- Sales menu unified in
fusion_plating_configurator. Portal adds Quote Requests + Portal Jobs as children. Do NOT create a separate Sales menu in portal. - New top-level menus should be a LAST resort. Most new functionality belongs as a child of one of the 6 existing top-levels. Adding to Configuration goes into the right themed folder.
- When adding a new bucket folder to Configuration, define it in
fusion_plating/views/fp_menu.xmlnear the top (Odoo's data loader is strictly sequential — every parent xmlid must be defined before any child references it).
Retired / Do-Not-Install Modules
These modules have source code in this repo but are intentionally NOT installed on entech (the client's live Odoo). Do not:
- Include them in any
-ior-usequence that installs modules automatically. - Add them as a
dependstarget from any other Fusion Plating module. - Re-sync their folders to
/mnt/extra-addons/custom/on entech. - Recommend installing them to the user without explicit confirmation.
| Module | State on entech | Retired because | What to do if revisiting |
|---|---|---|---|
fusion_plating_culture |
state=uninstalled, dir removed from entech disk |
Soft people-ops feature (peer kudos / "Fundamental of the Week"); zero data entered; not a client priority. Top-level "Culture" menu confused operators. | Ask the client whether they want it before reinstalling. If yes: re-sync folder + -i fusion_plating_culture + seed a value set. |
fusion_plating_sensors |
deleted entirely (not in repo anymore) | Duplicated fusion_plating_iot's scope but with no working alerting logic. Its valuables (sensor_type taxonomy, dashboard, location flexibility) were ported into fusion_iot/fusion_plating_iot/. |
N/A — gone. Any new sensor work goes in fusion_iot/fusion_plating_iot/. |
Critical Rules — Odoo 19
- NEVER code from memory — Read reference files from the server first.
- Backend OWL:
static template,static props = ["*"], standalonerpc()from@web/core/network/rpc. NOTuseService("rpc"). - HTTP routes:
type="jsonrpc"— NOTtype="json"(deprecated in Odoo 19). - Search views: NO
group expand="0", NOstringattribute on<search>, NO<group string="...">wrapper for group-by filters. Use bare<group>for group-by. - res.config.settings: Only boolean/integer/float/char/selection/many2one/datetime. NO Date fields.
- res.groups: Use
privilege_id(NOTcategory_id).user_idsis OK but the deprecatedusersalias is NOT. Always includesequencefield. - Field params:
parent_pathdoes NOT acceptunaccentparameter in Odoo 19. - SCSS borders: Use
$border-color(SCSS variable) for card borders, NOTcolor-mix()in border shorthand — the SCSS compiler drops it.color-mix()works fine inbackground-color,box-shadow, etc. - Theme awareness: All colours must use CSS custom properties (
var(--bs-body-bg),var(--bs-body-color),var(--bs-border-color),var(--bs-secondary-color),var(--o-action)). NO hardcoded hex. Seefusion_plating_shopfloor.scssas the gold standard. - XML comments: No double-hyphens (
--) inside<!-- -->comments — invalid XML, causes lxml parse error. - XML data ordering: Window actions must be defined BEFORE
<menuitem>elements that reference them in the same file. - Module install on new modules: Use
--update=basealongside-i MODULEto ensure Odoo rescans the addons path and finds the new module directory. - Implied group cascade:
implied_idsonres.groupsdoes NOT reliably propagate to users on module install. Always includeuser_idsto explicitly assign admin, or fix via SQL post-install.
Naming
- New custom models (post-2026-04):
fp.*prefix (e.g.fp.part.catalog,fp.certificate) - Existing custom models: Keep
fusion.plating.*(e.g.fusion.plating.portal.job,fusion.plating.delivery) - New fields on standard Odoo models:
x_fc_*prefix - Legacy fields:
x_studio_* - Canadian English for all user-facing text
- SCSS class prefix:
o_fp_*(shopfloor:o_fp_po_*,o_fp_pt_*; recipes:o_fp_recipe_*) - Monetary fields: always pair with
currency_idfield on the same model
Smart Buttons — Anatomy + Conventions
Smart buttons sit in the <div class="oe_button_box" name="button_box"> at the top of a form view. Every smart button MUST follow this canonical pattern so the row stays visually consistent — icon on top, count in the middle, label on the bottom.
Canonical button shape
<button name="action_view_holds" <!-- method on the underlying model -->
type="object"
class="oe_stat_button" <!-- mandatory — drives the box styling -->
icon="fa-hand-paper-o" <!-- Font Awesome 4.x class, always fa-* -->
invisible="x_fc_hold_count == 0"> <!-- optional; see "Conditional visibility" -->
<field name="x_fc_hold_count" widget="statinfo" string="Holds"/>
</button>
What each piece does:
name=— the Python method called on click (anaction_view_Xreturning a window action dict).class="oe_stat_button"— REQUIRED. Without it the button doesn't get the stat-box styling and renders as a plain action button.icon=— Font Awesome 4 (fa-cogs,fa-truck,fa-list-alt,fa-th-large, etc.). Pick one that telegraphs the target model.<field widget="statinfo">— REQUIRED for the count-on-top label-below format. Don't usestring="Foo"on the<button>itself when you want a count — that produces a label-only button (the emptyBOM Itemsissue we fixed in v19.0.17.6.0).
Don'ts (every one of these is a real bug we shipped + reverted)
- Don't use
string="Label"on<button>if the button has a meaningful count — you get a plainLabelbutton with no number. Use the<field widget="statinfo">form instead. - Don't anchor smart-button xpath to a model that may not exist (e.g.
//button[@name='action_view_mrp_production']—mrp.productionis gone post-Sub 11). Anchor to a stable button this same view adds (e.g.action_view_pickings) or to//div[hasclass('oe_button_box')]directly. - Don't add a smart button that always shows zero because the underlying field/model is gone (the dead
Work Ordersbutton we removed in 19.0.17.4.0). If the count is structurally zero, drop the button entirely. - Don't compute counts via
env.get('model')—Environmentin Odoo 19 has noget. Use'model.name' in self.envthenself.env['model.name'](see Critical Rules — Odoo 19). - Don't put the same data behind two different buttons. "Plating Jobs" and "Work Orders" were both fp.job lookups — we kept Plating Jobs and dropped Work Orders.
Conditional visibility
If a button is only meaningful for some SOs (e.g. BOM Items is noise on a single-part SO; By Job Group is noise on an SO with no group tags), HIDE it conditionally rather than letting it render as 0 Foo:
invisible="x_fc_distinct_part_count < 2" <!-- BOM Items: 2+ parts -->
invisible="not x_fc_has_wo_group_tag" <!-- By Job Group: at least one tag -->
invisible="x_fc_ncr_count == 0" <!-- NCRs: only when there are open ones -->
Add the supporting boolean / count as a stored or non-stored compute on the model. Group multiple visibility helpers in ONE compute method to keep the _compute_smart_button_visibility chain cheap (one pass over order_line).
Ordering / placement
- Always-visible meaningful buttons go first — they're the workflow signals an operator scans for first (Receiving, Plating Jobs, Holds, Checks).
- NCRs / RMAs sit in the middle — visible only when present (so they pop only when there's actual quality work).
- Conditional / multi-lens analytical buttons go LAST (BOM Items, By Job Group). They overflow into the
More ▾dropdown when the row is full, which is fine — they're the "I'm zooming into a complex SO" tools, not the daily-driver buttons.
To add a button at the end of the row regardless of where the inherited view positions things, use a second xpath:
<xpath expr="//div[hasclass('oe_button_box')]" position="inside">
<button .../>
</xpath>
position="inside" appends to the end of the button box.
Action method shape
def action_view_holds(self):
self.ensure_one()
return {
'name': _('Holds'),
'type': 'ir.actions.act_window',
'res_model': 'fusion.plating.quality.hold',
'view_mode': 'list,form', # always 'list,form' or 'kanban,list,form'
'domain': [('job_id', '=', self.id)], # filter to this record's data
'context': {'default_job_id': self.id}, # so the Create button pre-fills
}
Always include a context with default_* keys for the Create button on the empty-list state — otherwise the operator hits Create on an empty list and gets a blank form with no link back to the source record.
Smart-button row checklist before merge
- Uses
class="oe_stat_button"andwidget="statinfo"if it shows a count - Has an
icon=(FA 4 class) - Has an
invisible=clause if the count is structurally zero in some scenarios - Action method returns a window action with
view_mode,domain, andcontext.default_* - Conditional/analytical buttons are pushed to the end of the button box via a second
position="inside"xpath - No two buttons surface the same underlying records (no MRP/native duplicates)
Process Recipe System (NEW — v19.0.2.x)
Model: fusion.plating.process.node (in fusion_plating core)
- Hierarchical tree with
_parent_store = True - Node types:
recipe,sub_process,operation,step - Companion model:
fusion.plating.process.node.input(operator inputs) iconis a Selection field (24 curated plating icons), NOT a Char- Auto-icon: JS
guessIcon(name)maps keywords → icons when adding nodes - OWL tree editor: registered as
fp_recipe_tree_editorclient action - Controller:
fusion_plating/controllers/recipe_controller.py(7 endpoints) - SCSS:
fusion_plating/static/src/scss/recipe_tree_editor.scss
Recipe Endpoints
POST /fp/recipe/tree — full nested tree for OWL editor
POST /fp/recipe/node/create — add child node
POST /fp/recipe/node/write — update fields
POST /fp/recipe/node/unlink — delete + cascade
POST /fp/recipe/node/reorder — bulk sequence update
POST /fp/recipe/node/move — change parent_id
POST /fp/recipe/duplicate — deep-copy recipe
Steelhead Features Status
| Feature | Status |
|---|---|
| Hierarchical process tree | Done |
| Node types (recipe/sub/op/step) | Done |
| Auto-complete flag | Done |
| Customer visible flag | Done |
| Manual/automated flag | Done |
| Requires sign-off | Done |
| Opt In/Out (disabled/opt-in/opt-out) | Done |
| Icon picker | Done |
| Time tracking (created/updated with seconds) | Done |
| Operator inputs | Done |
| Description (rich text) | Done |
| File attachments (via mail.thread) | Done |
| OWL tree editor with drag-drop | Done |
| Tags | Not yet |
| Dashboard Transitions | Not yet |
| Treatment Groups / Choices | Not yet |
| Go To Node Options | Not yet |
| Spec Fields | Not yet |
Client Recipes Created
ENP-ALUM-BASIC— Electroless Nickel Plating Aluminium Basic (9 operations, 15 steps). Data file:fusion_plating/data/fp_recipe_enp_alum_basic.xml
Plant Overview Dashboard
- OWL client action:
fp_plant_overviewinfusion_plating_shopfloor - Kanban columns = work centres, cards = active
mrp.workorderrecords - Drag & drop between columns (writes
workcenter_idon the work order) - Endpoint:
POST /fp/shopfloor/plant_overview - Move endpoint:
POST /fp/shopfloor/plant_overview/move_card - Auto-refreshes every 30s
Deployment
odoo-entech (LXC 111 on pve-worker5)
- Type: Native Odoo (apt package, NOT Docker)
- IP: 10.200.1.26
- DB:
admin(PostgreSQL local, userodoo) - Config:
/etc/odoo/odoo.conf - Addons:
/mnt/extra-addons/custom/(fusion_plating modules live here) - Service:
systemctl {start|stop|restart} odoo - Update command:
ssh pve-worker5 "pct exec 111 -- bash -c 'systemctl stop odoo && su - odoo -s /bin/bash -c \"/usr/bin/odoo -c /etc/odoo/odoo.conf -d admin -u MODULE_NAME --stop-after-init\" && systemctl start odoo'" - Copy files:
cat LOCAL_FILE | ssh pve-worker5 "pct exec 111 -- bash -c 'cat > /mnt/extra-addons/custom/REMOTE_PATH'" - IMPORTANT: Must pass
-c /etc/odoo/odoo.confor Odoo won't find the repackaged enterprise addons
odoo-trial (VM 316 on pve-worker1)
- Type: Docker (container
odoo-trial-app, dbodoo-trial-db) - DB:
trial(userodoo) - Host addons path:
/opt/odoo/custom-addons/→ mounts as/mnt/extra-addons/in Docker - Docker network:
odoo_odoo-network - Copy files (base64 pipe through qm guest exec):
B64=$(base64 -w0 "LOCAL_FILE") ssh pve-worker1 "qm guest exec 316 -- bash -c 'echo $B64 | base64 -d > /opt/odoo/custom-addons/REMOTE_PATH'" - Clear asset cache (required after SCSS/JS changes):
ssh pve-worker1 "qm guest exec 316 -- bash -c \"docker exec odoo-trial-db psql -U odoo -d trial -c \\\"DELETE FROM ir_attachment WHERE url LIKE '%/web/assets/%';\\\"\"" - Update command:
ssh pve-worker1 "qm guest exec 316 -- bash -c 'docker stop odoo-trial-app && docker run --rm --network odoo_odoo-network -v odoo_odoo-data:/var/lib/odoo -v /opt/odoo/custom-addons:/mnt/extra-addons -v /opt/odoo/enterprise-addons:/mnt/enterprise-addons -v /opt/odoo/odoo.conf:/etc/odoo/odoo.conf odoo:19 odoo -d trial -u MODULE_NAME --stop-after-init && docker start odoo-trial-app'"
Git Push
cd K:/Github/Odoo-Modules/fusion-plating && git push origin main
Pushes to both GitHub and Gitea (nexasystems.ca) via multiple remotes.
Supabase Knowledge Base
Project: nexasystems (id: ikvdlqkbqsitabxidvnq)
fusionapps.decisions— past architecture decisionsfusionapps.issues— known issues and fixesfusionapps.code_snippets— reference codefusionapps.quick_commands— deployment and admin commandsfusionapps.vm_registry— VM inventoryfusionapps.proxmox_nodes— cluster node specs
End-to-End Business Workflow
Full Lifecycle (What Exists Today)
┌─ QUOTATION ──────────────────────────────────────────────────────┐
│ 1. Customer submits RFQ on portal [DONE] │
│ → FpQuoteRequest (state: new → under_review → quoted) │
│ → Model: fusion_plating_portal/models/fp_quote_request.py │
│ │
│ 2. Customer accepts → "Create Sale Order" button [DONE] │
│ → action_create_sale_order() creates SO with lines │
│ → Links SO origin back to RFQ ref │
│ │
│ 3. SO confirmed → MRP creates Manufacturing Order [DONE] │
│ → Standard Odoo sale_mrp flow │
└──────────────────────────────────────────────────────────────────┘
┌─ MANUFACTURING ──────────────────────────────────────────────────┐
│ 4. MO confirmed → Portal Job auto-created [DONE] │
│ → MrpProduction.action_confirm() override │
│ → Creates FpPortalJob (state: in_progress) │
│ → Links via x_fc_portal_job_id │
│ │
│ 5. Planner assigns recipe + configures steps [DONE] │
│ → x_fc_recipe_id set on MO │
│ → Opens fp.recipe.config.wizard for opt-in/out │
│ → Creates fusion.plating.job.node.override records │
│ │
│ 6. Work orders generated from recipe [DONE] │
│ → _generate_workorders_from_recipe() in bridge_mrp │
│ → One WO per operation node, steps become WO instructions │
│ → Respects opt-in/out overrides from job.node.override │
│ │
│ 7. Operators execute WOs on shopfloor [DONE] │
│ → Plant Overview kanban (drag between work centres) │
│ → Batch chemistry tracking (FpBatch + FpBatchChemistry) │
│ → Quality holds (FpQualityHold → FpNcr → FpCapa) │
│ │
│ 8. MO marked done → Portal job ready_to_ship [DONE] │
│ → MrpProduction.button_mark_done() override │
│ → Auto-creates FpDelivery (draft) │
└──────────────────────────────────────────────────────────────────┘
┌─ SHIPPING & INVOICING ───────────────────────────────────────────┐
│ 9. CoC report generated [DONE] │
│ → report_coc.xml (PDF with job info, certification, sig) │
│ → Attached to delivery + portal job │
│ │
│ 10. Delivery scheduled & executed [DONE] │
│ → FpDelivery: draft → scheduled → en_route → delivered │
│ → Chain of custody auto-logged (FpChainOfCustody) │
│ → Proof of delivery captured (FpProofOfDelivery) │
│ → Routes with stops (FpRoute + FpRouteStop) │
│ │
│ 11. Delivery marked → Portal job shipped [DONE] │
│ → FpDelivery.action_mark_delivered() override │
│ → Sets actual_ship_date + tracking_ref on portal job │
│ │
│ 12. Account hold check before invoicing [DONE] │
│ → x_fc_account_hold on res.partner (fusion_plating_invoicing)│
│ → Blocks SO confirm, invoice post, shipping for non-managers │
│ │
│ 13. Invoice posted → Portal job complete [DONE] │
│ → AccountMove.action_post() override │
│ → Sets invoice_ref on portal job, state → complete │
│ │
│ 14. Auto-email with CoC + Invoice + Tracking [DONE] │
│ → fusion_plating_notifications module │
│ → fp.notification.template (configurable per trigger event) │
│ → fp.notification.log (audit trail) │
└──────────────────────────────────────────────────────────────────┘
┌─ CUSTOMER PORTAL ────────────────────────────────────────────────┐
│ 15. Customer sees on portal [DONE] │
│ → Job progress bar (received → complete) │
│ → CoC download, invoice access, tracking ref │
│ → Quote request history │
└──────────────────────────────────────────────────────────────────┘
Per-Job Recipe Overrides (v19.0.2.0.0 bridge_mrp)
x_fc_recipe_idonmrp.production→ links MO to recipefusion.plating.job.node.override→ per-job opt-in/out decisionsfp.recipe.config.wizard→ checklist wizard for planner- "Overrides" stat button on MO form
- Located in
fusion_plating_bridge_mrp
All Gaps Resolved (2026-04-12/13)
| Gap | Resolution | Module |
|---|---|---|
| Recipe → Work Orders | _generate_workorders_from_recipe() — one WO per operation, steps become instructions |
fusion_plating_bridge_mrp v2.1.0 |
| Account Hold Check | x_fc_account_hold on res.partner, blocks SO/invoice/shipping for non-managers |
fusion_plating_invoicing |
| Auto-Email Package | fp.notification.template + fp.notification.log with hooks on SO confirm, receiving, invoice |
fusion_plating_notifications |
| Quotation Configurator | Part catalog, coating configs, pricing engine, 3D STL viewer, portal wizard | fusion_plating_configurator |
| Parts Receiving | Receiving records, inspection, damage logging, SO auto-create, MRP soft gate | fusion_plating_receiving |
| Certificate Registry | Unified fp.certificate with thickness readings, CoC/thickness/Nadcap types | fusion_plating_certificates |
| Local Delivery | Forked fusion_tasks with GPS/maps, stripped of claims/sync, delivery-specific fields | fusion_tasks |
Architectural Decisions Made
- Recipe → WO: One WO per
operationnode, childstepnodes become numbered instructions in WO description - Account hold: Manual flag on
res.partner(auto from aging is roadmap) - Email triggers: SO confirmed, parts received, invoice posted (configurable per trigger)
- Configurator: Custom build with formula-based pricing, estimator override, portal self-service wizard
- Model naming: New models use
fp.*prefix, existing keepfusion.plating.* - Security groups: Role-based (Estimator, Receiving, Accounting, Shop Manager) layered on existing privilege hierarchy (Operator→Supervisor→Manager→Admin)
Key Models Quick Reference
| Model | Module | Purpose |
|---|---|---|
fusion.plating.process.node |
fusion_plating |
Recipe tree (template) |
fusion.plating.process.node.input |
fusion_plating |
Operator input definitions |
fusion.plating.job.node.override |
fusion_plating_bridge_mrp |
Per-job opt-in/out |
fp.part.catalog |
fusion_plating_configurator |
Customer part library (geometry, material) |
fp.coating.config |
fusion_plating_configurator |
Coating configuration templates |
fp.treatment |
fusion_plating_configurator |
Pre/post treatment steps |
fp.pricing.rule |
fusion_plating_configurator |
Formula-based pricing engine |
fp.pricing.complexity.surcharge |
fusion_plating_configurator |
Complexity surcharge lines |
fp.quote.configurator |
fusion_plating_configurator |
Configurator session + price calc |
fp.receiving |
fusion_plating_receiving |
Parts receiving record |
fp.receiving.line |
fusion_plating_receiving |
Per-part receiving detail |
fp.receiving.damage |
fusion_plating_receiving |
Damage log entry |
fp.invoice.strategy.default |
fusion_plating_invoicing |
Customer-level invoice strategy |
fp.certificate |
fusion_plating_certificates |
Certificate registry (CoC, thickness, etc.) |
fp.thickness.reading |
fusion_plating_certificates |
Fischerscope measurement data |
fp.notification.template |
fusion_plating_notifications |
Configurable email notification |
fp.notification.log |
fusion_plating_notifications |
Email audit trail |
fusion.plating.quote.request |
fusion_plating_portal |
Customer RFQ |
fusion.plating.portal.job |
fusion_plating_portal |
Portal-facing job tracker |
fusion.plating.customer.spec |
fusion_plating_quality |
Spec library |
fusion.plating.quality.hold |
fusion_plating_quality |
Parts on hold |
fusion.plating.ncr |
fusion_plating_quality |
Non-conformance reports |
fusion.plating.capa |
fusion_plating_quality |
Corrective actions |
fusion.plating.batch |
fusion_plating_batch |
Rack/barrel batch tracking |
fusion.plating.kpi |
fusion_plating_kpi |
KPI definition (OTD, yield, throughput, etc.) |
fusion.plating.kpi.value |
fusion_plating_kpi |
KPI daily value (auto-computed or manual) |
fusion.plating.delivery |
fusion_plating_logistics |
Delivery with chain of custody |
fusion.plating.pickup.request |
fusion_plating_logistics |
Customer pickup requests |
fusion.plating.route |
fusion_plating_logistics |
Driver routes with stops |
fusion.technician.task |
fusion_tasks |
Local delivery task (GPS, maps) |
fusion.technician.location |
fusion_tasks |
Driver GPS tracking |
Repackaged Enterprise Modules
See K:\Github\RePackaged-Odoo\CLAUDE.md for full details. Key points:
- Odoo 19 enterprise modules repackaged for community edition
- All OEEL-1 licenses changed to LGPL-3
- Phone-home/telemetry gutted
web_enterpriseandmail_enterpriseare installed on odoo-entech- Addons path includes:
_dependencies,accounting,inventory_manufacturing,hr,sales,ai,fusion_backend,custom,website
Fine-Tuning Initiative (Started 2026-04-21)
System-wide UX gap closure. Running PLAN → SPEC → IMPLEMENT per sub-project so we don't
rewrite code as new requirements surface. Each sub-project has its own design doc in
docs/superpowers/specs/ and its own implementation plan before any code lands.
Sub-Project Roadmap
| # | Sub-project | Status | Gaps |
|---|---|---|---|
| 1 | Direct Order Wizard fix (no auto-confirm/auto-email) | Shipped 2026-04-22 (commit afd8bae+) | Gap 1 |
| 2 | Part Data Model Overhaul (part#/rev required, dual descriptions, per-part cert requirement, SKU→Part Number on customer docs) | Shipped 2026-04-22 (commits 868b418..afd8bae) | 2b, 2c, 2d, 4 |
| 3 | Default Process + Composer per part (reuse recipe tree) | Shipped 2026-04-22 (commits ce07daa..f059348) | 2e, 2f |
| 4 | Contract Review (optional, per-part, settings-driven QA roster, QA-005 1:1 PDF) | Shipped 2026-04-22 | 2i |
| 5 | Order-line fields (fp.serial registry, auto job#, coating-scoped thickness dropdown, revision picker) | Shipped 2026-04-22 | 5, 6, Q2 |
| 6 | Contact Profiles & Communication Routing (per-contact flags + per-location routing + global contact; single resolver helper) | Shipped 2026-04-22 | client transcript A/B/C |
| 7 | IoT tuning (per-sensor polling interval + ingest rate-limit; entech seeded with 25 tanks / 50 sensors) | Shipped 2026-04-22 | client transcript D |
| 8 | Receiving / Inspection / QC flow restructure (fp.receiving = box count only; new fp.racking.inspection per MO; WO soft gate; delivery box-parity warning) | Shipped 2026-04-22 | client transcript E |
| 9 | Process variants per part + persistent draft order wizard + tax per line + payment terms wired + chatter + nicer breadcrumbs across plating models | Shipped 2026-04-26 | various wizard/UX |
| 10 | Quote → Direct Order promotion (won quotes consolidate onto a single PO instead of spawning standalone 1-line SOs) | Shipped 2026-04-26 | redundancy concern |
| 11 | MRP cutout — bridge_mrp deletion + MRP module uninstall (7-phase migration: relocate models, swap inherits, drop legacy FK columns, uninstall mrp + 10 cascade modules) | Shipped 2026-04-26 | bridge_mrp removal |
| 12 | Native Quality — full Odoo quality_control replacement + RMA + integration polish |
In flight (planned) | quality dependency removal |
| ∞ | First-off / last-off QC | Deferred | client transcript F |
| ∞ | VEC machine auto-ingest (Word-format thickness report from network-connected XRF; different machine from Fischerscope) | Deferred | client transcript G |
| ∞ | RMA customer portal submission | Deferred (Sub 12 phase 2) | follow-on to Sub 12 |
Sub 2 Locked Decisions (2026-04-21)
| Q | Decision |
|---|---|
| Q1 — Cert requirement precedence | Part wins; partner is fallback. New selection certificate_requirement on fp.part.catalog: inherit / none / coc / coc_thickness. Default inherit preserves current behaviour for existing records. |
| Q2 — Revision handling | Keep existing chain (parent_part_id, is_latest_revision, revision_ids). Out-of-scope for Sub 2. The "revision picker at order entry" moves to Sub 5. |
| Q3 — Required-field flip | Strict + backfill. On upgrade: part_number = name if empty; revision = 'A' if empty. Then required=True for both. name becomes optional. |
| Q4 — Descriptions shape | Split fp.sale.description.template.description into internal_description + customer_facing_description. Repeater on the part's Descriptions tab gains two columns. Old description column dropped in migration. |
| Q5 — SKU vs Part Number | Use fp.part.catalog.part_number directly as the source of truth. Don't sync to default_code. Customer-facing reports print part_number; internal reports keep showing default_code (service code). Odoo-native screens untouched. |
| Q6 — Description required at order entry | Both required. SO line carries name (customer-facing, already Odoo standard) + new x_fc_internal_description (ops workflow). Both required before save. |
Sub 2 Defensive Measures (Prevent Rework When Later Subs Land)
- Single-source cert resolution function —
mrp.production._fp_resolve_cert_requirement(self)returns(want_coc, want_thickness). Every caller (cert cascade, QC gate, notification routing) goes through this. When Sub 6 restructures partner-level flags into location / contact permissions, one function updates — no call-site hunt. - Shared QWeb line-header macro —
fusion_plating_reports.customer_line_headerrenderspart_number + revision + customer-facing descriptionwith fallback to product name for non-part lines. All 4 customer-facing reports (SO, invoice, packing slip, BoL) call the macro. Sub 5's revision picker updates the macro once, all reports follow. - Isolated migration — Sub 2's
post_init_hookis idempotent (NULL/empty checks). Safe to re-run. Doesn't fight Sub 3/4/5/6 migrations. - Additive SO line fields —
x_fc_internal_description,x_fc_description_template_idsit alongside future Sub 5 fields (x_fc_serial_number,x_fc_job_number,x_fc_thickness,x_fc_revision_snapshot) with zero touchpoints. - Clean removal of old
descriptioncolumn — migrated then dropped. Not kept as deprecated. One clean break now beats two migrations later.
Sub 6 Preview — Contact Profiles & Communication Routing (client transcript A/B/C)
- Sub-contacts under
res.partnerwith per-contact permissions: certs / QC / quotes+SO / invoices. - Multiple delivery locations per customer; each location has its own notification list.
- Global contact (company-level + location-level) gets all communications.
- Will restructure or augment the partner-level
x_fc_send_coc/x_fc_send_thickness_reportflags that Sub 2 currently falls back to. Sub 2's_fp_resolve_cert_requirementis the update point.
Sub 7 Preview — IoT Tuning (client transcript D)
- 6–10 active tanks (of ~20–25 total) need continuous monitoring.
- Polling interval: 30 minutes acceptable, 15 minutes ideal. Configurable per tank.
- Temperature, pH, nickel concentration — all on automated controller (existing
fusion_plating_iotmodule). - Work scope: ensure per-sensor interval field exists + defaults + seed 6–10 tank.sensor records.
Sub 8 Preview — Receiving / Inspection / QC Restructure (client transcript E)
Current flow (wrong): Direct order → receiving entry → receiver inspects on arrival. Correct flow:
- Customer ships parts in boxes. Receiver counts boxes (does NOT inspect individual parts).
- Boxes sit in staging until racking.
- Racking crew opens boxes, inspects each part as they load racks (inspection ≠ receiving).
- Parts go through plating process.
- Post-plate QC on machine (thickness / depth / coating thickness) — existing QC gate (Phase 1–3 work).
- Pack back into the SAME boxes they arrived in. Same qty out as in.
Implication: The current fusion_plating_receiving module conflates receiving + inspection. Sub 8 splits them. Racking-time inspection becomes its own record, linked to WOs not to receiving.
Deferred Items (Future)
- First-off / last-off QC — first and last part of each batch get full QC inspection; middle parts sampled. Not priority.
- VEC machine auto-ingest — different from Fischerscope. Exports a Word doc (picture + data) named
workorder_PO.docxto a network share. Plan: auto-scan the share, parse, attach to QC as thickness_report. Defer until core flow is solid.
Client-Confirmed Operational Thresholds
- Tank polling: 15–30 min, half-hour acceptable
- Active tanks: 6–10 (not all 20–25)
- Boxes round-trip: parts ship back in the same boxes they arrived in, same quantity per box
How to Resume This Work in a Fresh Session
- Read this section (Fine-Tuning Initiative).
- Check the sub-project status table — which sub is in flight.
- Read the corresponding spec in
docs/superpowers/specs/YYYY-MM-DD-sub<N>-*-design.md. - Read the implementation plan if one exists.
- Continue from the next un-checked step.
Sub 11 — MRP Cutout (shipped 2026-04-26)
The Odoo mrp module + 10 cascade dependents have been uninstalled. fusion_plating_bridge_mrp is gone. The plating shop runs entirely on fp.job / fp.job.step. Document this so a fresh session doesn't try to re-add MRP refs.
Final state
- 0 rows in
mrp_production,mrp_workorder,mrp_workcenter - 205+
fp.jobrows, 1,800+fp.job.steprows in production - 0 custom-table FKs to MRP
- Modules uninstalled:
mrp,mrp_workorder,mrp_account,sale_mrp,purchase_mrp,quality_mrp,quality_mrp_workorder,project_mrp*,fusion_manufacturing,fusion_plating_bridge_mrp
Where things ended up after Sub 11
| Model / asset | Old home | New home |
|---|---|---|
fp.work.role, fp.operator.proficiency, hr.employee shop-roles, fusion.plating.process.node.x_fc_work_role_id |
fusion_plating_bridge_mrp |
fusion_plating (core) |
fp.qc.checklist.template (+line) |
fusion_plating_bridge_mrp |
fusion_plating_quality |
fusion.plating.quality.check (+line) |
fusion_plating_bridge_mrp |
fusion_plating_quality |
fp.thickness.reading.quality_check_id link + auto_extracted |
fusion_plating_bridge_mrp |
fusion_plating_quality |
res.partner.x_fc_requires_qc + x_fc_qc_template_id |
fusion_plating_bridge_mrp |
fusion_plating_quality |
fp.job.consumption |
fusion_plating_bridge_mrp |
fusion_plating_jobs |
sale.order.x_fc_workflow_stage + x_fc_assigned_manager_id + workflow buttons |
fusion_plating_bridge_mrp |
fusion_plating_jobs |
QC tablet OWL (fp_qc_checklist.js/.xml/.scss) + /fp/qc/* controller |
fusion_plating_bridge_mrp |
fusion_plating_quality |
| Production Priorities kanban | fusion_plating_bridge_mrp (mrp.workorder) |
fusion_plating_jobs (fp.job.step) |
Hard rules going forward
- Never re-introduce
'mrp'as a manifest dep. Usefp.jobfor jobs,fp.job.stepfor operations. x_fc_job_idis the canonical job link, notproduction_id. Drop legacy MO refs as you find them.fusion_plating_qualitydepends onfusion_plating_shopfloorfor SCSS tokens ($fp-page,$fp-card,$fp-accent). Don't strip that dep — the QC tablet bundle breaks without it.- The QC tablet OWL template namespace is
fusion_plating_quality.FpQcChecklist(wasfusion_plating_bridge_mrp.FpQcChecklist). Don't rename back.
Sub 12 — Native Quality Module (in flight, ~4 working days)
Goal: Build a complete native quality stack matching Odoo quality_control functionality plus plating-specific extensions (RMA, CAPA effectiveness, holds, 8D reports), with zero dependency on Odoo's quality / quality_control. After Sub 12 lands, those modules + fusion_plating_bridge_quality get uninstalled.
Module choice
Enrich fusion_plating_quality — no new modules. Existing module already owns NCR / CAPA / Hold / Check / Calibration / AVL / FAIR / Audit / Doc Control / Customer Spec / Contract Review.
Locked decisions (don't re-ask in fresh session)
| Q | Decision |
|---|---|
| RMA portal submission | Deferred to phase 2. Internal-only RMA in Sub 12. |
| 8D format | Full 8D (D1–D8 sections in the combined NCR + CAPA PDF). |
| Quality Dashboard | 5 tabs (Holds / Checks / NCRs / CAPAs / RMAs) in one client action with a summary header that totals open + overdue across all five. |
| Auto-NCR + auto-Hold on RMA receive | Automatic, with a manager-only "skip this RMA's auto-spawn" toggle on the RMA record. |
| Auto-CAPA on NCR closure | Automatic when severity in (high, critical), with a manager-only override on the NCR. |
| Quality team model | Build a dedicated fp.quality.team rather than reusing res.groups. Teams need their own kanban grouping + per-team escalation chains, which groups don't model well. |
| Stage model vs. state field on NCR | Both. Keep the existing state Selection (used by code paths). Add a parallel stage_id Many2one to fp.quality.alert.stage for the kanban draggable view. Computed bidirectional sync (stage ↔ state). |
| Trigger-based quality.point | Build a new fp.quality.point model. Trigger types: manual, receiving_done, job_step_done, job_done. Existing fp.qc.checklist.template STAYS — it's the template a point fires; the point is the trigger rule. |
| RMA back-link to original SO line | Required field. Always carry the original SO line so cert / part / coating context follows the return. |
| Module choice (one or many) | Single module — enrich fusion_plating_quality. |
Phase A — RMA model (~1 day)
File: fusion_plating_quality/models/fp_rma.py
Model: fusion.plating.rma
| Field | Type | Notes |
|---|---|---|
name |
Char | Sequence RMA/YYYY/NNNN |
partner_id |
M2O res.partner |
Required |
sale_order_id |
M2O sale.order |
The original order being returned |
sale_order_line_ids |
M2M sale.order.line |
Specific lines being returned (subset of the SO) |
original_job_ids |
M2O fp.job (compute from SO lines) |
For navigation only |
state |
Selection | draft / authorised / shipped_to_us / received / triaged / resolving / resolved / closed / cancelled |
trigger_source |
Selection | customer_complaint / qc_fail_post_ship / inspection_post_delivery / other |
severity |
Selection | low / medium / high / critical |
complaint_description |
Html | What the customer reported |
triage_findings |
Html | What we found on inspection |
resolution_type |
Selection | replace / rework / refund / scrap |
resolution_notes |
Html | Free-form notes on the chosen path |
replacement_job_id |
M2O fp.job |
When replace/rework — the new job created |
refund_invoice_id |
M2O account.move |
When refund — the credit note |
inbound_receiving_id |
M2O fp.receiving |
The receiving record auto-created when carrier delivers |
inbound_picking_id |
M2O stock.picking |
Optional — if a stock.picking is also created |
linked_ncr_ids |
O2M fusion.plating.ncr (inverse rma_id) |
NCRs spawned from this RMA |
linked_capa_ids |
O2M fusion.plating.capa (related via NCRs) |
Read-only roll-up |
linked_hold_ids |
O2M fusion.plating.quality.hold (inverse rma_id) |
Holds placed on returned parts |
qty_returned |
Integer | Total units customer is returning |
qty_received |
Integer | Counted on receipt |
customer_tracking |
Char | Customer's outbound tracking # |
our_tracking |
Char | Our return-to-shop tracking # |
carrier_id |
M2O delivery.carrier |
Optional |
qr_code |
Binary (compute) | QR encoding /fp/rma/<id> for the authorisation PDF |
auto_spawn_ncr |
Boolean | Default True. Manager can toggle off before saving. |
auto_spawn_hold |
Boolean | Default True. |
tag_ids |
M2M fp.quality.tag |
(Sub 12 Phase B) |
reason_id |
M2O fp.quality.reason |
(Sub 12 Phase B) |
team_id |
M2O fp.quality.team |
(Sub 12 Phase B) |
chatter |
mail.thread | mandatory |
Lifecycle hooks
action_authorise: statedraft → authorised. Generate the RMA authorisation PDF + email link/QR to customer (usingfp.notification.templateif installed; falls back to standard mail.template).action_mark_shipped_to_us: customer-driven; updates state when carrier scan logged.- On
fp.receivingcreate withrma_idset: state→ received. Ifauto_spawn_ncr, create anfusion.plating.ncrpre-filled (description, severity, customer, parent SO line). Ifauto_spawn_hold, createfusion.plating.quality.holdfor the returned qty. action_triage_complete: state→ triaged. Requiresresolution_typeset.action_resolve: state→ resolved. Triggers resolution-specific actions:replace→ spawn newfp.jobcloned from originalrework→ spawn newfp.jobreferencing the returned units (linked to inboundfp.receiving)refund→ openaccount.move.refundwizard, link result torefund_invoice_idscrap→ createfp.job.consumptionrow tagged 'rma_scrap' + post chatter
action_close: state→ closed. Locks editing.action_cancel: any state →cancelled(manager only).
Smart buttons
RMA form gets buttons to: original SO, original Jobs, inbound Receiving, replacement Job, refund Invoice, NCRs (count), CAPAs (count), Holds (count). Per-target button visibility based on resolution_type / state.
Sequence
Create ir.sequence fp.rma with prefix RMA/%(year)s/, padding 4. Data file fp_rma_sequence.xml.
Reports
fusion_plating_reports/report/report_fp_rma_authorisation.xml — single-page customer-facing PDF with QR code. Branded "EN Technologies".
Phase B — Categorisation & kanban infra (~half day)
Files: fusion_plating_quality/models/fp_quality_tag.py, fp_quality_reason.py, fp_quality_team.py, fp_quality_alert_stage.py
fp.quality.tag
name(Char, required, translate)color(Integer, kanban color)active(Boolean)- Reused by NCR / CAPA / Hold / RMA / Check via
M2M tag_ids
fp.quality.reason
name,description,category(selection:process / supplier / equipment / human / material / other)- Curated reason library so root-cause classification is consistent
fp.quality.team
name,lead_user_id(M2O res.users),member_ids(M2M res.users)escalation_user_id(manager who gets notified on missed deadlines)- Used by NCR / RMA — primary owner team
fp.quality.alert.stage
name,sequence,fold(Boolean — collapsed-by-default in kanban)- Default stages seeded: New / Investigating / Containment / Disposition / Awaiting Sign-off / Closed / Cancelled
- Add
stage_id = fields.Many2one('fp.quality.alert.stage')tofusion.plating.ncrANDfusion.plating.rma. Map state ↔ stage_id via_inverse_*so legacy code paths keep working.
Apply tag/reason/team M2M/M2O fields to: NCR, CAPA, Hold, Check, RMA
Each model gets tag_ids, reason_id, team_id. NCR + RMA additionally get stage_id.
Phase C — Trigger-based quality points (~half day)
File: fusion_plating_quality/models/fp_quality_point.py
fp.quality.point
name,active,descriptiontrigger_type(Selection):manual / receiving_done / job_confirmed / job_step_done / job_done / so_confirmed- Filters (any combination):
partner_ids(M2M),part_catalog_ids(M2M),coating_config_ids(M2M),step_kind(Selection — wet/bake/inspect/etc.) template_id(M2Ofp.qc.checklist.template) — required, the checks to spawnassignee_user_id(M2Ores.users) — optional default inspector- Fires
_spawn_check_for(<source_record>)which creates afusion.plating.quality.checkfrom the template + binds it to the source viajob_idorstep_id.
Hooks (already partly in place — extend)
fp.receiving.writehook (existing): when state flips toclosed, walk allfp.quality.pointwith triggerreceiving_donematching the receiving's partner/parts → spawn checks.fp.job.action_confirmhook (existing — currently calls_fp_create_qc_check_if_needed): replace with quality.point lookup. Keep the existing partner-template fallback as a default point seeded byfp_qc_data.xml.fp.job.button_mark_done: triggerjob_donepoints.fp.job.step.button_finish: triggerjob_step_donepoints.
Phase D — Integration polish (~1 day)
fp.jobform smart-button row: addHolds,Checks,NCRs,CAPAs,RMAsbuttons with badge counts. Always-visible (zero is OK).sale.orderform smart-button row: same five, rolled up across all linked jobs.res.partnerform: customer-level "Quality History" smart button that opens a kanban filtered to that partner across all 5 record types.- One-click cross-creation:
- Hold form →
Open NCRbutton — pre-fills NCR with hold's part / customer / quantity / linked job. - NCR form →
Spawn CAPAbutton — visible when state ∈ {disposition, closed} and severity ≥ medium. - CAPA form →
Verify Effectivenessbutton — schedules a follow-up check on the originating NCR.
- Hold form →
- Unified Quality Dashboard (
fp_quality_dashboardclient action):- 5 tabs: Holds / Checks / NCRs / CAPAs / RMAs
- Each tab is a kanban grouped by
stage_id(NCRs/RMAs) orstate(Holds/Checks/CAPAs) - Header summary card: open count + overdue count across all 5 types
- Filters: my team / my customer / overdue / high-severity
- Menu: Plating → Quality → Dashboard
- CAPA closure-loop linkage: when CAPA effectiveness verification fails, auto-spawn a new NCR linked back to the original. Closes the loop "we said we fixed it but it happened again."
Phase E — Reports (~half day)
Files: fusion_plating_reports/report/report_fp_rma_authorisation.xml, report_fp_8d.xml, report_fp_quality_monthly.xml
- RMA Authorisation PDF: single-page customer-facing. Header with our logo + customer info, RMA number, parts listed (table), return-to address, QR code linking to
/fp/rma/<id>for status tracking, carrier instructions. - 8D Report (NCR + CAPA combined):
- D1: Team (from
team_id+ member_ids) - D2: Problem description (NCR description + scope)
- D3: Containment (NCR containment narrative)
- D4: Root cause analysis (CAPA root_cause + reason_id)
- D5: Permanent corrective action (CAPA action_plan)
- D6: Implement & verify (CAPA implementation_date + verification_evidence)
- D7: Prevent recurrence (CAPA preventive_actions)
- D8: Congratulate the team (CAPA closure notes + team sign-offs)
- Auto-renders when both NCR and CAPA exist; degraded mode if CAPA missing.
- D1: Team (from
- Monthly Quality Summary (
fp_quality_monthlyreport):- Counts by record type / severity / customer / month
- Overdue ageing buckets
- CAPA effectiveness rate (verified / total closed)
- Repeat-customer-issue flag (>2 NCRs same customer in 90 days)
- Run via cron monthly + on-demand from dashboard.
Phase F — Test + verify (~half day)
End-to-end smoke flow on a fresh DB:
- Customer reports issue → create RMA → authorise → email PDF
- Customer ships → carrier delivers →
fp.receivingauto-created → RMA receives → NCR + Hold auto-spawn - QA triages NCR → finds root cause → spawns CAPA (auto via severity rule)
- CAPA assigned to engineering → action plan written → implemented → effectiveness check scheduled
- Effectiveness verified → CAPA closes → NCR closes → RMA resolves (rework path) → replacement job created from original → ships → CoC issued → invoice
- Run 8D report on the closed NCR/CAPA pair
- Verify dashboard counts update at every state transition
- Confirm legacy NCR/CAPA/Hold/Check forms still work (no regressions)
- ACL drilldown: operator sees what they should, supervisor more, manager all
Phase G — Drop Odoo quality cascade (~30 min)
Pre-conditions: Phases A–F all merged + smoke-tested.
- Strip the three custom fields from
fusion.plating.ncr(x_fc_quality_alert_id,x_fc_quality_alert_synced,x_fc_auto_sync— added by bridge_quality) - Remove
fusion_plating_bridge_qualityfrom/mnt/extra-addons/custom/ - SQL:
UPDATE ir_module_module SET state='to remove' WHERE name IN ('fusion_plating_bridge_quality', 'quality_control', 'quality') AND state='installed'; - Restart odoo → cascade uninstall fires
- ALTER TABLE drop the three NCR columns
- (Optional) move
/mnt/extra-addons/inventory_manufacturing/quality{,_control}/out of the path so they can't auto-reinstall
Server / deployment notes (entech)
- LXC 111 on pve-worker5, native odoo (apt), DB
admin, addons path/mnt/extra-addons/custom/ - Update flow:
ssh pve-worker5 "pct exec 111 -- bash -c 'systemctl stop odoo && \ su - odoo -s /bin/bash -c \"/usr/bin/odoo -c /etc/odoo/odoo.conf -d admin \ -u fusion_plating_quality --stop-after-init\" && systemctl start odoo'" - File copy:
cat LOCAL | ssh pve-worker5 "pct exec 111 -- bash -c 'cat > REMOTE'" - Asset cache bust:
DELETE FROM ir_attachment WHERE url LIKE '/web/assets/%'; - Always bump module version in
__manifest__.pyfor migrations to fire (currentfusion_plating_quality:19.0.3.0.0; bump to19.0.4.0.0for Sub 12).
Build order (executable checklist for fresh session)
- Read this Sub 12 section in full + the Sub 11 section above (for context on what's already native).
- Bump
fusion_plating_quality/__manifest__.pyversion to19.0.4.0.0. - Phase A — RMA: create
fp_rma.pymodel +fp_rma_views.xml+fp_rma_sequence.xml+ ACL rows + add to__manifest__.pydata list. - Phase A migration: not needed (new model, fresh table).
- Phase B — categorisation: create the 4 small models + their views + ACL. Add
tag_ids / reason_id / team_idM2M/M2O to NCR, CAPA, Hold, Check, RMA. Addstage_idto NCR + RMA. - Phase B data: seed default stages + a few starter tags/reasons/teams in
fp_quality_categorisation_data.xml. - Phase C —
fp.quality.pointmodel + view + ACL + the 4 trigger hooks (infp.receiving,fp.job.action_confirm,fp.job.button_mark_done,fp.job.step.button_finish). - Phase D — smart buttons on
fp.job,sale.order,res.partner. Cross-creation buttons. Dashboard client action. - Phase E — three QWeb reports.
- Phase F — manual smoke test + ACL drilldown + screenshot the dashboard.
- Deploy each phase as it lands (don't batch — easier to roll back). Bump version each time.
- Phase G runs LAST, only after confirmation that A–F work end-to-end.
Things to NOT do
- Don't add
'quality'or'quality_control'to any manifest dep. They will be uninstalled by Phase G. - Don't import from
odoo.addons.quality.*. Use only native models. - Don't put RMA in a new module. It belongs in
fusion_plating_quality. - Don't break the existing QC tablet OWL. Its template namespace is
fusion_plating_quality.FpQcChecklist, endpoints are/fp/qc/*, andfusion_plating_qualitydepends onfusion_plating_shopfloorfor SCSS tokens. - Don't re-introduce
production_idreferences anywhere. Usejob_id/x_fc_job_id. MRP is gone. - Don't forget
rma_idinverse field on NCR + Hold — those One2many fields on RMA need an inverse Many2one on the linked model.
Status check before starting (run this first in the fresh session)
-- Should show 4: NCR, CAPA, Hold, Check (Sub 12 adds RMA = 5)
SELECT model FROM ir_model WHERE model LIKE 'fusion.plating.%' AND model SIMILAR TO '%(ncr|capa|hold|check|rma)%';
-- Should show 'fusion_plating_quality_bridge_quality_control' state — likely 'installed' until Phase G
SELECT name, state FROM ir_module_module WHERE name LIKE 'quality%' OR name LIKE 'fusion_plating_bridge_quality';
-- Confirm MRP is gone (Sub 11)
SELECT name, state FROM ir_module_module WHERE name = 'mrp'; -- expect 'uninstalled'
-- Live row counts so you know what survives
SELECT 'ncr' AS m, count(*) FROM fusion_plating_ncr
UNION ALL SELECT 'capa', count(*) FROM fusion_plating_capa
UNION ALL SELECT 'hold', count(*) FROM fusion_plating_quality_hold
UNION ALL SELECT 'check', count(*) FROM fusion_plating_quality_check;
Battle Tests — Real-World Operator Scenario Coverage
Persona-driven shop-floor scenarios that surfaced bugs / workflow holes. Every scenario has:
- A test script in
fusion_plating_quality/scripts/bt_s*.pyyou can re-run end-to-end on entech (or any DB) - A fix shipped at a specific module version
- A description of how a real operator would trip the gap and what the system now does
How to re-run any scenario
# From a fresh shell, point at the entech DB:
ssh pve-worker5 "pct exec 111 -- bash -c 'echo \"exec(open(\\\"/mnt/extra-addons/custom/fusion_plating_quality/scripts/bt_sN_NAME.py\\\").read())\" | su - odoo -s /bin/bash -c \"/usr/bin/odoo shell -c /etc/odoo/odoo.conf -d admin --no-http\"'"
Each script is self-contained — builds a fresh SO + job, walks the scenario, asserts the fix is in effect.
Scenario index
| ID | Persona / Scenario | Gap before | Fix shipped | Module version | Test script |
|---|---|---|---|---|---|
| S1 | Carlos forgot to click Start; realizes 2h later | date_started readonly + no way to back-date |
action_recompute_duration_from_timelogs on fp.job.step re-sums after timelog edits |
fusion_plating_jobs 19.0.6.10.0 |
bt_s2_* (covered with S2) |
| S2 | Carlos finished step physically; forgot Finish; went home (12h ghost) | Same as S1 | Same fix — supervisor edits the timelog row, clicks Recompute Duration | fusion_plating_jobs 19.0.6.10.0 |
battle_test_v2.py Fix 4 |
| S3 | Two operators tap Start on same step | ✓ already blocked correctly | n/a | — | battle_test.py |
| S4 | Out-of-order step finish (intentional for parallel tanks) | Allowed by design (parallel work). Use S14 for opt-in serial | n/a | — | battle_test.py |
| S5 | Manager takes over a stuck step (operator on vacation) | ✓ reassign + finish work — added audit in S9 | See S9 | — | battle_test.py |
| S6 | Bake window expired; operator wants to start anyway | Silently allowed → no audit | action_start_bake blocks missed_window; manager-only action_force_start_missed overrides + posts chatter audit |
fusion_plating_shopfloor 19.0.24.1.0 |
battle_test_v2.py Fix 1 |
| S7 | Step ran 12× expected duration | Silent | Chatter warning posted on the job at 1.5×+ overrun | fusion_plating_jobs 19.0.6.10.0 |
battle_test_v2.py Fix 2 |
| S8 | Job closed with qty_done=0 despite qty=5 |
Silent — invoiced for parts that may not exist | button_mark_done blocks until qty_done + qty_scrapped == qty. Manager bypass fp_skip_qty_reconcile=True |
fusion_plating_jobs 19.0.6.10.0 |
battle_test_v2.py Fix 3 |
| S9 | Bob takes over Carlos's in_progress step | Silent reassignment (only step's own chatter logged) | write() override on fp.job.step posts to JOB chatter when assigned_user_id changes on active state |
fusion_plating_jobs 19.0.6.11.0 |
bt_s9_reassign.py |
| S10 | Operator paused for lunch, never resumed → 14 stale-paused steps in prod | No alert / cron / activity | Daily cron _cron_nudge_stale_paused (24h threshold) — schedules mail.activity on parent job for the manager. Idempotent |
fusion_plating_jobs 19.0.6.12.0 |
bt_s10_stale_paused.py |
| S11 | Rectifier dies mid-plating → operator has no abort+retry path | Only options: cancel (kills step) or pause+writetank+start (no audit) | New action_abort_for_retry(reason, new_tank_id) — closes timelog, swaps tank, posts chatter, resets to ready |
fusion_plating_jobs 19.0.6.13.0 |
bt_s11_verify.py |
| S12 | Sarah edits SO line qty 5→8 mid-job | Silent — Carlos plates 5, invoice ships 8 | sale.order.line.write posts warning to job chatter; new action_sync_qty_from_so button on job for explicit propagation |
fusion_plating_jobs 19.0.6.14.0 |
bt_s12_verify.py |
| S13 | Recipe author wrote detailed step instructions; operator never sees them on tablet | Tablet payload omitted instructions/thickness_target/dwell_time_minutes/bake_setpoint_temp/requires_signoff |
All 5 fields added to /fp/shopfloor/scan response AND _step_payload for tablet_overview |
fusion_plating_shopfloor 19.0.24.2.0 |
bt_s13_verify.py |
| S14 | No way to enforce serial-required steps (e.g. acid etch → plating) | Out-of-order start always allowed | New requires_predecessor_done Boolean on fusion.plating.process.node → related on fp.job.step → button_start blocks if any earlier-sequence step isn't done/skipped/cancelled. Manager bypass fp_skip_predecessor_check=True |
fusion_plating 19.0.9.2.0, fusion_plating_jobs 19.0.6.15.0 |
bt_s14_verify.py |
| S15 | Job marked done but bake.window still awaiting_bake |
Compliance bomb — parts ship without bake record | button_mark_done blocks if any linked fusion.plating.bake.window is awaiting_bake or bake_in_progress. Manager bypass fp_skip_bake_gate=True for documented customer deviation |
fusion_plating_jobs 19.0.6.16.0 |
bt_s15_bake_close.py |
| S16 | 45 phantom in_progress steps in DB (operator clocked Start, never moved) | No alert / cron / activity | Hourly cron _cron_nudge_stale_in_progress (8h threshold) — sister to S10 cron |
fusion_plating_jobs 19.0.6.17.0 |
bt_s16_phantom_inprogress.py |
| S17 | Operator drops parts, bumps qty_scrapped 0→2 |
Silent — no AS9100 disposition record | fp.job.write hook auto-spawns fusion.plating.quality.hold for the scrap delta. Operator updates description with cause |
fusion_plating_jobs 19.0.6.18.0 |
bt_s17_scrap_ncr.py |
| S18 | CoC issuance broken in 4 places — operator can't actually email a cert | (a) auto-spawn left every useful field blank → Issue blocked on missing spec_reference; (b) Issue button never generated PDF → attachment_id stayed empty; (c) Send to Customer opened email composer with no attachment; (d) auto-spawn had no idempotency → dupes on button_mark_done retry |
_fp_create_certificates now pre-fills spec_reference (from coating), part_number, quantity_shipped (qty − scrap), po_number, customer_job_no, process_description, entech_wo_number, sale_order_id. Idempotency check skips dupes. action_issue now renders the EN CoC PDF via new _fp_render_and_attach_pdf and sets attachment_id so Send to Customer attaches it automatically. Smart button "Certificates" already on the job form (visible when count > 0) so Tom finds the cert from the job he just closed |
fusion_plating_certificates 19.0.5.1.0, fusion_plating_jobs 19.0.6.19.0 |
bt_s18_cert_flow.py |
| S19 | Lisa uploads Fischerscope X-Ray thickness PDF to QC; CoC ships without it as page 2 — and even after the back-end merge worked, operators couldn't see in the cert form whether the merge would happen | Existing merge logic lived in uninstalled fusion_plating_bridge_mrp (keyed off mrp.production — gone with Sub 11). Post-Sub-11 cert path rendered CoC only; Fischerscope PDF stayed orphaned on the QC record. Even after Phase 1 fix shipped, the cert form had zero indicator that a thickness PDF was on file or had been merged → user reported "I did not see anything in the certification issue" |
Phase 1 (back-end merge): Ported merge to fp.certificate._fp_merge_thickness_into_pdf. New _fp_render_and_attach_pdf wraps cert PDF generation: renders the CoC via QWeb, then looks up the linked fusion.plating.quality.check (x_fc_job_id → fp.job → QC), finds the most recent passed QC with thickness_report_pdf_id, merges via pypdf.PdfWriter.append() (PyPDF2 PdfMerger fallback), posts chatter audit Fischerscope thickness report from QC <name> appended to CoC PDF.. Hooked into action_issue so the multi-page PDF lands on attachment_id automatically. Phase 2 (UI surface): Added 3 computed fields on fp.certificate (in fusion_plating_jobs): x_fc_thickness_qc_id (linked QC), x_fc_thickness_pdf_id (Fischerscope PDF), x_fc_thickness_status (none / pending / merged). Cert form now shows: (1) coloured banner above the title — blue "Will Append on Issue" / green "Merged" / amber "No PDF — operator action required"; (2) two new smart buttons (Plating Job, Fischerscope status); (3) new "Thickness Report (Fischerscope)" notebook tab with clickable PDF preview + step-by-step instructions when none uploaded |
fusion_plating_certificates 19.0.5.2.0, fusion_plating_jobs 19.0.6.20.0 |
bt_s19_fischer_merge.py (asserts both pre-Issue pending + post-Issue merged status flips) |
| S20 | Tablet Station UX hardening — three real-world UX gaps surfaced during a persona walk on the Tablet + Manager Desk client actions | (a) Scrap reason dropped: /fp/shopfloor/bump_qty_scrapped accepted operator's typed reason via window.prompt, passed it through context as fp_scrap_reason — but fp.job.write never read it, so the auto-spawned Hold's description had the generic "OPERATOR: replace this text with the actual reason" placeholder instead of what Carlos typed. Audit trail lost what just happened on the floor. (b) KPI/panel mismatch: tablet KPI strip showed plant-wide totals ("Quality Holds: 12") but the Holds panel below was scoped to the operator's own jobs (might show 0). Operator stares at a big red 12, scrolls down, sees nothing — confused/distrustful. (c) UserError stack-trace leak: when start_wo hit an S14 predecessor lock (or any other button_start-side guard), the raw UserError propagated through the JSON-RPC handler and operator got a Python stack-trace dialog instead of the nice setMessage("...", "danger") flash. Same hole on stop_wo, start_bake, end_bake, mark_gate, bump_qty_done, bump_qty_scrapped. |
(a) fp.job.write now reads self.env.context.get('fp_scrap_reason') and prepends Operator reason: <text> to the Hold description so the audit row captures what the operator actually typed. (b) Tablet KPI strip now reuses my_job_ids_for_kpi (the operator's own steps) for awaiting_bakes, bake_in_progress, missed, open_holds — same scope as the panels below, so the strip never lies. Manager dashboard keeps its own plant-wide KPI set. (c) Wrapped every action endpoint in try: ... except UserError as e: return {'ok': False, 'error': str(e.args[0])} — operator now gets the clean setMessage flash with the real guard text ("Step 'X' requires predecessors done first…") instead of a stack-trace popup. |
fusion_plating_jobs 19.0.6.22.0, fusion_plating_shopfloor 19.0.24.4.0 |
persona walk via sim_tablet_actions.py + sim_reverify.py (asserts: typed reason ends up in hold.description, KPI=panel for holds, start_wo returns {ok:False, error:"..."} for locked step) |
| S20 | Tablet usability pass — operators were squinting at the tablet, scanning back-and-forth between recipe binders and the screen because the tablet showed step names but no targets, no live timer, no predecessor visibility. QC fail left parts in limbo with no Hold record. Manager Desk showed feel-good KPIs but hid the compliance bombs (missed bakes, stale steps, locked steps, holds, pending QC missing PDF) | Tablet My Queue rows had no instructions, thickness_target, dwell_time_minutes, bake_setpoint_temp, requires_signoff — operators kept scanning the QR code just to read the bake temperature. Steps with requires_predecessor_done=True (S14) showed a green Start that always failed with a UserError. Active step "duration" was a stale number that only refreshed every 30s. Holds and bake windows showed plant-wide noise from other crews. No banner alerted Carlos when his job had a pending QC (Lisa was not called → QC sat for hours). No way to bump qty_done or scrap from the tablet → S17 hold auto-spawn never fired because operators didn't update the field. action_fail on QC marked the check failed but spawned no Hold — AS9100 disposition trail broken. Manager Desk KPIs were missing 7 compliance metrics: stale paused/in-progress steps (cron data), missed bake windows, open holds, predecessor-locked steps, pending QCs, QCs missing Fischerscope PDF, draft cert pipeline |
Carlos's Shopfloor Tablet — every queue row now carries the recipe-author fields (instructions snippet, thickness target chip, dwell-time chip, bake-temp chip, sign-off badge) so operators read the targets inline. Predecessor-blocked steps render with a 🔒 lock icon, an "Awaiting [step name]" notice, and a disabled Locked button (no more Start-then-fail). Active step now shows a live ticking HH:MM:SS clock (1s interval, computed from date_started_iso JS-side; flips to red on >1.5× planned duration) plus +1 Done and Scrap buttons that hit two new endpoints (/fp/shopfloor/bump_qty_done, /fp/shopfloor/bump_qty_scrapped — scrap prompts for reason and S17 auto-spawns the Hold). New Pending QC banner lists open QCs for my jobs with line-progress + Fischerscope-PDF status badge, and a tap deep-links into Lisa's mobile QC checklist. Holds and bake windows are now scoped to my jobs first (fall back to facility-wide for managers). QC checklist — action_fail now auto-creates a fusion.plating.quality.hold with hold_reason='qc_failure' (new selection value), populated description listing the failed checks, idempotent on retry. Manager Desk — 7 new clickable compliance KPI tiles: Missed Bakes (S15), Open Holds (S17 + QC fail), Stale Steps (S10/S16 cron data), Locked Steps (S14), Pending QC + "X need PDF" (S19 + missing-Fischerscope), Draft Certs + "Y today" (cert pipeline). Each tile drills into a list filtered to the relevant exception |
fusion_plating_shopfloor 19.0.24.3.0, fusion_plating_quality 19.0.4.8.0 |
sim_tablet_walk.py, sim_timer_pred_test.py, sim_qc_fail_hold.py, sim_manager_qc_fail.py (one-off persona walkthroughs) |
Manager-bypass context flags
When you need to override a guard (documented customer deviation, emergency rework, etc.), set the context key on the call. All bypasses post to chatter with the user name for audit:
| Flag | Skips |
|---|---|
fp_skip_step_gate=True |
step-completion check on button_mark_done (S5/S8 era) |
fp_skip_qc_gate=True |
QC checklist requirement on button_mark_done |
fp_skip_qty_reconcile=True |
qty_done + qty_scrapped == qty check on button_mark_done |
fp_skip_bake_gate=True |
bake.window pending check on button_mark_done (S15) |
fp_skip_predecessor_check=True |
requires_predecessor_done check on button_start (S14) |
fp_skip_missed_window=True |
missed_window block on bake.window.action_start_bake (S6) |
Daily / hourly crons added by battle tests
| Cron | Schedule | What it does |
|---|---|---|
Fusion Plating: Nudge stale paused steps |
daily | 24h threshold, schedules activity on job for stale paused steps |
Fusion Plating: Nudge stale in-progress steps |
hourly | 8h threshold, sister cron for in_progress (phantom-time guard) |
Fusion Plating: Update Bake Window states |
every 5 min | (pre-existing) flips awaiting_bake → missed_window past required_by |
Open scenarios — flagged for next session
- S21 — Operator clocks two steps simultaneously across different jobs (multi-tasking conflict)
- S22 — Bath chemistry drift mid-step — operator measures bath while plating, value out of spec; no alert on the step
- S23 — Wrong recipe attached — Carlos sees mismatch with the part he's holding; recovery path?
- S24 — Customer orders 100 parts spread across 3 jobs; one job's recipe gets edited — does it propagate to siblings?
- S25 — Hold-aging cron + 3-day escalation (flagged in original audit, not yet built)
- S26 — Calibration + permit-expiry cron (flagged in original audit, not yet built)
- S27 — FAIR detection on first-shipment to a new customer/part combo (flagged in original audit, not yet built)
Tablet UI / persona-coverage gaps (S20 audit follow-ups)
The S20 walkthrough mapped 6 OWL apps (fp_shopfloor_tablet, fp_plant_overview, fp_process_tree, fp_manager_dashboard, fp_qc_checklist, fp_quality_dashboard) and surfaced these missing pieces. Each is a separate scenario for a future session:
- S28 — Bake Oven Operator dedicated tablet. HE-bake operators currently work from Carlos's tablet (Bake Windows panel). Real shops have a separate oven station; needs: oven-scoped queue (ovens they're certified on), countdown to
bake_required_by, one-tap Start/End, photo of chart recorder, daily history./fp/shopfloor/start_bake+end_bakealready exist — only a focused OWL action + menu item needed. - S29 — Tank-side chemistry logger.
/fp/shopfloor/log_chemistryendpoint exists in shopfloor controller but has no UI calling it. Plating tech walks the line, takes Hull Cell + concentration readings, has nowhere to log them on the tablet. Needs a "Log Bath Reading" action that hits the existing endpoint. - S30 — Receiving dock tablet. Mike works from desktop list/form. A tablet-friendly view at the dock would let him scan PO QR → counted → staged → closed without typing. Existing
fp.receivingstate machine + actions are tablet-ready; only OWL view missing. - S31 — Maintenance technician mobile work-orders.
fusion_plating_bridge_maintenanceshows kanban / list views but no tablet UI. Maintenance walks to broken equipment with a phone — needs "My Open Work Orders" mobile view with photo + start/finish + parts checkout. - S32 — Shipping/Logistics tablet. Tom uses cert form + delivery list. A "Today's Shipments" tablet would let him scan job QR → pull cert → mark delivered. The cert PDF + Send-to-Customer flow is already built in S18 — only a packaging/dispatch view is missing.
- S33 — Operator landing page after clock-in. When Carlos clocks in, the system has no "where do I go?" prompt. Should auto-route to Tablet Station with their station pre-paired (currently relies on manual scan or last-localStorage value).
Where the test scripts live
K:/Github/Odoo-Modules/fusion_plating/fusion_plating_quality/scripts/
battle_test.py— original S1–S8 (mixed, some not-bug scenarios)battle_test_v2.py— re-verify of S6/S7/S8/S2 fixesbt_s9_reassign.pythroughbt_s17_scrap_ncr.py— one script per scenariobt_s18_cert_flow.py— full CSR→operator→QC→shipper cert issuance + Send to Customerbt_s19_fischer_merge.py— uploads fake Fischerscope PDF to QC, asserts CoC + thickness merged into 2-page outputstep_internal_full.py— full pause/resume/skip/bake-spawn walk
To re-test the whole battle suite after a future change, run each bt_s*.py in sequence and confirm green.
Sub 12a / 12b / 12c — Simple Recipe Editor + Tablet Move/Rack/Timer + Reports (shipped 2026-04-27/28)
Three sequential sub-projects implementing Steelhead-replacement features for clients who prefer a simpler UX over the existing tree editor. All shipped on entech.
Spec: docs/superpowers/specs/2026-04-27-sub12-simple-recipe-editor-design.md (full design) Steelhead screen inventory: docs/superpowers/specs/2026-04-27-simple-recipe-editor-steelhead-screens.md (24 screens)
Sub 12a — Simple Recipe Editor + Step Library (versions: fusion_plating 19.0.10.0.0)
New models:
fp.step.template— reusable step library; tank_ids, target ranges (time/temp/voltage/viscosity),default_kindselection (15 kinds), input_template_ids + transition_input_ids,_seed_default_inputs()helper.fp.step.template.input— operation-measurement definitions (during step). 11 input_types: text, number, boolean, selection, date, signature, time_hms, time_seconds, temperature, thickness, pass_fail.fp.step.template.transition.input— compliance prompts fired on move-out. 9 input_types incl. photo, location_picker, customer_wo. compliance_tag selection (none/as9100/nadcap/cgp/nuclear).
Additive fields on fusion.plating.process.node (zero impact on tree editor):
is_templateBoolean (recipe-level — appears in Import Starter dropdown).source_template_idM2Ofp.step.template(snapshot trace; no live coupling).tank_idsM2M tofusion.plating.tank(via new join tablefp_node_tank_rel).material_callout,time_min/max_target,time_unit,temp_min/max_target,temp_unit,voltage_target,viscosity_target.requires_rack_assignment,requires_transition_form,default_kind,preferred_editor(tree/simple/auto).
Additive fields on fusion.plating.process.node.input:
kindSelection (step_input/transition_input, defaultstep_input).target_min,target_max,target_unit,compliance_tag.- 9 new typed input_type values appended (existing values preserved).
Settings: res.company.x_fc_default_recipe_editor (tree/simple).
OWL client action: fp_simple_recipe_editor — flat 2-pane drag-drop layout. Library on right, Selected on left. HTML5 drag-drop with two distinct dataTransfer types (application/x-fp-step vs application/x-fp-library) so the drop handler knows whether to reorder or snapshot-copy. Drop-position simulator (commit 3098fcf): green dashed reservation line snaps above/below each row based on cursor Y vs row midpoint, with ghost-preview chip showing dragged step's icon + name. 80ms transition glides between slots.
11 JSONRPC routes under /fp/simple_recipe/...:
load,library/{list,create,write,delete},step/{insert,write,remove,reorder},template/{list,import}.- Library + template imports SNAPSHOT-COPY fields (Q4 = A locked) — editing a library template later does NOT mutate recipes already built.
library/deleteis soft when any node references the template viasource_template_id.
Recipe form integration: 2 header buttons (Open Tree Editor / Open Simple Editor), is_template + preferred_editor fields, new "Step Authoring" notebook page for step/operation nodes.
_resolve_preferred_editor() + action_open_recipe_with_preferred_editor() — per-recipe preferred_editor wins; auto falls back to company default; final fallback tree.
Menu: Plating → Configuration → Step Library (later moved to Configuration → Recipes & Steps in Phase 2).
post_init_hook: backfills kind='step_input' on existing process.node.input rows; seeds 13–18 starter library templates from ENP-ALUM-BASIC recipe (idempotent — won't re-seed).
Naming gotcha: _seed_default_inputs was originally underscore-prefixed which Odoo 19 rejects when called from a view button — renamed to action_seed_default_inputs (commit 5494684). Public name required for any method called from XML buttons.
Sub 12b — Move Parts / Move Rack / Rack Parts / Stop Timer dialogs (versions: fusion_plating 19.0.10.1.0, fusion_plating_shopfloor 19.0.25.0.0)
Decisions adjusted from the original spec:
fusion.plating.rackalready existed (wear-tracking model withstateselection). Sub 12b adds an ORTHOGONALracking_statefield for the load lifecycle. The two states coexist — a rack can be wear-active AND racking-loaded simultaneously.fp.labor.timerwas NOT created. Instead, the existingfp.job.step.timelog(used by S1/S2 battle tests) is extended with a state machine. Single source of truth for labor; preserves S1/S2 paths.fp.job.step.rack_idalready existed and is reused as the "current rack on this step" pointer (no newcurrent_rack_id).
New models:
fp.rack.tag— M2M tag registry (Rush / Hold for QC / Damaged / Customer Sample seeded by post_init_hook).fp.job.step.move— chain-of-custody log, one row per Move Parts/Rack commit. FP/MOVE/YYYY/NNNN sequence. Carries from/to step + tank, transfer_type (step/hold/scrap/rework/split/return), qty_moved, to_location, photo_evidence_id, customer_wo_count, rack_id, moved_by_user_id.fp.job.step.move.input.value— captured transition prompt values per move. Typed dispatch on input_type → correct value_text/number/boolean/date/attachment column.
Extended fusion.plating.rack:
racking_state(empty/loading/loaded/in_use/awaiting_unrack/out_of_service) — orthogonal to existing wearstate.tag_idsM2M,capacity_count(soft warn), notes.current_job_step_id,current_tank_id,current_part_count(computes that walk fp.job.step.move history).
Extended fp.job.step:
requires_rack_assignment,requires_transition_form(related from recipe_node_id).move_ids(O2M from_step_id),incoming_move_ids(O2M to_step_id).is_racked(compute, stored, depends rack_id) — drives tablet rack-vs-parts greyed-button guard.qty_at_step_start,qty_at_step_finish.
Extended fp.job: qty_received, qty_visual_inspection_rejects, qty_rework, special_requirements, active_timer_ids (filtered O2M), move_ids.
Extended fp.job.step.timelog with persistent state machine:
stateSelection (running/paused/stopped/reconciled, default running — preserves S1/S2).last_paused_at,total_paused_seconds,accrued_seconds(compute).billed_hrs/min/sec,billed_total_seconds,billed_pct(compute).product_id(split-by-product reconciliation),notes.job_id(related, indexed) for fast O2M fromfp.job.active_timer_ids.
12 tablet controller endpoints in fusion_plating_shopfloor/controllers/move_controller.py:
- Move Parts:
/preview,/commit - Move Rack:
/preview,/commit - Rack Parts:
/commit - Rack picker:
/rack/list_empty,/rack/scan_qr - Persistent labor timer:
/labor_timer/{start,pause,resume,stop,reconcile}
Manager-bypass context flags (consistent with existing fp_skip_* protocol): fp_skip_predecessor_check, fp_skip_rack_assignment, fp_skip_transition_form. All bypasses post to chatter on the move record naming the user + which flags fired. Manager group check enforced.
_safe() wrapper: UserError → JSONRPC-friendly {ok: False, error: msg} so OWL components show a flash without crashing.
4 OWL dialogs (in fusion_plating_shopfloor/static/src/js/):
move_parts_dialog.js— mirror of Steelhead screens 1-3, 14-15. System-derived top section (Part Count / From Node / To Node / Transfer Type / To Station / To Location with camera button). Compliance Prompts section renders authored transition_input_ids. Blockers section (NEW pattern, our improvement over Steelhead): each blocker has inline Resolve button. Soft (amber + button enabled) vs hard (amber + button disabled with tooltip listing reasons). MOVE button greys out when blocked.move_rack_dialog.js— atomic multi-batch move. Rack name in title, tag chips, batches list, Type + To Node + To Station picker.rack_parts_dialog.js— searchable empty-rack picker, QR Scan input, Unit + Amount fields. Save / Save+Print (the latter opens/report/pdf/fusion_plating_reports.action_report_fp_rack_travel/<id>— gap closed in Sub 12c+ commit7d3b8f1).stop_timer_dialog.js— opens with state already atstopped(server flips on load), pre-fills billed_* from accrued. Cancel / Save / Save & Start New Timer (chains into a fresh timer for the same step).
Custom event protocol: fp-resolve-rack window CustomEvent fired from Move Parts dialog when operator clicks Resolve on a rack-required blocker → tablet listens → spawns Rack Parts sub-dialog inline. Cleanup on unmount.
Shopfloor tablet (shopfloor_tablet.js): wired Move Parts + Stop Timer button handlers; dialog service injected; rack-resolve event listener with cleanup on onWillUnmount.
Plant overview (plant_overview.js + XML): new top "Racks" pane shows racks in (loaded/in_use/awaiting_unrack) state with tag chips, current_part_count, breadcrumb (current node + tank code), MOVE RACK button per row. Backend /fp/shopfloor/plant_overview extended to return racks array alongside the existing parts/batches.
Operator UX rule: fp.job.step.is_racked drives the tablet's MOVE PARTS button grey-out. Operator MUST go through MOVE RACK when batch is racked — enforced by disabled button state, not error message.
post_init_hook: seeds 4 starter rack tags (idempotent).
Deploy gotcha: to_step_id was originally required=True, ondelete='set null' — Odoo 19 disallows that combination. Switched to ondelete='restrict' (commit e718a47). Audit-safety bonus: destination steps can't be unlinked while move-log rows reference them.
Sub 12c — Reports + Labor History screen (versions: fusion_plating 19.0.10.2.0, fusion_plating_jobs 19.0.7.0.0, fusion_plating_reports 19.0.10.0.0, fusion_plating_certificates 19.0.5.3.0)
Re-scoped from the original 18-task plan to 5 tasks after auditing existing artifacts: report_coc_en / report_coc_fr already had Nadcap / AS9100 / CGP infrastructure built into fusion_plating_reports. company.x_fc_nadcap_logo etc. already existed.
Operator Traveller v2 (fusion_plating_jobs/report/report_fp_job_traveller.xml):
- A4 landscape paper-style (matching Amphenol screens 16-18), replaces the minimal portrait template.
- Header: company logo + Code 128 barcode + WO# + Date In + Due Date + Type + Order# + PO# + WO-Generated-By + customer block.
- Item Information: Part# / Rev / Mat / Catg / S/N + Item-Name + Qty Rec / VIS INSP / Rework / Special Requirements / Stamp-Date.
- Process-Sheet header: recipe name + category + spec/info.
- Routing table (11 cols): Step / Tank / Operation+Actuals / Instruction / Unit / Material / Voltage / Time(min) / Temp / Stamp / Date.
- Targets pulled from recipe-node fields when present (Sub 12a authored), 'N/A' otherwise.
- Defensive QWeb — every cross-module field guarded via
'X in record._fields'. - New paperformat
paperformat_fp_traveller_landscape.
Chronological CoC body (fusion_plating_reports/report/report_coc_chronological.xml):
- New
coc_body_chronologicaltemplate walksfp.job.step.moverecords ordered bymove_datetime. - Per-move heading
<step.name> (<tank.code>)+ "Moved By / Time / Qty" meta line. - 5-column measurement sub-table (Name / Description / Target / Actual / Recorded By) when destination step has captured inputs OR move has captured
transition_input_value_ids. - Actual column (gap-fix commit
7d3b8f1): buildscaptured_values_by_inputdict frommv.transition_input_value_ids, renders typed values (text as-is, number with target unit, boolean as PASS/FAIL, datetime formatted, attachment placeholder). - New router template
coc_body_routerpicks chronological vs classic body viafp.certificate.body_stylefield. - Both English + French CoC actions (
report_coc_en,report_coc_fr) rerouted through the router. Existing certs default toclassicso no regressions.
fp.certificate.body_style Selection (classic/chronological), default classic. Surfaced on cert form alongside certified_by_id.
Per-customer cert statement (gap-fix 7d3b8f1): 3-tier resolution.
res.partner.x_fc_cert_statementText (per-customer override, surfaced on partner form under Cert + Document Routing block).res.company.x_fc_default_cert_statementText (company-level fallback).- Hardcoded AS9100 / ISO 9001 boilerplate as final fallback.
Rack Travel Ticket PDF (gap-fix 7d3b8f1) in fusion_plating_reports/report/report_fp_rack_travel.xml:
- A5 landscape, 28pt rack name, Code 128 barcode of
FP-RACK:<name>, tag chips, contained-batches table (qty / part number / WO / customer / current step). - Bound to
fusion.plating.rackmodel — appears in the rack form's Print menu. - Closes Sub 12b's Save+Print 404 placeholder.
Labor History screen (fusion_plating/views/fp_job_step_timelog_views.xml):
- Plating → Operations → Labor History (sequence 64).
- List view colour-coded by state, with
billed_pctprogressbar. - 8 search filters (My Timers default, Today, Running, Paused, Pending Reconciliation, Reconciled) + Group-by Operator/Job/Date.
- Form view: identity readonly, billed_hrs/min/sec editable for supervisors+ until
state=reconciled.create=false(timers are runtime-produced via tablet). - ACL rows for
fp.job.step.timelog: operator (rwc, no unlink), supervisor (rwc, no unlink), manager (full).
Other sub-12 era ergonomics shipped in this session
- Tank model (commit
cfe776b):code→ "Tank Number",name→ "Tank Name". Header buttons for state transitions (Mark Empty/Filled/In Use/Draining/Maintenance/Out of Service) with chatter audit logging. - Plating app default landing screen (commit
cfe776b):menu_fp_root.action→action_fp_sale_orders(later replaced by Phase 1 resolver server action). - WO label (commit
cfe776b): SO smart-button "Plating Jobs" → "WO". - Drop-position simulator in Simple Recipe Editor (commit
3098fcf): green dashed reservation line + ghost chip showing exactly where the drop will land. Snaps above/below row midpoint based on cursor Y. 80ms transition.
Phase 1 / 2 / 3 — Menu reorganization (shipped 2026-04-28)
Customer feedback: "too many top-level menus" + "configuration is unorganized". Three-phase reshuffle reduces 17 top-levels to 6 (operator-visible), groups the flat 36-entry Configuration into 7 themed folders, and tightens role-based visibility.
Phase 1 — Top-level consolidation + landing-page resolver (fusion_plating 19.0.11.0.0, commit 0ad382e)
New top-level structure (manager view):
🏭 Plating (action = landing resolver — see below)
├── 📊 KPIs [seq 85, supervisor+]
├── 💰 Sales & Quoting (Sales + Configurator)
├── 🔧 Operations [seq 18]
│ ├── Process Recipes, Baths, Chemistry Logs, Tanks, Racks
│ ├── Replenishment Suggestions [Phase 3: supervisor+]
│ ├── Maintenance [Phase 1: re-parented from top]
│ ├── Move Log [Phase 1+3: re-parented + supervisor+]
│ └── Labor History [Phase 1: re-parented from top]
├── 📦 Receiving & Shipping
├── ✅ Quality
│ └── Certificates [Phase 1: re-parented from top]
├── 📋 Compliance [seq 50, supervisor+]
│ ├── General ← was top-level Compliance
│ ├── Safety / WHMIS ← was top-level Safety
│ ├── Aerospace (AS9100 / Nadcap) ← was top-level
│ ├── Nuclear (CSA N299 / CNSC) ← was top-level
│ └── Controlled Goods (CGP) ← was top-level
└── ⚙ Configuration [seq 90, manager-only]
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→fusion_plating_quality.menu_fp_qualityfusion_plating_bridge_maintenance.menu_fp_maintenance→fusion_plating.menu_fp_operationsfusion_plating.menu_fp_job_step_move(Move Log) →menu_fp_operationsfusion_plating.menu_fp_job_step_timelog(Labor History) →menu_fp_operations
Landing-page resolver (fusion_plating/data/fp_landing_data.xml):
ir.actions.servernamedaction_fp_resolve_plating_landing. Code in the action: user override → company default → Sale Orders fallback.menu_fp_rootrewired to call this server action.- New fields:
ir.actions.act_window.x_fc_pickable_landing— Boolean tag for curated picklist.res.company.x_fc_default_landing_action_id— admin sets fallback.res.users.x_fc_plating_landing_action_id— per-user override.
- UI surfaces in
fusion_plating/views/fp_landing_views.xml:- User Profile / Preferences → Fusion Plating tab (per-user dropdown).
- Settings → Fusion Plating → Plating Landing Page block (company default).
fusion_plating_configurator's earlier menu_fp_root override (action_fp_sale_orders direct) was removed — core's resolver now owns the routing.- Pickable list is curated via inline
<field name="x_fc_pickable_landing" eval="True"/>on action records — currently flagged:action_fp_sale_orders,action_fp_quotations,action_fp_process_recipe. Add more by tagging the relevant act_window record at its source.
Phase 2 — Configuration sub-folder grouping (fusion_plating 19.0.11.1.0, commits 3641b78 + 62c1315 + 4671541)
7 themed folders + Settings sibling:
⚙ Configuration [manager-only]
├── ⚡ Settings (sequence 1, sibling)
├── 🏢 Shop Setup (10)
│ ├── Facilities, Production Lines, Routing Stations,
│ ├── Process Categories, Process Types,
│ └── Bake Ovens, Shopfloor Stations, Vehicles
├── 📜 Recipes & Steps (20)
│ └── Step Library, QC Checklist Templates, Quality Points
├── 🧪 Materials & Tanks (30)
│ ├── Bath Parameters, Replenishment Rules, Chemicals,
│ └── Rack Tags, Calibration Equipment, Calibration Events
├── 👥 Workforce (40)
│ └── Operator Certifications, Shop Roles, Training Types, Quality Teams
├── 📝 Quality & Documents (50)
│ ├── Customer Specs, Approved Vendor List,
│ ├── Quality Tags, Quality Reasons, Quality Stages, N299 Levels,
│ └── Notification Templates, Notification Log
├── 💵 Pricing & Billing (60)
│ └── Invoice Strategy Defaults, Account Holds
└── 🔁 Reference Data (70)
└── Value Sets, Value Rotations
The 7 bucket folders are defined in fusion_plating/views/fp_menu.xml. Touched 11 module XML files to re-parent existing children:
fusion_plating_invoicing→ Pricing & Billingfusion_plating_notifications→ Quality & Documentsfusion_plating_safety→ Workforce + Materials & Tanksfusion_plating_shopfloor→ Shop Setupfusion_plating_logistics→ Shop Setup (Vehicles)fusion_plating_culture→ Reference Datafusion_plating_nuclear→ Quality & Documents (N299 Levels)fusion_plating_quality→ Materials & Tanks (Calibration), Quality & Documents (Specs/AVL/Tags/Reasons/Stages), Workforce (Quality Teams), Recipes & Steps (Quality Points + QC Templates)
Critical load-order rule (caught by entech upgrade 62c1315 + 4671541):
- Every parent menuitem MUST be defined before any child references it by xmlid. Odoo's data loader is strictly sequential — within a single XML file AND across the manifest's
datalist. fp_menu.xmlwas reorganized so its declaration order is: Root → Configuration + 7 buckets → Compliance hub → Operations parent → all children.- The manifest's
datalist was reordered to loadviews/fp_menu.xmlBEFORE any view file that references the bucket xmlids (e.g.fp_rack_tag_views.xml, downstream module views). - Lesson for future menu reshuffles: when adding a new bucket folder, define it in
fp_menu.xmlnear the top, AND make sure that file loads early in the manifest data list.
Phase 3 — Tightened group-gating (fusion_plating 19.0.11.2.0, fusion_plating_kpi 19.0.1.1.0, commit 5f6c7af)
Three targeted gates so operators no longer see admin/audit views:
menu_fp_dashboard(KPIs) →groups="fusion_plating.group_fusion_plating_supervisor". Operators don't need dashboards.menu_fp_job_step_move(Move Log) → supervisor+. Operators see their own moves on the tablet; this top-level menu is the audit-of-everyone-else view.menu_fp_replenishment_suggestions→ supervisor+. Purchasing decision, not operator concern.
Net effect by role:
| Top-level | Operator | Supervisor | Manager |
|---|---|---|---|
| Sales / Configurator | — | ✓ (if estimator) | ✓ |
| Shop Floor | ✓ | ✓ | ✓ |
| Operations | ✓ | ✓ | ✓ |
| Receiving & Shipping | ✓ (if receiving) | ✓ | ✓ |
| Quality | ✓ | ✓ | ✓ |
| KPIs | — | ✓ | ✓ |
| Compliance (hub) | — | ✓ | ✓ |
| Configuration | — | — | ✓ |
Operator now sees ~5 top-level menus instead of the previous ~10.
Production Line / Routing Station rename (commit afcd128, fusion_plating 19.0.11.3.0)
Two distinct entities were both labelled "Work Centre" / "Work Centers" — only the US/UK spelling differentiated them. Renamed by purpose:
| Model | Old display | New display | What it is |
|---|---|---|---|
fusion.plating.work.center |
Work Centers | Production Lines | Physical shop-layout grouping that owns tanks. Has tank_ids, supported_process_ids, capacity_per_day. |
fp.work.centre |
Work Centres | Routing Stations | Per-job-step routing entity (post-Sub-11 mrp.workcenter replacement). Has kind (wet_line/bake/mask/rack/inspect), cost_per_hour, default_bath_id, default_tank_id. |
Conceptually a Production Line CONTAINS many Routing Stations.
Model IDs unchanged (12 + 9 cross-refs preserved). Updated: _description on both models, string= on name fields, list/form/search view strings, act_window names, menu items, doc comments.
Naming convention recap (Plating menu hierarchy as of 2026-04-28)
When adding a new menu, default to one of these 6 top-level homes:
- Sales & Quoting — quote/order workflows, customers, parts catalog
- Operations — recipes, baths, tanks, racks, jobs, move log, labor, maintenance
- Receiving & Shipping — inbound/outbound logistics
- Quality — holds, NCRs, CAPAs, certificates, FAIR, audits, doc control
- Compliance (hub) — General / Safety / Aerospace / Nuclear / CGP
- Configuration (manager-only) — Settings + 7 themed folders
Avoid creating a new TOP-LEVEL menu under menu_fp_root unless it's a genuinely new domain. Most new functionality belongs as a child of an existing top-level.
When adding a new admin config, drop it into the right Configuration folder:
- Equipment / physical infrastructure → Shop Setup
- Recipe authoring → Recipes & Steps
- Chemicals, baths, calibration → Materials & Tanks
- People, roles, training → Workforce
- Specs, vendors, quality categorisation, customer notifications → Quality & Documents
- Pricing rules, account holds → Pricing & Billing
- Generic value lists → Reference Data
Don't add new top-level Configuration entries (siblings of the 7 folders) unless absolutely necessary — Settings is the only one allowed.