Files
Odoo-Modules/fusion_plating/CLAUDE.md
gsinghpal f51976cb08 changes
2026-04-27 08:48:55 -04:00

76 KiB
Raw Blame History

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)

The Plating app (menu_fp_root, seq 46) has these top-level menus:

Seq Menu Module Children
3 KPIs fusion_plating_kpi KPIs, KPI History, Production/Quality/Finance dashboards
5 Sales fusion_plating_configurator + portal Quotations, Sale Orders, Customers, Part Catalog, Quote Requests, Portal Jobs
8 Configurator fusion_plating_configurator New Quote, Coating Configs, Pricing Rules, Treatments
12 Shop Floor fusion_plating_shopfloor Plant Overview, Tablet Station, Bake Windows, First-Piece Gates
15 Receiving fusion_plating_receiving All Receiving, Pending Inspection, Discrepancies
18 Operations fusion_plating (core) Process Recipes, Production Priorities (bridge_mrp), Batches (batch), Baths, Chemistry Logs, Tanks
25 Certificates fusion_plating_certificates All, CoC, Thickness Reports
30 Quality fusion_plating_quality Holds, NCRs, CAPAs, FAIR, Audits, Doc Control
40 Compliance fusion_plating_compliance Permits, Discharge, Waste, Calendar, Spills, Config
45 Safety fusion_plating_safety SDS, Training, Exposure, JHSC, Incidents, PPE
50 Logistics fusion_plating_logistics + fusion_tasks Pickups, Deliveries, Routes, CoC, POD, Field Tasks, Task Map, Task Calendar
60 Aerospace fusion_plating_aerospace AS9100, Nadcap, Counterfeit, Config Items, Risk
65 Nuclear fusion_plating_nuclear Program, ITP, 10CFR21, Pedigree, CNSC
70 CGP fusion_plating_cgp Registration, AI, PSA, Visitors, Goods, Shipments, Security, Access Log
80 Culture fusion_plating_culture Values, Recognitions — RETIRED, uninstalled on entech, code kept in repo only
90 Configuration fusion_plating (core) + many Facilities, Work Centres, Process Categories/Types, Bath Params, Stations, Ovens, Invoice Strategy, Account Holds, Training Types, Chemicals, Notification Templates/Log, Calibration, Specs, AVL, Value Sets/Rotations, N299 Levels, Vehicles

Field Service (fusion_tasks) also has its own standalone root app (seq 45) with Map View, Tasks, Calendar, Configuration. The same task actions are also accessible under Plating > Logistics.

