diff --git a/fusion-plating/docs/superpowers/specs/2026-04-12-entech-plating-workflow-design.md b/fusion-plating/docs/superpowers/specs/2026-04-12-entech-plating-workflow-design.md index 3a024b51..7a2b4b0d 100644 --- a/fusion-plating/docs/superpowers/specs/2026-04-12-entech-plating-workflow-design.md +++ b/fusion-plating/docs/superpowers/specs/2026-04-12-entech-plating-workflow-design.md @@ -11,6 +11,17 @@ Complete end-to-end ERP workflow for an electroless nickel plating shop, replacing Steelhead Software. The system covers customer inquiry through invoicing, with a quotation configurator, parts receiving/inspection, flexible invoicing strategies, automated email notifications, certificate management, and local delivery dispatch. +### Model Naming Convention + +| Context | Convention | Example | +|---------|-----------|---------| +| New custom models (all new modules) | `fp.*` prefix | `fp.part.catalog`, `fp.certificate` | +| Existing custom models (already in codebase) | Keep `fusion.plating.*` | `fusion.plating.portal.job`, `fusion.plating.delivery` | +| New fields on standard Odoo models | `x_fc_*` prefix | `x_fc_po_number` on `sale.order` | +| Legacy fields (from Studio era) | `x_studio_*` | Preserved, not renamed | + +The `fp.*` prefix is the new short-form convention for all models created in the new modules. Existing `fusion.plating.*` models are NOT renamed — they keep their current `_name`. All references in this spec use canonical `_name` values. + ### Architecture: Approach 2 — Dedicated Modules per Sub-System | Module | Purpose | Location | @@ -19,10 +30,11 @@ Complete end-to-end ERP workflow for an electroless nickel plating shop, replaci | `fusion_plating_receiving` | Parts receiving, inspection, damage logging, PO matching | `fusion-plating/` | | `fusion_plating_invoicing` | Invoice strategy engine (deposit/progress/net/COD), account holds | `fusion-plating/` | | `fusion_plating_notifications` | Auto-email engine, notification templates, audit log | `fusion-plating/` | +| `fusion_plating_certificates` | Certificate registry (CoC, thickness, Nadcap, customer-specific) | `fusion-plating/` | | `fusion_tasks` | Local delivery dispatch (forked from Westin, stripped of claims) | `Entech Plating/` | Plus updates to existing modules: -- `fusion_plating_bridge_mrp` — Recipe-to-WO generation, manufacturing gates +- `fusion_plating_bridge_mrp` — Recipe-to-WO generation (lightweight; gates live in their own modules) - `fusion_plating_portal` — Customer-facing configurator UI, 3D preview - `fusion_plating_reports` — CoC template updates, thickness report template - `fusion_plating_quality` — Thickness reading model for Fischerscope data @@ -43,7 +55,7 @@ Fusion Plating (app) │ ├── Customers (res.partner, customer_rank > 0) │ └── Part Catalog (fp.part.catalog) ├── Configurator -│ ├── New Quote (fp.quote.configurator wizard) +│ ├── New Quote (fp.quote.configurator, persistent model) │ ├── Coating Configurations (fp.coating.config) │ └── Pricing Rules (fp.pricing.rule) ├── Manufacturing @@ -90,17 +102,21 @@ Fusion Plating (app) ### Permission-Based Visibility -| Group | Sees | -|-------|------| -| `fp_group_estimator` | Sales, Configurator, Customers, Part Catalog | -| `fp_group_shop_manager` | Everything (full menu) | -| `fp_group_shop_floor` | Manufacturing, Work Orders, Plant Overview only | -| `fp_group_receiving` | Receiving & Inspection, can view Sales (read-only) | -| `fp_group_shipping` | Shipping & Delivery, can view Sales (read-only) | -| `fp_group_quality` | Quality, can view Manufacturing | -| `fp_group_accounting` | Sales (invoicing fields), Reports | +The existing codebase defines a 4-level privilege hierarchy in `fusion_plating/security/fp_security.xml`: Operator → Supervisor → Manager → Administrator. These new groups are **role-based** and work **alongside** (not replacing) the existing privilege levels. A user has both a privilege level (what they can do: read/write/create/delete) and one or more roles (what they can see: which menus appear). -These are Fusion Plating security groups layered on top of standard Odoo groups. +| Role Group | Menu Visibility | Required Privilege Level | +|------------|----------------|------------------------| +| `fp_group_estimator` | Sales, Configurator, Customers, Part Catalog | Supervisor+ | +| `fp_group_shop_manager` | Everything (full menu) | Manager+ | +| `fp_group_shop_floor` | Manufacturing, Work Orders, Plant Overview only | Operator+ | +| `fp_group_receiving` | Receiving & Inspection, can view Sales (read-only) | Operator+ | +| `fp_group_shipping` | Shipping & Delivery, can view Sales (read-only) | Operator+ | +| `fp_group_quality` | Quality, can view Manufacturing | Supervisor+ | +| `fp_group_accounting` | Sales (invoicing fields), Reports | Supervisor+ | + +Users are assigned to one or more role groups. The existing privilege hierarchy controls CRUD permissions; role groups control menu/view visibility. `fp_group_shop_manager` implies all other role groups (full access). + +Standard Odoo groups are still required for underlying model access (e.g. `sales_team.group_sale_salesman` for SO access). --- @@ -146,7 +162,7 @@ These are Fusion Plating security groups layered on top of standard Odoo groups. |-------|------|-------------| | `name` | Char | e.g. "EN Mid-Phos AMS 2404" | | `process_type_id` | Many2one fusion.plating.process.type | Process type | -| `recipe_id` | Many2one fusion.plating.process.node | Default recipe | +| `recipe_id` | Many2one fusion.plating.process.node | Default recipe. **Domain: `[('node_type', '=', 'recipe')]`** | | `phosphorus_level` | Selection | low_phos, mid_phos, high_phos, na | | `thickness_min` | Float | Min thickness | | `thickness_max` | Float | Max thickness | @@ -164,6 +180,7 @@ These are Fusion Plating security groups layered on top of standard Odoo groups. | `name` | Char | e.g. "Bead Blast", "Zincate", "Bake" | | `treatment_type` | Selection | pre, post | | `default_duration_minutes` | Float | Estimated duration | +| `currency_id` | Many2one res.currency | Company currency (default) | | `default_cost` | Monetary | Cost per application | #### `fp.pricing.rule` — Formula-Based Pricing Engine @@ -175,9 +192,10 @@ These are Fusion Plating security groups layered on top of standard Odoo groups. | `substrate_material` | Selection | Optional filter | | `certification_level` | Selection | Optional filter | | `pricing_method` | Selection | per_sqin, per_sqft, per_piece, flat_rate, formula | +| `currency_id` | Many2one res.currency | Company currency (default) | | `base_rate` | Monetary | $ per unit | | `thickness_factor` | Float | Multiplier per mil of thickness | -| `complexity_surcharge` | Text/JSON | Mapping complexity → surcharge % | +| `complexity_surcharge_ids` | One2many fp.pricing.complexity.surcharge | Surcharges by complexity level | | `masking_rate_per_zone` | Monetary | $ per masking area | | `setup_fee` | Monetary | One-time per batch | | `minimum_charge` | Monetary | Floor price | @@ -185,10 +203,22 @@ These are Fusion Plating security groups layered on top of standard Odoo groups. | `sequence` | Integer | Priority — first matching rule wins | | `active` | Boolean | | -#### `fp.quote.configurator` — The Configurator Session +#### `fp.pricing.complexity.surcharge` — Complexity-Based Surcharge Line | Field | Type | Description | |-------|------|-------------| +| `rule_id` | Many2one fp.pricing.rule | Parent rule (cascade) | +| `complexity` | Selection | simple, moderate, complex, very_complex | +| `surcharge_percent` | Float | Surcharge % for this complexity level | + +#### `fp.quote.configurator` — The Configurator Session (Persistent Model) + +This is a `models.Model` (NOT transient). Records persist for audit trail, re-quoting, and linking back from sale orders. The SO links back via `x_fc_configurator_id`. + +| Field | Type | Description | +|-------|------|-------------| +| `name` | Char | Auto-sequence (CFG-00001) | +| `state` | Selection | draft, confirmed, cancelled | | `partner_id` | Many2one res.partner | Customer | | `part_catalog_id` | Many2one fp.part.catalog | For repeat parts | | `coating_config_id` | Many2one fp.coating.config | Coating selection | @@ -197,17 +227,21 @@ These are Fusion Plating security groups layered on top of standard Odoo groups. | `surface_area` | Float | From catalog or entered | | `thickness_requested` | Float | | | `masking_zones` | Integer | | -| `complexity` | Selection | | +| `complexity` | Selection | simple, moderate, complex, very_complex | | `rush_order` | Boolean | | | `turnaround_days` | Integer | | | `delivery_method` | Selection | local_delivery, shipping_partner, customer_pickup | +| `currency_id` | Many2one res.currency | Company currency (default) | | `shipping_fee` | Monetary | | | `delivery_fee` | Monetary | | | `calculated_price` | Monetary | Computed from pricing rules | | `price_breakdown_html` | Html | Rendered breakdown | | `estimator_override_price` | Monetary | Final price (defaults to calculated) | +| `sale_order_id` | Many2one sale.order | Created SO (set on "Create Quotation") | | `notes` | Text | | +**Lifecycle:** draft → (estimator builds quote) → confirmed (when SO created) → cancelled (if abandoned). Confirmed records are read-only. Re-quoting creates a new configurator record. + ### Price Calculation Flow ``` @@ -258,7 +292,7 @@ Part Catalog (surface area, complexity, masking) | Field | Type | Description | |-------|------|-------------| | `name` | Char | Auto-sequence (RCV-00001) | -| `sale_order_id` | Many2one sale.order | Required | +| `sale_order_id` | Many2one sale.order | Required. **Design decision:** one receiving record per SO. If a customer ships parts for multiple SOs in one box, create separate receiving records per SO (receiver splits the count). This keeps the SO↔receiving link clean and avoids Many2many complexity. | | `partner_id` | Many2one res.partner | Related from SO | | `po_number` | Char | Related from SO x_fc_po_number | | `received_by_id` | Many2one res.users | Who logged it | @@ -344,13 +378,29 @@ SO Confirmed → Receiving record auto-created (state=draft) | `payment_term_id` | Many2one account.payment.term | | | `notes` | Text | | +### Auto-Population Rules + +When a customer is selected on a new SO: +1. Look up `fp.invoice.strategy.default` for that `partner_id` +2. If found → auto-fill `x_fc_invoice_strategy` and `x_fc_deposit_percent` from the default +3. If not found → leave blank (estimator must select manually) +4. Estimator can always override per order + +When a coating config is selected in the configurator: +1. Auto-fill `thickness_requested` from `coating_config.thickness_min` (default to minimum) +2. Auto-fill surface area UOM from company default setting + +When a part catalog entry is selected: +1. Auto-fill `surface_area`, `complexity`, `masking_zones`, `substrate_material` from the catalog entry +2. These can be overridden per-quote if the part has changed + ### Account Hold (extends `res.partner`) | Field | Type | Description | |-------|------|-------------| | `x_fc_account_hold` | Boolean | Manually set by accounting | | `x_fc_account_hold_reason` | Text | Why hold was placed | -| `x_fc_account_hold_date` | Date | When placed | +| `x_fc_account_hold_date` | Datetime | When placed (Datetime for audit precision) | | `x_fc_account_hold_by_id` | Many2one res.users | Who placed it | ### Account Hold Behaviour @@ -460,11 +510,18 @@ Based on Fischerscope XDAL 600 output: - Operator, date/time - Data entry: manual for now, future Fischerscope CSV import +### Work Centre Mapping Note + +The codebase has two work centre models: `fusion.plating.work.center` (core) and `mrp.workcenter` (standard MRP). Recipe nodes reference `fusion.plating.work.center`; MRP work orders use `mrp.workcenter`. The recipe-to-WO generation logic in `fusion_plating_bridge_mrp` must map between them. Each `fusion.plating.work.center` should have an `x_fc_mrp_workcenter_id` field linking to the corresponding `mrp.workcenter`. This mapping field should be added to the core module. + --- -## 7. Certificate Registry — `fp.certificate` +## 7. `fusion_plating_certificates` — Certificate Registry -Unified model logging every certificate issued. +**Module owner:** `fusion_plating_certificates` (NEW dedicated module). +**Dependencies:** `fusion_plating`, `fusion_plating_portal`, `fusion_plating_reports`, `mrp` + +This module owns `fp.certificate` and `fp.thickness.reading`. It depends on `fusion_plating_portal` for the `fusion.plating.portal.job` link and on `fusion_plating_reports` for report generation. ### Model: `fp.certificate` @@ -475,7 +532,7 @@ Unified model logging every certificate issued. | `partner_id` | Many2one res.partner | Customer | | `sale_order_id` | Many2one sale.order | | | `production_id` | Many2one mrp.production | | -| `portal_job_id` | Many2one fp.portal.job | | +| `portal_job_id` | Many2one fusion.plating.portal.job | Uses canonical model name | | `part_number` | Char | Denormalized for fast search | | `process_description` | Char | e.g. "ELECTROLESS NICKEL PLATING PER AMS 2404" | | `spec_reference` | Char | | @@ -491,9 +548,31 @@ Unified model logging every certificate issued. | `void_reason` | Text | | | `notes` | Html | | +### Model: `fp.thickness.reading` — Fischerscope Measurement Data + +| Field | Type | Description | +|-------|------|-------------| +| `certificate_id` | Many2one fp.certificate | Parent certificate (cascade) | +| `production_id` | Many2one mrp.production | Link to MO (independent of cert) | +| `reading_number` | Integer | Reading sequence (n=1, n=2, n=3) | +| `nip_mils` | Float(10,4) | NiP thickness in mils | +| `ni_percent` | Float(6,3) | Nickel content % | +| `p_percent` | Float(6,4) | Phosphorus content % | +| `position_label` | Char | Where on the part this reading was taken | +| `equipment_model` | Char | e.g. "Fischerscope XDAL 600" | +| `product_ref` | Char | e.g. "2805031 / NiP/Al-alloys 2805030" | +| `calibration_std_ref` | Char | e.g. "NiP/Al STD SET SN 100174568" | +| `microscope_image_id` | Many2one ir.attachment | Microscope photo | +| `operator_id` | Many2one res.users | Who took the reading | +| `reading_datetime` | Datetime | When reading was taken | +| `measuring_time_seconds` | Integer | e.g. 120 | + +**Statistical fields** (computed from reading lines per certificate): +- `mean_nip_mils`, `stddev_nip_mils`, `cov_percent`, `range_nip_mils` — computed on `fp.certificate` from its `thickness_reading_ids` + ### Auto-Creation -When CoC or thickness report is generated, fp.certificate record auto-created with PDF attached. +When CoC or thickness report is generated, `fp.certificate` record auto-created with PDF attached. ### Views @@ -501,6 +580,10 @@ When CoC or thickness report is generated, fp.certificate record auto-created wi - **Search**: Quick filters by Customer, Certificate Type, Date Range, Part Number, PO Number. Group by: Customer, Type, Month, Issued By. - **Form**: Certificate type badge, state buttons (Issue/Void), customer info, part details, tabs for Thickness Readings, Attachments, Notes. "Regenerate PDF" and "Send to Customer" buttons. +### CoC Bilingual Implementation + +The bilingual EN/FR certification statement uses QWeb template logic with `t-if` on a `bilingual` flag (default: True for Canadian compliance). The English and French text blocks are both rendered in the same template — not using Odoo's `ir.translation` system, since both languages must appear on the same document simultaneously. + --- ## 8. `fusion_tasks` (Entech Plating) — Local Delivery Dispatch @@ -541,10 +624,10 @@ Renamed from `fusion.technician.task`. |-------|------|-------------| | `delivery_id` | Many2one fusion.plating.delivery | Links to logistics | | `sale_order_id` | Many2one sale.order | | -| `portal_job_id` | Many2one fp.portal.job | | +| `portal_job_id` | Many2one fusion.plating.portal.job | Uses canonical model name | | `partner_id` | Many2one res.partner | Customer | | `driver_id` | Many2one hr.employee | Renamed from technician_id | -| `vehicle_id` | Many2one fusion.plating.vehicle | | +| `vehicle_id` | Many2one fusion.plating.vehicle | From logistics module | | `packages_count` | Integer | Number of boxes/crates | | `weight_total` | Float | Total weight | | `requires_signature` | Boolean | POD required | @@ -555,12 +638,14 @@ Renamed from `fusion.technician.task`. - `action_mark_delivered()`: logs GPS + timestamp, captures signature/photo, updates fp.delivery → delivered, cascades to portal job → shipped, triggers shipment notification email - `action_mark_failed()`: logs reason, creates follow-up activity -### Dependencies +### Dependencies & Deployment ```python 'depends': ['base', 'mail', 'hr', 'fusion_plating_logistics'], ``` +**Deployment strategy:** `fusion_tasks` lives in a separate repo (`Entech Plating/`) but is deployed to the SAME server as the fusion_plating modules. Both repos are copied to `/mnt/extra-addons/custom/` on the target server. The `fusion_plating_logistics` dependency is therefore always available at install time. There is no standalone driver-only install scenario — drivers access the system via the same Odoo instance. The standalone "Delivery Dispatch" menu (below) provides a driver-focused view without needing a separate deployment. + ### Menu Placement Inside Fusion Plating: Shipping & Delivery → Local Delivery Tasks, Driver Map @@ -683,23 +768,29 @@ fusion_plating (core) │ └── depends: fusion_plating, sale_management ├── fusion_plating_receiving │ └── depends: fusion_plating, sale_management + │ └── provides: mrp.production gate mixin (overrides action_confirm) ├── fusion_plating_invoicing │ └── depends: fusion_plating, sale_management, account + │ └── provides: invoice strategy automation, account hold on res.partner ├── fusion_plating_notifications │ └── depends: fusion_plating, fusion_plating_reports, mail - ├── fusion_plating_bridge_mrp - │ └── depends: fusion_plating, fusion_plating_configurator, - │ fusion_plating_receiving, fusion_plating_invoicing, - │ mrp + ├── fusion_plating_certificates + │ └── depends: fusion_plating, fusion_plating_portal, + │ fusion_plating_reports, mrp + ├── fusion_plating_bridge_mrp (lighter — gates live in receiving/invoicing) + │ └── depends: fusion_plating, fusion_plating_configurator, mrp + │ └── soft-depends: fusion_plating_receiving, fusion_plating_invoicing ├── fusion_plating_portal │ └── depends: fusion_plating, fusion_plating_configurator, │ fusion_plating_notifications, portal ├── fusion_plating_logistics │ └── depends: fusion_plating - └── fusion_tasks (Entech Plating — separate repo) + └── fusion_tasks (Entech Plating — separate repo, same server) └── depends: fusion_plating_logistics, hr, mail ``` +**Note on bridge_mrp:** The receiving gate and invoice strategy gates are implemented as lightweight mixins within `fusion_plating_receiving` and `fusion_plating_invoicing` respectively (each overrides `mrp.production` independently). This avoids funnelling all dependencies through bridge_mrp. Bridge_mrp focuses on recipe-to-WO generation and the configurator link. + --- ## 11. Pricing Variables Reference @@ -737,7 +828,20 @@ All 10 pricing variables that drive the configurator: --- -## 13. Roadmap Items (Not in Initial Build) +## 13. Data Migration: Existing Quote Request Flow + +The existing `fusion.plating.quote.request` model has an `action_create_sale_order()` method that creates basic SOs. The new configurator introduces a parallel, richer path. + +**Coexistence strategy:** +- The existing `action_create_sale_order()` on `fusion.plating.quote.request` remains functional — it is the "quick path" for simple quotes that don't need the full configurator +- The new configurator is the "full path" for detailed quotes with part catalog, coating config, and pricing rules +- When a quote request comes in via portal, the estimator chooses: use the configurator (creates `fp.quote.configurator` → SO) or use the quick path (existing `action_create_sale_order()`) +- Both paths create SOs with `x_fc_*` fields. The quick path leaves configurator-specific fields blank; the full path populates everything +- No existing data needs migration — the two paths coexist + +--- + +## 14. Roadmap Items (Not in Initial Build) - Claude Vision for PDF drawing measurement extraction - Auto account hold computed from invoice aging