docs(fusion_plating): add Promote Customer Spec design + implementation plan
- Spec: retire fp.coating.config + fp.treatment, promote fusion.plating.customer.spec - Two-picker SO line UX (Specification + Recipe), aerospace-correct audit posture - Plan: 5 phases (foundation, SO line, pricing/quality/job/cert, reports/tablet/portal, removal) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,558 @@
|
||||
# Promote Customer Specification, Retire Coating Config — Design
|
||||
|
||||
**Date:** 2026-05-14
|
||||
**Author:** brainstorming session with Gurpreet
|
||||
**Status:** Draft — pending user review
|
||||
**Backup branch:** `backup/pre-spec-recipe-collapse-2026-05-14` (1414ef2 on origin + gitea)
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
`fp.coating.config` ("Primary Treatment") is a half-baked sketch that conflates a customer-facing specification with an internal production process. The codebase already contains a properly-modelled aerospace specification entity (`fusion.plating.customer.spec`) with revision tracking, document URLs, AS9100 / Nadcap / N299 extensions, and 15 seeded industry specs. This design proposes:
|
||||
|
||||
1. **Retire** `fp.coating.config` and `fp.treatment` entirely — no archive, no commented blocks, no "obsolete" module.
|
||||
2. **Promote** `fusion.plating.customer.spec` to the primary specification entity, exposed on the SO line as a picker labelled **"Specification"**.
|
||||
3. **Move process parameters** (thickness, bake-relief, phosphorus level) from coating config onto the Recipe model where they semantically belong.
|
||||
4. **Keep the spec ↔ recipe separation** — both are pickers on the SO line. They auto-fill from part defaults so order entry stays single-click for repeat work.
|
||||
|
||||
The result is an aerospace-correct two-picker design ("Spec the customer cited" + "Recipe we used") that handles all 7 aerospace audit scenarios cleanly while preserving fast order entry for ENPlating's repeat-customer workflow.
|
||||
|
||||
---
|
||||
|
||||
## Problem statement
|
||||
|
||||
ENPlating is an aerospace/defence plating shop running Fusion Plating in development. The current SO line UX presents two pickers that look conceptually identical to the operator:
|
||||
|
||||
- **"Primary Treatment"** (`fp.coating.config`) — process type, phos level, thickness range, spec reference, cert level, bake settings, default recipe pointer
|
||||
- **"Process Variant"** (`fusion.plating.process.node` recipe root) — the production tree
|
||||
|
||||
The client has stated:
|
||||
|
||||
- "Recipe and Primary Treatment are the same thing" (correctly observing the conceptual overlap)
|
||||
- Speed of order entry is paramount (repeat customers, repeat parts, AI auto-fill on the roadmap)
|
||||
- Per-step variation ("skip the bake") is rare and already handled by `fp.job.node.override`
|
||||
|
||||
The client serves aerospace and defence primes (Boeing, Lockheed, Northrop, RTX, etc. — to be confirmed). Audit posture matters. Source approval letters, AS9100, Nadcap, FAI / AS9102, spec revision tracking — these are real concerns even if the daily operator doesn't think about them.
|
||||
|
||||
---
|
||||
|
||||
## Background — the two parallel models
|
||||
|
||||
### `fp.coating.config` ("Primary Treatment")
|
||||
|
||||
- **Module:** `fusion_plating_configurator`
|
||||
- **Origin:** built by the configurator team as an order-entry convenience
|
||||
- **Seeded data:** zero records ship with the module
|
||||
- **Fields:** name, process_type_id (single), phosphorus_level, thickness_min/max/uom, thickness_option_ids, spec_reference (free Char), certification_level, requires_bake_relief, bake_window_hours, bake_temperature(_uom), bake_duration_hours, pre_treatment_ids, post_treatment_ids, recipe_id (default recipe pointer), description, sequence, active
|
||||
- **Audit features:** none (no chatter, no revision tracking, no unique constraint on spec ref, no customer linkage)
|
||||
- **Used by:** SO line picker, direct order wizard, portal customer self-service, fp.job, fp.delivery, account.move.line, fp.pricing.rule, fp.quality.point, fp.certificate (auto-fill), reports
|
||||
- **Seeded coating records:** 0
|
||||
|
||||
### `fusion.plating.customer.spec` ("Customer Specification")
|
||||
|
||||
- **Module:** `fusion_plating_quality` (with extensions in `fusion_plating_aerospace` + `fusion_plating_nuclear`)
|
||||
- **Origin:** built by the quality team as the auditable spec library
|
||||
- **Seeded data:** 11 aerospace specs + 4 nuclear specs ship with the module
|
||||
- **Fields:** code (e.g. AMS 2404), revision, effective_date, partner_id, process_type_ids (M2M), spec_type (industry/customer/internal), document_url, notes (Html), company_id, active, plus mail.thread + mail.activity.mixin
|
||||
- **Aerospace extension fields:** x_fc_is_aerospace, x_fc_as9100_clause_ids, x_fc_nadcap_required, x_fc_requires_first_article, x_fc_pri_file_code, x_fc_customer_approval_required
|
||||
- **Nuclear extension fields:** x_fc_is_nuclear, x_fc_n299_level_id, x_fc_nqa1_applicable, x_fc_extended_retention_years, x_fc_nuclear_customer_type
|
||||
- **Audit features:** mail.thread chatter, unique `(code, revision, company_id)` constraint, tracking on every key field
|
||||
- **Used by:** fp.job.customer_spec_id (parallel to coating_config_id, currently underused)
|
||||
- **Seeded specs:** AMS 2404, AMS 2700, AMS 2759, AMS QQ-P-416, ASTM B733, BAC 5709, MIL-A-8625, MIL-C-26074, MIL-DTL-13924, PRI AS7108, QQ-C-320, OPG SQAP, Bruce N299, AECL N299, Candu N299
|
||||
|
||||
### Why both exist
|
||||
|
||||
Two different teams (configurator + quality) built parallel solutions to overlapping problems. Neither cleaned up after the other. The result is two records describing the same real-world artifact, with the better-architected one (Customer Spec) sitting unused in the daily flow while the weaker one (Coating Config) drives the UI.
|
||||
|
||||
---
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1 — Promote Customer Spec, retire Coating Config
|
||||
|
||||
`fusion.plating.customer.spec` becomes the primary specification entity. `fp.coating.config` is removed entirely (no archive, no obsolete flag, no commented blocks).
|
||||
|
||||
**Rationale:**
|
||||
- Customer Spec already has the audit infrastructure aerospace requires
|
||||
- Customer Spec already has aerospace + nuclear extension modules
|
||||
- Customer Spec already ships with real industry specs
|
||||
- The aerospace team has already invested in this model
|
||||
- Coating Config has 0 seed records, no audit trail, no customer linkage
|
||||
|
||||
### Decision 2 — Two-picker SO line UX
|
||||
|
||||
The SO line carries two Many2one pickers:
|
||||
|
||||
| Picker label | Backing model | Purpose |
|
||||
|---|---|---|
|
||||
| **Specification** | `fusion.plating.customer.spec` | What the customer cited on the PO |
|
||||
| **Recipe** | `fusion.plating.process.node` (root) | How we make it |
|
||||
|
||||
Both auto-fill from the part's defaults. For repeat customer + part combinations, order entry remains one click. For the rare phos swap (Mid → High) the estimator changes one dropdown.
|
||||
|
||||
**Rationale:**
|
||||
- Spec ≠ Process in aerospace doctrine — auditors expect the separation
|
||||
- Many-to-many in concept (one spec covers multiple recipes; one recipe satisfies multiple specs) — modelling them as one record breaks down
|
||||
- AI drawing detection populates one decision per field; both fields are independently AI-fillable
|
||||
- Existing seed data already shows the pattern: AMS 2404 already lists `[ptype_en_lp, ptype_en_mp, ptype_en_hp]` as applicable processes
|
||||
|
||||
### Decision 3 — Move process parameters onto Recipe
|
||||
|
||||
Fields currently on Coating Config that describe the production process (not the customer requirement) move onto `fusion.plating.process.node` (recipe root):
|
||||
|
||||
| Field | From | To |
|
||||
|---|---|---|
|
||||
| `phosphorus_level` | `fp.coating.config` | `fusion.plating.process.node` (recipe root) |
|
||||
| `thickness_min`, `thickness_max`, `thickness_uom` | `fp.coating.config` | `fusion.plating.process.node` (recipe root) |
|
||||
| `thickness_option_ids` | `fp.coating.config` (`fp.coating.thickness`) | re-parented as `fp.recipe.thickness` |
|
||||
| `requires_bake_relief` | `fp.coating.config` | `fusion.plating.process.node` (recipe root) |
|
||||
| `bake_window_hours` | `fp.coating.config` | `fusion.plating.process.node` (recipe root) |
|
||||
| `bake_temperature`, `bake_temperature_uom` | `fp.coating.config` | `fusion.plating.process.node` (recipe root) |
|
||||
| `bake_duration_hours` | `fp.coating.config` | `fusion.plating.process.node` (recipe root) |
|
||||
| pre/post treatment lists | `fp.coating.config` (M2M `fp.treatment`) | already covered — recipe steps include pre/post operations |
|
||||
|
||||
**Rationale:** these fields describe HOW we plate (the process), not WHAT we promised the customer (the spec).
|
||||
|
||||
**Why bake-relief belongs on Recipe specifically** — AMS 2759/9 says "bake if hardness ≥ HRC 31 AND hydrogen-embrittlement risk exists." The risk is determined by the plating chemistry (Mid-Phos = high HE risk; High-Phos = low; non-EN processes = none). The recipe author knows which chemistry their recipe uses and ticks `requires_bake_relief` once. The spec doesn't need to drive this — AMS 2759/9 is invoked universally when conditions are met. Bake-window auto-create logic on `fp.job.button_mark_done` reads from `recipe.requires_bake_relief` instead of `coating.requires_bake_relief`.
|
||||
|
||||
### Decision 4 — Delete `fp.treatment` model entirely
|
||||
|
||||
`fp.treatment` (the small library of named pre/post operations: Bead Blast, Zincate, Bake, Passivate, etc.) is removed. Pre/post operations are already first-class steps in the recipe tree. The `fp.step.template` library (Sub 12a) plays the role of "approved step library" for shops that want one.
|
||||
|
||||
### Decision 5 — Naming convention
|
||||
|
||||
| Concept | Label on screen | Technical model |
|
||||
|---|---|---|
|
||||
| The customer-facing spec record | **"Specification"** (formal, audit-friendly) | `fusion.plating.customer.spec` (unchanged) |
|
||||
| The same thing in tight UI spots (kanban chips, status bars) | **"Spec"** (short form) | (same) |
|
||||
| The admin menu | **"Specifications"** | (renamed from "Customer Specs") |
|
||||
| The production tree | **"Recipe"** (unchanged) | `fusion.plating.process.node` (unchanged) |
|
||||
|
||||
Rejected alternatives:
|
||||
- "Plating Spec" — too narrow (won't cover anodize, masking, etc.)
|
||||
- "Coating Spec" — same problem; "coating" is the customer's word, not internal
|
||||
- "Process Spec" — collides verbally with "Process Recipe"
|
||||
- "Customer Spec" — fine but slightly off when the spec is a public industry standard
|
||||
- "Treatment" / "Coating Configuration" — what we're explicitly removing
|
||||
|
||||
### Decision 6 — Recipe ↔ Specification relationship
|
||||
|
||||
Many-to-many. One spec applies to multiple recipes; one recipe can satisfy multiple specs.
|
||||
|
||||
Implementation: add `recipe_ids` Many2many on `fusion.plating.customer.spec`, with reverse field on the recipe model.
|
||||
|
||||
```python
|
||||
# On fusion.plating.customer.spec
|
||||
recipe_ids = fields.Many2many(
|
||||
'fusion.plating.process.node',
|
||||
'fp_customer_spec_recipe_rel',
|
||||
'spec_id',
|
||||
'recipe_id',
|
||||
domain="[('node_type', '=', 'recipe'), ('parent_id', '=', False)]",
|
||||
string='Applicable Recipes',
|
||||
)
|
||||
```
|
||||
|
||||
The existing `process_type_ids` M2M on customer.spec stays — useful for spec-level filtering at the process-type level (e.g., "AMS 2404 covers EN-LP, EN-MP, EN-HP").
|
||||
|
||||
---
|
||||
|
||||
## Detailed model changes
|
||||
|
||||
### Models removed entirely
|
||||
|
||||
- `fp.coating.config` — model definition, views, search, action, security rows
|
||||
- `fp.treatment` — model definition, views, search, action, security rows, seed data file
|
||||
|
||||
### Models modified
|
||||
|
||||
#### `fusion.plating.customer.spec` (in `fusion_plating_quality`)
|
||||
|
||||
Add fields to support what was on Coating Config:
|
||||
|
||||
```python
|
||||
# Process parameter helpers (optional; recipe is source of truth)
|
||||
recipe_ids = fields.Many2many(
|
||||
'fusion.plating.process.node',
|
||||
'fp_customer_spec_recipe_rel',
|
||||
'spec_id', 'recipe_id',
|
||||
domain="[('node_type', '=', 'recipe'), ('parent_id', '=', False)]",
|
||||
string='Applicable Recipes',
|
||||
help='Recipes that can produce work to this specification. '
|
||||
'Many-to-many — one spec can cover multiple processes; '
|
||||
'one recipe can satisfy multiple specs.',
|
||||
)
|
||||
|
||||
# Spec-level cert auto-fill helper (optional override of recipe's spec line)
|
||||
print_on_cert = fields.Boolean(
|
||||
string='Print on Certificate',
|
||||
default=True,
|
||||
help='When enabled, this spec\'s code+revision appear on the CoC '
|
||||
'when the spec is selected on the SO line.',
|
||||
)
|
||||
```
|
||||
|
||||
(Aerospace + nuclear extensions are already in place — no changes to those modules' fields.)
|
||||
|
||||
#### `fusion.plating.process.node` (in `fusion_plating`)
|
||||
|
||||
Add the process parameter fields previously on Coating Config:
|
||||
|
||||
```python
|
||||
# Recipe-only fields (apply when node_type='recipe' and parent_id is False)
|
||||
phosphorus_level = fields.Selection(
|
||||
[('low_phos', 'Low Phosphorus (2-5%)'),
|
||||
('mid_phos', 'Mid Phosphorus (6-9%)'),
|
||||
('high_phos', 'High Phosphorus (10-13%)'),
|
||||
('na', 'N/A')],
|
||||
string='Phosphorus Level',
|
||||
default='na',
|
||||
help='EN-specific. Set to N/A for non-EN processes (chrome, anodize, '
|
||||
'black oxide).',
|
||||
)
|
||||
thickness_min = fields.Float(string='Min Thickness', digits=(10, 4))
|
||||
thickness_max = fields.Float(string='Max Thickness', digits=(10, 4))
|
||||
thickness_uom = fields.Selection(
|
||||
[('mils', 'mils'), ('microns', 'microns'), ('inches', 'inches')],
|
||||
string='Thickness UoM', default='mils',
|
||||
)
|
||||
thickness_option_ids = fields.One2many(
|
||||
'fp.recipe.thickness',
|
||||
'recipe_id',
|
||||
string='Thickness Options',
|
||||
)
|
||||
|
||||
# Bake relief (AMS 2759/9 hydrogen embrittlement)
|
||||
requires_bake_relief = fields.Boolean(
|
||||
string='Requires Bake Relief',
|
||||
help='Hydrogen embrittlement relief bake required (high-strength steel, '
|
||||
'Rockwell C ≥ 31). When set, finishing the job auto-creates a '
|
||||
'bake window record and blocks shipment until bake is complete.',
|
||||
)
|
||||
bake_window_hours = fields.Float(
|
||||
string='Bake Window (hours)', default=4.0,
|
||||
help='Maximum time between plate exit and bake start. Typical 4h per '
|
||||
'AMS 2759/9.',
|
||||
)
|
||||
bake_temperature = fields.Float(
|
||||
string='Bake Temperature', default=375.0,
|
||||
help='Relief bake temperature. Default 375 (°F per AMS 2759/9 for '
|
||||
'steel ≥ HRC 40).',
|
||||
)
|
||||
bake_temperature_uom = fields.Selection(
|
||||
[('F', '°F'), ('C', '°C')],
|
||||
string='Temp Unit',
|
||||
default=lambda self: self.env.company.x_fc_default_temp_uom or 'F',
|
||||
)
|
||||
bake_duration_hours = fields.Float(
|
||||
string='Bake Duration (hours)', default=23.0,
|
||||
help='Minimum bake hold time at temperature. Typical 23h.',
|
||||
)
|
||||
|
||||
# Reverse of customer.spec.recipe_ids
|
||||
applicable_spec_ids = fields.Many2many(
|
||||
'fusion.plating.customer.spec',
|
||||
'fp_customer_spec_recipe_rel',
|
||||
'recipe_id', 'spec_id',
|
||||
string='Applicable Specifications',
|
||||
)
|
||||
```
|
||||
|
||||
These fields render only when `node_type='recipe'` and `parent_id=False` (i.e. the recipe root). Use `invisible="node_type != 'recipe' or parent_id"` on the form view.
|
||||
|
||||
#### `fp.coating.thickness` → `fp.recipe.thickness`
|
||||
|
||||
Renamed model. M2O `coating_config_id` becomes `recipe_id` pointing at `fusion.plating.process.node`.
|
||||
|
||||
```python
|
||||
class FpRecipeThickness(models.Model):
|
||||
_name = 'fp.recipe.thickness'
|
||||
_description = 'Fusion Plating — Recipe Thickness Option'
|
||||
_order = 'recipe_id, sequence'
|
||||
|
||||
recipe_id = fields.Many2one(
|
||||
'fusion.plating.process.node',
|
||||
string='Recipe',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
domain="[('node_type', '=', 'recipe'), ('parent_id', '=', False)]",
|
||||
)
|
||||
# ... (existing thickness/uom/sequence/label fields)
|
||||
```
|
||||
|
||||
### Models with field deletions
|
||||
|
||||
| Model | Field removed |
|
||||
|---|---|
|
||||
| `sale.order.line` | `x_fc_coating_config_id` |
|
||||
| `sale.order.line` | `x_fc_treatment_ids` |
|
||||
| `fp.part.catalog` | `x_fc_default_coating_config_id` |
|
||||
| `fp.part.catalog` | `x_fc_default_treatment_ids` |
|
||||
| `account.move.line` | `x_fc_coating_config_id` |
|
||||
| `fp.job` | `coating_config_id` |
|
||||
| `fp.pricing.rule` | `coating_config_id` |
|
||||
| `fp.quality.point` | `coating_config_ids` |
|
||||
| `fp.direct.order.line` | `coating_config_id` |
|
||||
| `fp.direct.order.line` | `treatment_ids` |
|
||||
|
||||
### Models with field additions
|
||||
|
||||
| Model | Field added | Purpose |
|
||||
|---|---|---|
|
||||
| `sale.order.line` | `x_fc_customer_spec_id` Many2one | Specification picker on SO line |
|
||||
| `fp.part.catalog` | `x_fc_default_customer_spec_id` Many2one | Per-part default spec |
|
||||
| `account.move.line` | `x_fc_customer_spec_id` Many2one | Carries spec to invoice for cert reference |
|
||||
| `fp.pricing.rule` | `customer_spec_id` Many2one | Primary key (replaces coating_config_id) |
|
||||
| `fp.pricing.rule` | `recipe_id` Many2one | Secondary key for recipe-only rules |
|
||||
|
||||
**Pricing rule lookup priority** (most specific → least specific):
|
||||
1. `customer_spec_id` AND `recipe_id` both set → exact match
|
||||
2. `customer_spec_id` set, `recipe_id` blank → spec-tier rule (e.g. "all AS9100 work +15%")
|
||||
3. `recipe_id` set, `customer_spec_id` blank → recipe-tier rule (e.g. "EN Mid-Phos $X/sqft")
|
||||
4. Both blank → catch-all (customer/material defaults)
|
||||
|
||||
The pricing engine returns the FIRST match in this order. Composability: a single quote can stack a base recipe rate + a spec surcharge by configuring two rules.
|
||||
| `fp.quality.point` | `customer_spec_ids` Many2many | Re-keyed QC trigger filter |
|
||||
| `fp.quality.point` | `recipe_ids` Many2many | Recipe-level QC trigger filter |
|
||||
| `fp.direct.order.line` | `customer_spec_id` Many2one | Wizard picker |
|
||||
|
||||
### Models unchanged (already use customer.spec)
|
||||
|
||||
- `fp.job.customer_spec_id` — already exists, will become the primary spec link
|
||||
- All aerospace + nuclear extension fields on customer.spec — unchanged
|
||||
|
||||
---
|
||||
|
||||
## UI / view changes
|
||||
|
||||
### SO line view
|
||||
|
||||
Replace the Primary Treatment + Treatments + Process Variant block with:
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ Part: ABC-001 Rev A [picker] │
|
||||
│ Specification: AMS 2404 Rev D [picker] │
|
||||
│ Recipe: EN Mid-Phos #4 [picker] │
|
||||
│ Thickness: 0.0005-0.0008 in [picker — scoped to recipe] │
|
||||
│ Qty: 50 │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Both Specification and Recipe pickers use `default_get` to pre-fill from the chosen part's defaults. The thickness picker domain becomes `[('recipe_id', '=', x_fc_process_variant_id)]` (was `coating_config_id`).
|
||||
|
||||
### Direct order wizard
|
||||
|
||||
Same change: replace coating + treatments fields with specification picker. Rename "Primary Treatment" label → "Specification".
|
||||
|
||||
### Job form
|
||||
|
||||
Remove `coating_config_id` field. Surface `customer_spec_id` (already exists) as the primary spec link with proper widget styling. Add smart button "View Spec" that opens the spec record.
|
||||
|
||||
### Reports
|
||||
|
||||
| Report | Change |
|
||||
|---|---|
|
||||
| `report_fp_sale.xml` | Print `line.x_fc_customer_spec_id.code` + `revision` instead of `x_fc_coating_config_id.name` |
|
||||
| `report_fp_wo_sticker.xml` | Same |
|
||||
| `report_fp_job_traveller.xml` | Same |
|
||||
| `report_fp_job_sticker.xml` | Same |
|
||||
| `report_coc_en.xml` / `report_coc_fr.xml` | Cert reads `spec.code Rev rev` instead of `coating.spec_reference` |
|
||||
| Cert auto-fill (`_fp_create_certificates`) | Read from `job.customer_spec_id` instead of `coating_config_id` |
|
||||
|
||||
### Portal
|
||||
|
||||
`fusion_plating_portal/controllers/portal_configurator.py` — change "Pick a Coating" picker to "Pick a Specification". Customer-facing label may stay "Coating" if `partner.x_fc_portal_label_preference` says so (settings-driven; defer to client preference).
|
||||
|
||||
### Menus
|
||||
|
||||
- Remove "Coating Configurations" menu item under Configuration → Materials & Tanks (or wherever it lives today)
|
||||
- Promote "Customer Specifications" under Configuration → Quality & Documents → **rename to "Specifications"**
|
||||
- Optionally surface "Specifications" higher in the menu tree (e.g. as a top-level Configuration tab) since it's now a primary admin entity
|
||||
|
||||
### Smart buttons
|
||||
|
||||
On `res.partner` (customer): add "Specifications" smart button → opens specs filtered to `partner_id = self`.
|
||||
|
||||
On `fusion.plating.process.node` (recipe root): add "Applicable Specifications" smart button → opens specs where `recipe_ids` includes this recipe.
|
||||
|
||||
---
|
||||
|
||||
## Per-module impact summary
|
||||
|
||||
| Module | Impact | Notes |
|
||||
|---|---|---|
|
||||
| `fusion_plating` | High | Add fields to fp.process.node + fp.recipe.thickness model + bake-relief logic refactor |
|
||||
| `fusion_plating_configurator` | Critical | Delete fp.coating.config + fp.treatment + their views/data; refactor SO line + part catalog + direct order wizard + pricing |
|
||||
| `fusion_plating_quality` | Medium | Add `recipe_ids` to customer.spec; add `print_on_cert` field; refactor quality.point trigger |
|
||||
| `fusion_plating_jobs` | Medium | Remove coating_config_id from fp.job; sale_order.py spec resolution chain; pricing path |
|
||||
| `fusion_plating_certificates` | Medium | Cert auto-fill reads from customer_spec_id |
|
||||
| `fusion_plating_reports` | Medium | All 4 plating reports updated |
|
||||
| `fusion_plating_portal` | Medium | Picker change, label decision |
|
||||
| `fusion_plating_shopfloor` | Low | Tablet payload updated to read recipe + spec instead of coating |
|
||||
| `fusion_plating_logistics` | Low | `fp.delivery.x_fc_thickness_id` — change M2O target from `fp.coating.thickness` to `fp.recipe.thickness` |
|
||||
| `fusion_plating_aerospace` | None | Already extends customer.spec correctly |
|
||||
| `fusion_plating_nuclear` | None | Already extends customer.spec correctly |
|
||||
| `fusion_plating_compliance*` | None | Doesn't reference coating |
|
||||
| Tank / bath / chemistry models | None | Keyed off `process_type` not `coating` |
|
||||
| `fusion_iot` | None | No coating references |
|
||||
| `fusion_plating_bridge_maintenance` | None | No coating references |
|
||||
|
||||
---
|
||||
|
||||
## Aerospace scenarios validated
|
||||
|
||||
The 7 aerospace scenarios from the brainstorming session all resolve cleanly:
|
||||
|
||||
1. **Same chemistry, different customer specs** — multiple spec records (BAC 5680, LMS-3045, AMS 2404) all link to one Recipe. Cert prints the spec the customer cited; process freeze remains intact.
|
||||
2. **Spec revision (BAC 5680 Rev D → Rev E)** — separate spec records via `(code, revision, company)` unique constraint. Both can be active. Old POs reference old rev; new POs reference new rev. Recipe untouched.
|
||||
3. **Nadcap process freeze** — Recipe edits trigger the existing sign-off workflow. Spec edits don't touch the recipe. Two clean audit trails.
|
||||
4. **Source Approval Letter** — `partner_id` on customer.spec naturally surfaces "Boeing-approved specs" via filter.
|
||||
5. **First Article Inspection (AS9102)** — `x_fc_requires_first_article` on customer.spec drives the gate. FAI tied to (part, spec) — matches AS9102 doctrine.
|
||||
6. **Customer source inspection** — Specifications menu becomes the customer-facing audit view.
|
||||
7. **DPAS / ITAR / DFARS** — Future extension on customer.spec via a new module (`fusion_plating_compliance_export` or similar).
|
||||
|
||||
For the mid-phos / high-phos same-customer scenario:
|
||||
- Both lines pick the same Specification (e.g. AMS 2404)
|
||||
- Each line picks a different Recipe (Mid-Phos vs High-Phos)
|
||||
- Bake settings travel with the recipe (Mid-Phos requires bake; High-Phos doesn't)
|
||||
- One spec record serves both orders; no duplication
|
||||
|
||||
---
|
||||
|
||||
## Open questions for client
|
||||
|
||||
Before finalizing implementation, confirm with the client:
|
||||
|
||||
1. **Customer-facing terminology on the portal:** when his customers self-service quote, do they think they're ordering a "coating" or a "specification"? Determines whether the portal picker label says "Coating" or "Specification" on the customer-facing page (internal label stays "Specification").
|
||||
2. **Pricing rule complexity:** how many active pricing rules does ENPlating use? Are surcharges keyed to spec (AS9100 = +15%) or to recipe (Mid-Phos = $X/sqft)? Determines whether `fp.pricing.rule` keeps both `customer_spec_id` and `recipe_id` keys or just one.
|
||||
3. **Customer Source Approvals:** does ENPlating have formal Source Approval Letters from any primes? If yes, names + counts so we can scope a "Customer Approval" tracking enhancement.
|
||||
4. **Retire the menu under Materials & Tanks?** "Coating Configurations" menu likely sits there today — confirm safe to remove (vs hide for one release).
|
||||
|
||||
These are nice-to-haves; the design proceeds without their answers but the answers refine UX details.
|
||||
|
||||
---
|
||||
|
||||
## Out of scope (explicitly NOT doing)
|
||||
|
||||
- Data migration of existing coating config records (per user direction: dev-stage, no historical data to preserve)
|
||||
- Backwards-compatibility shims (`if 'coating_config_id' in self._fields` guards) — clean removal
|
||||
- Archive / obsolete code patterns — clean deletion only
|
||||
- Resurrecting `fp.treatment` for any purpose
|
||||
- Adding a new `fp.spec.library` or `fp.process.specification` model — `fusion.plating.customer.spec` IS the spec model
|
||||
- Building a Source Approval Letter tracker (deferred — flag for future enhancement after client confirms the need)
|
||||
- Building DPAS / ITAR / DFARS export-control tracking (deferred — separate compliance extension module when needed)
|
||||
|
||||
---
|
||||
|
||||
## Risk analysis
|
||||
|
||||
| Risk | Severity | Mitigation |
|
||||
|---|---|---|
|
||||
| Bake-relief logic accidentally regresses (compliance-grade) | High | Smoke test: create a Mid-Phos job, verify bake_window auto-creates with correct temp/duration. Create a High-Phos job, verify NO bake_window |
|
||||
| Cert auto-fill loses spec_reference | High | Smoke test: complete a job with Spec=AMS 2404, verify cert PDF prints "Plated to AMS 2404" |
|
||||
| Pricing rules silently fail (no rule matches new keys) | Medium | Re-key existing rules to new model in same commit; add unit test that lookup returns expected price |
|
||||
| Portal customer-facing flow breaks | Medium | Manual smoke test of portal quote flow before deploy |
|
||||
| Recipe-thickness picker domain breaks (orphan thickness records) | Low | Drop fp.coating.thickness rows; recreate as fp.recipe.thickness during implementation |
|
||||
| QC trigger filter (Sub 12 quality point) misses jobs | Medium | Test job creation triggers expected QC checks |
|
||||
| Reports fail to render (missing field) | Low | Update all 4 plating reports in same commit; smoke test each PDF generation |
|
||||
|
||||
**Rollback strategy:** if catastrophic, `git reset --hard backup/pre-spec-recipe-collapse-2026-05-14`. The backup branch is pushed to both GitHub and Gitea.
|
||||
|
||||
---
|
||||
|
||||
## Implementation phases (high-level)
|
||||
|
||||
The detailed implementation plan goes into a separate `writing-plans` artifact. High-level phases:
|
||||
|
||||
### Phase 1 — Recipe model fields + thickness rename
|
||||
- Add new fields to `fusion.plating.process.node` (recipe root)
|
||||
- Rename `fp.coating.thickness` → `fp.recipe.thickness`
|
||||
- Update views to surface new fields on recipe form
|
||||
- Bump module version
|
||||
|
||||
### Phase 2 — Customer Spec enhancements
|
||||
- Add `recipe_ids` M2M to `fusion.plating.customer.spec`
|
||||
- Add `print_on_cert` Boolean
|
||||
- Update views (form, list, search) to surface recipe linkage
|
||||
- Bump module version
|
||||
|
||||
### Phase 3 — SO line + wizard rewrite
|
||||
- Add `x_fc_customer_spec_id` to `sale.order.line`
|
||||
- Add `x_fc_default_customer_spec_id` to `fp.part.catalog`
|
||||
- Update SO line view + direct order wizard
|
||||
- Auto-fill logic from part defaults
|
||||
- Domain scoping (thickness depends on recipe)
|
||||
|
||||
### Phase 4 — Pricing + quality point re-keying
|
||||
- Add `customer_spec_id` + `recipe_id` to `fp.pricing.rule`
|
||||
- Add `customer_spec_ids` + `recipe_ids` to `fp.quality.point`
|
||||
- Update rule lookup logic in pricing engine
|
||||
- Update quality.point trigger hooks
|
||||
|
||||
### Phase 5 — Job + cert + reports refactor
|
||||
- Drop `fp.job.coating_config_id`
|
||||
- Update `fp.certificate._fp_create_certificates` to read from `customer_spec_id`
|
||||
- Update all 4 plating reports
|
||||
- Smart buttons on partner + recipe
|
||||
|
||||
### Phase 6 — Portal updates
|
||||
- Change portal coating picker → specification picker
|
||||
- Update portal templates + JS
|
||||
- Test portal quote flow end-to-end
|
||||
|
||||
### Phase 7 — Removal
|
||||
- Delete `fp.coating.config` model + view + data + ACL
|
||||
- Delete `fp.treatment` model + view + data + ACL
|
||||
- Delete `x_fc_coating_config_id` field on SO line, account.move.line, etc.
|
||||
- Remove menu item
|
||||
- Remove old data files from manifest
|
||||
- Bump module version
|
||||
- Final smoke test: full order entry → SO → job → tablet → QC → cert → invoice
|
||||
|
||||
Each phase is a separate commit (or small set of commits) for clear rollback.
|
||||
|
||||
---
|
||||
|
||||
## Success criteria
|
||||
|
||||
The work is complete when:
|
||||
|
||||
1. ✅ A new SO line on a brand-new DB shows ONLY two pickers (Specification + Recipe), each pre-fillable from part defaults
|
||||
2. ✅ The aerospace specs (AMS 2404, BAC 5709, MIL-C-26074, etc.) appear in the Specification dropdown out of the box
|
||||
3. ✅ Confirming an SO with a Mid-Phos recipe auto-creates a bake window; with a High-Phos recipe does not
|
||||
4. ✅ Issuing a CoC prints "Plated to {spec.code} Rev {spec.revision}" derived from the SO line's specification
|
||||
5. ✅ Pricing rule lookup returns a price based on the chosen spec + recipe combination
|
||||
6. ✅ Quality point auto-spawn fires on jobs matching its `customer_spec_ids` / `recipe_ids` filters
|
||||
7. ✅ Tank, bath, chemistry log, IoT, compliance, maintenance modules unchanged and unaffected
|
||||
8. ✅ No `fp.coating.config` or `fp.treatment` references remain anywhere in the active codebase (grep returns zero results)
|
||||
9. ✅ Reports (sale ack, WO sticker, job traveller, job sticker, CoC EN, CoC FR) all render correctly with spec + recipe data
|
||||
10. ✅ Portal customer self-service quote flow completes end-to-end with the new Specification picker
|
||||
|
||||
---
|
||||
|
||||
## Appendix A — Field reconciliation table
|
||||
|
||||
What ends up where for every Coating Config field:
|
||||
|
||||
| Coating Config field | Disposition | New location |
|
||||
|---|---|---|
|
||||
| `name` | Removed | (record itself removed) |
|
||||
| `process_type_id` (single) | Removed | Already on customer.spec as M2M `process_type_ids` |
|
||||
| `recipe_id` | Removed | Replaced by customer.spec.recipe_ids M2M |
|
||||
| `phosphorus_level` | Moved | `fusion.plating.process.node.phosphorus_level` |
|
||||
| `thickness_min`, `thickness_max`, `thickness_uom` | Moved | `fusion.plating.process.node.thickness_*` |
|
||||
| `thickness_option_ids` | Re-parented | `fp.recipe.thickness` (was `fp.coating.thickness`) |
|
||||
| `spec_reference` | Removed | Replaced by customer.spec.code + revision |
|
||||
| `certification_level` | Removed | Replaced by customer.spec.spec_type + aerospace flags |
|
||||
| `pre_treatment_ids`, `post_treatment_ids` | Removed | Already covered by recipe steps |
|
||||
| `requires_bake_relief` | Moved | `fusion.plating.process.node.requires_bake_relief` |
|
||||
| `bake_window_hours` | Moved | `fusion.plating.process.node.bake_window_hours` |
|
||||
| `bake_temperature(_uom)` | Moved | `fusion.plating.process.node.bake_temperature(_uom)` |
|
||||
| `bake_duration_hours` | Moved | `fusion.plating.process.node.bake_duration_hours` |
|
||||
| `description` | Removed | Recipe already has description on the root node |
|
||||
| `sequence`, `active` | Removed | (record itself removed) |
|
||||
| `currency_id`, `default_cost` | Removed | Pricing logic moves to fp.pricing.rule with new keys |
|
||||
|
||||
Every field accounted for. Nothing dropped silently.
|
||||
Reference in New Issue
Block a user