Key rule: Sales menu is unified in fusion_plating_configurator. Portal module adds Quote Requests + Portal Jobs as children (referencing fusion_plating_configurator.menu_fp_sales). Do NOT create a separate Sales menu in portal.

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 -i or -u sequence that installs modules automatically.
  • Add them as a depends target 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

  1. NEVER code from memory — Read reference files from the server first.
  2. Backend OWL: static template, static props = ["*"], standalone rpc() from @web/core/network/rpc. NOT useService("rpc").
  3. HTTP routes: type="jsonrpc" — NOT type="json" (deprecated in Odoo 19).
  4. Search views: NO group expand="0", NO string attribute on <search>, NO <group string="..."> wrapper for group-by filters. Use bare <group> for group-by.
  5. res.config.settings: Only boolean/integer/float/char/selection/many2one/datetime. NO Date fields.
  6. res.groups: Use privilege_id (NOT category_id). user_ids is OK but the deprecated users alias is NOT. Always include sequence field.
  7. Field params: parent_path does NOT accept unaccent parameter in Odoo 19.
  8. SCSS borders: Use $border-color (SCSS variable) for card borders, NOT color-mix() in border shorthand — the SCSS compiler drops it. color-mix() works fine in background-color, box-shadow, etc.
  9. 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. See fusion_plating_shopfloor.scss as the gold standard.
  10. XML comments: No double-hyphens (--) inside <!-- --> comments — invalid XML, causes lxml parse error.
  11. XML data ordering: Window actions must be defined BEFORE <menuitem> elements that reference them in the same file.
  12. Module install on new modules: Use --update=base alongside -i MODULE to ensure Odoo rescans the addons path and finds the new module directory.
  13. Implied group cascade: implied_ids on res.groups does NOT reliably propagate to users on module install. Always include user_ids to 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_id field 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 (an action_view_X returning 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 use string="Foo" on the <button> itself when you want a count — that produces a label-only button (the empty BOM Items issue 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 plain Label button 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.production is 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 Orders button 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')Environment in Odoo 19 has no get. Use 'model.name' in self.env then self.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" and widget="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, and context.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)
  • icon is 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_editor client 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_overview in fusion_plating_shopfloor
  • Kanban columns = work centres, cards = active mrp.workorder records
  • Drag & drop between columns (writes workcenter_id on 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, user odoo)
  • 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.conf or Odoo won't find the repackaged enterprise addons

odoo-trial (VM 316 on pve-worker1)

  • Type: Docker (container odoo-trial-app, db odoo-trial-db)
  • DB: trial (user odoo)
  • 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 decisions
  • fusionapps.issues — known issues and fixes
  • fusionapps.code_snippets — reference code
  • fusionapps.quick_commands — deployment and admin commands
  • fusionapps.vm_registry — VM inventory
  • fusionapps.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_id on mrp.production → links MO to recipe
  • fusion.plating.job.node.override → per-job opt-in/out decisions
  • fp.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

  1. Recipe → WO: One WO per operation node, child step nodes become numbered instructions in WO description
  2. Account hold: Manual flag on res.partner (auto from aging is roadmap)
  3. Email triggers: SO confirmed, parts received, invoice posted (configurable per trigger)
  4. Configurator: Custom build with formula-based pricing, estimator override, portal self-service wizard
  5. Model naming: New models use fp.* prefix, existing keep fusion.plating.*
  6. 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_enterprise and mail_enterprise are 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)

  1. Single-source cert resolution functionmrp.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.
  2. Shared QWeb line-header macrofusion_plating_reports.customer_line_header renders part_number + revision + customer-facing description with 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.
  3. Isolated migration — Sub 2's post_init_hook is idempotent (NULL/empty checks). Safe to re-run. Doesn't fight Sub 3/4/5/6 migrations.
  4. Additive SO line fieldsx_fc_internal_description, x_fc_description_template_id sit alongside future Sub 5 fields (x_fc_serial_number, x_fc_job_number, x_fc_thickness, x_fc_revision_snapshot) with zero touchpoints.
  5. Clean removal of old description column — 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.partner with 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_report flags that Sub 2 currently falls back to. Sub 2's _fp_resolve_cert_requirement is the update point.

Sub 7 Preview — IoT Tuning (client transcript D)

  • 610 active tanks (of ~2025 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_iot module).
  • Work scope: ensure per-sensor interval field exists + defaults + seed 610 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:

  1. Customer ships parts in boxes. Receiver counts boxes (does NOT inspect individual parts).
  2. Boxes sit in staging until racking.
  3. Racking crew opens boxes, inspects each part as they load racks (inspection ≠ receiving).
  4. Parts go through plating process.
  5. Post-plate QC on machine (thickness / depth / coating thickness) — existing QC gate (Phase 13 work).
  6. 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.docx to 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: 1530 min, half-hour acceptable
  • Active tanks: 610 (not all 2025)
  • 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

  1. Read this section (Fine-Tuning Initiative).
  2. Check the sub-project status table — which sub is in flight.
  3. Read the corresponding spec in docs/superpowers/specs/YYYY-MM-DD-sub<N>-*-design.md.
  4. Read the implementation plan if one exists.
  5. 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.job rows, 1,800+ fp.job.step rows 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

  1. Never re-introduce 'mrp' as a manifest dep. Use fp.job for jobs, fp.job.step for operations.
  2. x_fc_job_id is the canonical job link, not production_id. Drop legacy MO refs as you find them.
  3. fusion_plating_quality depends on fusion_plating_shopfloor for SCSS tokens ($fp-page, $fp-card, $fp-accent). Don't strip that dep — the QC tablet bundle breaks without it.
  4. The QC tablet OWL template namespace is fusion_plating_quality.FpQcChecklist (was fusion_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 (D1D8 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: state draft → authorised. Generate the RMA authorisation PDF + email link/QR to customer (using fp.notification.template if installed; falls back to standard mail.template).
  • action_mark_shipped_to_us: customer-driven; updates state when carrier scan logged.
  • On fp.receiving create with rma_id set: state → received. If auto_spawn_ncr, create an fusion.plating.ncr pre-filled (description, severity, customer, parent SO line). If auto_spawn_hold, create fusion.plating.quality.hold for the returned qty.
  • action_triage_complete: state → triaged. Requires resolution_type set.
  • action_resolve: state → resolved. Triggers resolution-specific actions:
    • replace → spawn new fp.job cloned from original
    • rework → spawn new fp.job referencing the returned units (linked to inbound fp.receiving)
    • refund → open account.move.refund wizard, link result to refund_invoice_id
    • scrap → create fp.job.consumption row 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') to fusion.plating.ncr AND fusion.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, description
  • trigger_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 (M2O fp.qc.checklist.template) — required, the checks to spawn
  • assignee_user_id (M2O res.users) — optional default inspector
  • Fires _spawn_check_for(<source_record>) which creates a fusion.plating.quality.check from the template + binds it to the source via job_id or step_id.

Hooks (already partly in place — extend)

  • fp.receiving.write hook (existing): when state flips to closed, walk all fp.quality.point with trigger receiving_done matching the receiving's partner/parts → spawn checks.
  • fp.job.action_confirm hook (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 by fp_qc_data.xml.
  • fp.job.button_mark_done: trigger job_done points.
  • fp.job.step.button_finish: trigger job_step_done points.

Phase D — Integration polish (~1 day)

  1. fp.job form smart-button row: add Holds, Checks, NCRs, CAPAs, RMAs buttons with badge counts. Always-visible (zero is OK).
  2. sale.order form smart-button row: same five, rolled up across all linked jobs.
  3. res.partner form: customer-level "Quality History" smart button that opens a kanban filtered to that partner across all 5 record types.
  4. One-click cross-creation:
    • Hold form → Open NCR button — pre-fills NCR with hold's part / customer / quantity / linked job.
    • NCR form → Spawn CAPA button — visible when state ∈ {disposition, closed} and severity ≥ medium.
    • CAPA form → Verify Effectiveness button — schedules a follow-up check on the originating NCR.
  5. Unified Quality Dashboard (fp_quality_dashboard client action):
    • 5 tabs: Holds / Checks / NCRs / CAPAs / RMAs
    • Each tab is a kanban grouped by stage_id (NCRs/RMAs) or state (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
  6. 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

  1. 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.
  2. 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.
  3. Monthly Quality Summary (fp_quality_monthly report):
    • 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:

  1. Customer reports issue → create RMA → authorise → email PDF
  2. Customer ships → carrier delivers → fp.receiving auto-created → RMA receives → NCR + Hold auto-spawn
  3. QA triages NCR → finds root cause → spawns CAPA (auto via severity rule)
  4. CAPA assigned to engineering → action plan written → implemented → effectiveness check scheduled
  5. Effectiveness verified → CAPA closes → NCR closes → RMA resolves (rework path) → replacement job created from original → ships → CoC issued → invoice
  6. Run 8D report on the closed NCR/CAPA pair
  7. Verify dashboard counts update at every state transition
  8. Confirm legacy NCR/CAPA/Hold/Check forms still work (no regressions)
  9. ACL drilldown: operator sees what they should, supervisor more, manager all

Phase G — Drop Odoo quality cascade (~30 min)

Pre-conditions: Phases AF all merged + smoke-tested.

  1. 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)
  2. Remove fusion_plating_bridge_quality from /mnt/extra-addons/custom/
  3. SQL: UPDATE ir_module_module SET state='to remove' WHERE name IN ('fusion_plating_bridge_quality', 'quality_control', 'quality') AND state='installed';
  4. Restart odoo → cascade uninstall fires
  5. ALTER TABLE drop the three NCR columns
  6. (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__.py for migrations to fire (current fusion_plating_quality: 19.0.3.0.0; bump to 19.0.4.0.0 for Sub 12).

Build order (executable checklist for fresh session)

  1. Read this Sub 12 section in full + the Sub 11 section above (for context on what's already native).
  2. Bump fusion_plating_quality/__manifest__.py version to 19.0.4.0.0.
  3. Phase A — RMA: create fp_rma.py model + fp_rma_views.xml + fp_rma_sequence.xml + ACL rows + add to __manifest__.py data list.
  4. Phase A migration: not needed (new model, fresh table).
  5. Phase B — categorisation: create the 4 small models + their views + ACL. Add tag_ids / reason_id / team_id M2M/M2O to NCR, CAPA, Hold, Check, RMA. Add stage_id to NCR + RMA.
  6. Phase B data: seed default stages + a few starter tags/reasons/teams in fp_quality_categorisation_data.xml.
  7. Phase C — fp.quality.point model + view + ACL + the 4 trigger hooks (in fp.receiving, fp.job.action_confirm, fp.job.button_mark_done, fp.job.step.button_finish).
  8. Phase D — smart buttons on fp.job, sale.order, res.partner. Cross-creation buttons. Dashboard client action.
  9. Phase E — three QWeb reports.
  10. Phase F — manual smoke test + ACL drilldown + screenshot the dashboard.
  11. Deploy each phase as it lands (don't batch — easier to roll back). Bump version each time.
  12. Phase G runs LAST, only after confirmation that AF 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/*, and fusion_plating_quality depends on fusion_plating_shopfloor for SCSS tokens.
  • Don't re-introduce production_id references anywhere. Use job_id / x_fc_job_id. MRP is gone.
  • Don't forget rma_id inverse 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*.py you 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.stepbutton_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 checklistaction_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_bake already exist — only a focused OWL action + menu item needed.
  • S29 — Tank-side chemistry logger. /fp/shopfloor/log_chemistry endpoint 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.receiving state machine + actions are tablet-ready; only OWL view missing.
  • S31 — Maintenance technician mobile work-orders. fusion_plating_bridge_maintenance shows 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 S1S8 (mixed, some not-bug scenarios)
  • battle_test_v2.py — re-verify of S6/S7/S8/S2 fixes
  • bt_s9_reassign.py through bt_s17_scrap_ncr.py — one script per scenario
  • bt_s18_cert_flow.py — full CSR→operator→QC→shipper cert issuance + Send to Customer
  • bt_s19_fischer_merge.py — uploads fake Fischerscope PDF to QC, asserts CoC + thickness merged into 2-page output
  • step_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.