docs(plating): fine-tuning initiative roadmap + Sub 2 design spec

Captures the current state of the system-wide fine-tuning initiative so a
fresh Claude Code session can resume without context loss.

CLAUDE.md additions (fusion_plating/CLAUDE.md):
* Sub-project roadmap (Sub 1 through 8 + two deferred items)
* Sub 2 locked decisions (Q1–Q6 answers)
* Sub 2 defensive measures that prevent rework when later subs land
* Sub 6 / 7 / 8 previews from the client transcript
* Client-confirmed operational thresholds (tank polling, active tanks)
* How to resume in a fresh session

Sub 2 design spec (docs/superpowers/specs/):
* Part Data Model Overhaul — covers gaps 2b, 2c, 2d, 4
* 12 sections: scope, data model, migration, UI, cert resolution,
  reports, testing, defensive measures, files touched, rollout,
  success criteria, open questions
* All clarifying questions answered; zero placeholders
* Ready for writing-plans skill to generate implementation plan

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-21 19:53:40 -04:00
parent bdbfda7ce9
commit a15b75e38a
2 changed files with 460 additions and 0 deletions

View File

@@ -0,0 +1,381 @@
# Sub 2 — Part Data Model Overhaul
**Date:** 2026-04-21
**Module scope:** `fusion_plating_configurator`, `fusion_plating_reports`, `fusion_plating_bridge_mrp` (cert-resolution wiring)
**Status:** Design approved; ready for implementation plan
**Predecessor context:** Fine-Tuning Initiative, entry in `fusion_plating/CLAUDE.md`
---
## 1. Scope
Four gaps from the Fine-Tuning running plan:
| Gap | Summary |
|---|---|
| **2b** | Part Number + Revision become required; Part Name becomes optional |
| **2c** | Dual descriptions (internal + customer-facing) on every description-template row, flowing to SO line |
| **2d** | Per-part `certificate_requirement` with `inherit` fallback to the partner's existing flags |
| **4** | "SKU" relabel to "Part Number" in UI; customer-facing reports print `part_number`, not `default_code` |
### Out of scope (moved to later sub-projects)
- Part's default process / Process Composer → **Sub 3**
- Contract Review workflow → **Sub 4**
- Order-line additions (serial, job#, thickness dropdown, revision picker) → **Sub 5**
- Contact Profiles and per-location notifications → **Sub 6**
---
## 2. Data Model Changes
### 2.1 `fp.part.catalog` (customer parts library)
```
part_number : Char, required=True # was optional
revision : Char, required=True, default='A' # was optional
name : Char, required=False # was required
+ certificate_requirement : Selection(
('inherit', 'Inherit from Customer'),
('none', 'No Certificate'),
('coc', 'CoC Only'),
('coc_thickness', 'CoC + Thickness Report'),
), default='inherit', tracking=True
```
### 2.2 `fp.sale.description.template` (Descriptions tab repeater)
```
description : REMOVED (migrated out and dropped)
+ internal_description : Text, required=True
+ customer_facing_description : Text, required=True
```
### 2.3 `sale.order.line` (Odoo standard, extended)
```
name : (Odoo native, already required. Clarified
semantically as THE customer-facing
description — no repurpose, just formal
naming.)
+ x_fc_internal_description : Text, required=True
+ x_fc_description_template_id : Many2one('fp.sale.description.template')
(which template row the estimator picked)
```
### 2.4 Explicitly NOT changing
- `product.product` / `product.template.default_code` — untouched. The generic service products (EN Plating, Chrome, etc.) keep their SKUs.
- Revision chain (`parent_part_id`, `is_latest_revision`, `revision_ids`) — kept as-is (Sub 5 consumes it for the revision picker).
- `fp.part.catalog.notes` Html — kept as freeform drawer notes, not part of the description system.
- Partner-level cert flags (`res.partner.x_fc_send_coc`, `x_fc_send_thickness_report`) — kept as the fallback layer for `certificate_requirement = 'inherit'`.
---
## 3. Migration Strategy
Runs once via `post_init_hook` on the module upgrade. All steps idempotent (NULL/empty guards). Safe to re-run.
### Step 1 — Backfill `fp.part.catalog.part_number`
```sql
UPDATE fp_part_catalog
SET part_number = name
WHERE part_number IS NULL OR part_number = '';
```
Defensible: before this change, `name` was the part identifier. Copy over so `required=True` doesn't reject existing records on upgrade.
### Step 2 — Backfill `fp.part.catalog.revision`
```sql
UPDATE fp_part_catalog
SET revision = 'A'
WHERE revision IS NULL OR revision = '';
```
### Step 3 — Split `fp.sale.description.template.description`
For every template row with non-empty `description`:
```
internal_description := (original description value)
customer_facing_description := (original description value)
```
Both fields start with the same text. Estimators separate internal workflow text from customer text over time. The old `description` column is dropped in the same migration.
### Step 4 — Backfill `sale.order.line.x_fc_internal_description`
For every SO line in any state:
```
x_fc_internal_description := name (starter, copy from the existing line description)
```
The `required=True` constraint applies to **new lines only** — migration flag skips enforcement on historical records so upgrades don't fail on old orders.
### Step 5 — Default cert requirement
Every existing `fp.part.catalog` record gets `certificate_requirement = 'inherit'`. Zero behaviour change for in-flight jobs: the cert cascade still reads partner toggles until a shop admin explicitly sets a part to something other than `inherit`.
---
## 4. UI Changes
### 4.1 Part form (`fp.part.catalog`)
Top identity block:
```
Part Number* [required] │ Revision* [required, default 'A']
Part Name [optional] │ Customer [required]
```
New "Quality & Delivery" group:
```
Certificate Requirement: [Inherit from Customer ▾]
options: Inherit / None / CoC / CoC + Thickness
Help tooltip: "Inherit reads the customer's default
(Partner → Plating Documents tab)."
```
Descriptions tab — the existing repeater gains two columns:
| Template Name | Category | Internal Description | Customer-Facing Description | Used | Active |
|---|---|---|---|---|---|
Both description columns required to save a row. Hint above the table:
> "Internal = what the shop floor sees on the WO / traveler. Customer-Facing = what prints on SO, invoice, packing slip."
### 4.2 Direct-Order Wizard + SO Line form
Per line:
- Part picker (unchanged)
- **Description Template** dropdown — lists active templates where `part_catalog_id == the chosen part`. Partner-wide and coating-wide templates (which the data model allows) are **not** shown at order entry; the dropdown is narrowed to per-part templates to match the shop's mental model ("canned descriptions for this part").
- **Customer-Facing Description** text box — prefilled from template, editable
- **Internal Description** text box — prefilled from template, editable
If the part has zero templates, both boxes start blank and the estimator types them. Both required before save.
### 4.3 Universal SKU → Part Number relabel
- Fusion Plating form / list / search views: `string="SKU"``string="Part Number"` on fields referencing `fp.part.catalog.part_number`.
- Customer-facing reports: line rendering flows through the new QWeb macro (§6.1) — prints `part_number + revision + name` (customer-facing desc), no `[default_code]`, no product display name.
- Internal reports: show everything — part number, revision, customer-facing description, internal description, service product name, service SKU.
- Odoo-native screens (inventory, warehouse, product selector): untouched. Keep showing `default_code` as "SKU". Customer never sees these.
---
## 5. Certificate-Requirement Resolution (Runtime)
### 5.1 Single-source resolver (Defensive Measure 1)
All cert-related decisions route through one method on `mrp.production`:
```
def _fp_resolve_cert_requirement(self):
"""Returns (want_coc: bool, want_thickness: bool) for this MO.
Walks SO lines to collect each part's certificate_requirement.
Part-level wins; 'inherit' falls back to partner toggles.
Multi-line MO: strictest wins.
"""
```
### 5.2 Resolution order
```
1. Walk MO → origin (SO name) → sale.order → sale.order.lines → x_fc_part_catalog_id.
2. For each line's part:
if part.certificate_requirement != 'inherit':
want_coc_line = part.certificate_requirement in ('coc', 'coc_thickness')
want_thickness_line = part.certificate_requirement == 'coc_thickness'
else:
# Fallback to partner-level flags
want_coc_line = partner.x_fc_send_coc
want_thickness_line = partner.x_fc_send_thickness_report
3. Multi-line MO — strictest wins:
want_coc = any(want_coc_line across lines)
want_thickness = any(want_thickness_line across lines)
```
### 5.3 Callers (must use the resolver)
- `_fp_generate_cert_pdf` in `fusion_plating_bridge_mrp/models/mrp_production.py` (cert cascade on MO done)
- QC gate's thickness-required check (if/when it reads partner flags today — audit during implementation)
- Notification routing for auto-email (future Sub 6 will hook here)
### 5.4 Multi-line and fallback cases
- **Multi-line MO** — strictest wins so a customer never gets less paperwork than promised for any part in the batch.
- **MO with no SO link** (manual MO) — falls back to `mo.partner_id` if set, else `want_coc=True, want_thickness=False` as safe default.
### 5.5 Backward compatibility
Every existing part has `certificate_requirement = 'inherit'` after migration. The resolver falls through to partner toggles exactly as today. No behaviour change for any shipping job until a shop admin explicitly changes a part's cert requirement.
---
## 6. Report Impact
### 6.1 Shared QWeb line-header macro (Defensive Measure 2)
New template in `fusion_plating_reports`:
```xml
<template id="customer_line_header">
<!-- Renders part_number + revision + customer-facing description.
Falls back to the generic product name for non-part lines
(rush fees, freight, expedite charges).
Called from every customer-facing report. -->
</template>
```
All four customer-facing report line renderers call `<t t-call="fusion_plating_reports.customer_line_header"/>`. When Sub 5 adds the revision picker (and with it the revision snapshot on the SO line), the macro is updated once and all reports follow.
### 6.2 Per-report changes
| Report | Audience | Change |
|---|---|---|
| `report_fp_sale.xml` | Customer | Line header via macro: `part_number + revision + name`. Skip `[default_code]`, skip product display name. |
| `report_fp_invoice.xml` | Customer | Same (via macro) |
| `report_fp_packing_slip.xml` | Customer | Same (via macro) |
| `report_fp_bol.xml` | Customer | Same (via macro) |
| `report_fp_work_order.xml` | Internal (operator) | Keep product/service name + `default_code`. Add `part_number`, `revision`, `name` (customer-facing desc), `x_fc_internal_description` (ops workflow). |
| `report_fp_job_traveller.xml` | Internal | Same as work order. |
| `report_coc.xml` | Customer | Audit — reads `doc.part_number` on `fp.certificate`. Spot-check that the cert model populates `part_number` from `sale_order_line.x_fc_part_catalog_id.part_number`. No change expected. |
| `report_fp_receipt.xml` | Customer | Customer macro treatment. |
### 6.3 Fallback for non-part lines
The macro's fallback branch renders the generic product name for lines without `x_fc_part_catalog_id` (rush fees, freight, expedite). Those lines were never meant to show a part number.
---
## 7. Testing Strategy
### 7.1 Migration tests (one-shot, runs on upgrade)
- `fp.part.catalog` with empty `part_number``part_number = name` after migration
- `fp.part.catalog` with empty `revision``revision = 'A'` after migration
- `fp.sale.description.template` rows with text in `description` → both `internal_description` AND `customer_facing_description` hold that same text; old column gone
- Confirmed/done SO lines get `x_fc_internal_description = name` backfilled
- Every existing part ends with `certificate_requirement = 'inherit'`
- Running the migration a second time is a no-op (idempotency)
### 7.2 Unit tests
- New part without `part_number` → save rejected (UserError / validation)
- New part without `revision` → save rejected
- New part without `name` → saves fine (optional)
- Description template row: both descriptions must be non-empty
- SO line: both `name` AND `x_fc_internal_description` must be non-empty
- Cert resolution: part `coc_thickness` + partner `send_coc=False` → result `(True, True)` — part wins
- Cert resolution: part `inherit` + partner `send_coc=True, send_thickness=False` → result `(True, False)` — falls through
- Cert resolution: multi-line MO with parts `none` + `coc_thickness` → result `(True, True)` — strictest wins
- Cert resolution: MO with no SO link → safe fallback
### 7.3 End-to-end smoke (odoo-shell scripts, pattern from QC suite)
- Direct order → SO confirm → MO confirm → MO done → CoC generated with customer part number on page 1, no `[default_code]`
- Same flow with `certificate_requirement = 'none'` on the part → no CoC generated even if partner has `send_coc=True`
- Order entry: pick a description template row → both fields populate on the SO line → save → reopen → both fields persist → customer SO PDF shows only the customer-facing description
- Two customers both add a part numbered `WIDGET-001` at different revisions → no collision (different fp.part.catalog records; default_code on the service product is unchanged)
### 7.4 Regression on Phase 13 (QC work)
After the Sub 2 migration, re-run:
- `fp_qc_smoke.py` (9-step smoke)
- `fp_qc_e2e.py` (8-case edge suite)
- `fp_full_workflow.py` (full lifecycle)
All must stay green. Sub 2 only wires cert resolution through a new helper — QC checklist logic is untouched.
---
## 8. Defensive Measures (Prevent Rework When Later Subs Land)
1. **Single-source cert resolution** (§5.1) — `_fp_resolve_cert_requirement` is the only place partner-level fallback is read. When Sub 6 restructures partner flags into per-location or per-contact permissions, one function updates — no call-site hunt.
2. **Shared QWeb macro** (§6.1) — all four customer-facing reports render the line header through one template. Sub 5's revision picker updates the macro, all reports follow.
3. **Idempotent migration** (§3) — safe to re-run; doesn't fight future sub-project migrations.
4. **Additive SO line fields**`x_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 in the same migration. No deprecated-field confusion later.
---
## 9. Files Touched (Anticipated)
### Models
- `fusion_plating_configurator/models/fp_part_catalog.py` — field changes (part_number / revision required, name optional, + certificate_requirement)
- `fusion_plating_configurator/models/fp_sale_description_template.py` — field split
- `fusion_plating_configurator/models/sale_order_line.py` — add `x_fc_internal_description`, `x_fc_description_template_id`
- `fusion_plating_bridge_mrp/models/mrp_production.py` — add `_fp_resolve_cert_requirement`; rewire `_fp_generate_cert_pdf` to call it
### Views
- `fusion_plating_configurator/views/fp_part_catalog_views.xml` — required markers, cert requirement field, relabels
- `fusion_plating_configurator/views/fp_part_catalog_views.xml` (Descriptions tab) — two-column repeater
- `fusion_plating_configurator/views/sale_order_views.xml` — SO line internal-description field
- `fusion_plating_configurator/wizard/fp_direct_order_wizard_views.xml` — description-template picker + dual-description inputs
- Wherever "SKU" appears in views → relabel to "Part Number"
### Reports
- `fusion_plating_reports/report/report_fp_sale.xml` — use macro
- `fusion_plating_reports/report/report_fp_invoice.xml` — use macro
- `fusion_plating_reports/report/report_fp_packing_slip.xml` — use macro
- `fusion_plating_reports/report/report_fp_bol.xml` — use macro
- `fusion_plating_reports/report/report_fp_work_order.xml` — add internal description, keep product/default_code
- `fusion_plating_reports/report/report_fp_job_traveller.xml` — same as WO
- `fusion_plating_reports/report/customer_line_header.xml` — NEW macro
### Migration
- `fusion_plating_configurator/migrations/19.0.X.X.X/post-migration.py` — all five migration steps
- `fusion_plating_configurator/hooks.py` — register `post_init_hook` if not already present
### Security
- No new models, so no new ACL rows needed. Existing `fp.part.catalog` and `fp.sale.description.template` ACLs already cover the new fields.
### Manifest
- `fusion_plating_configurator/__manifest__.py` — bump version; register new view / wizard changes
- `fusion_plating_reports/__manifest__.py` — register new macro file
- `fusion_plating_bridge_mrp/__manifest__.py` — bump version
---
## 10. Rollout
1. Bump version on all three affected modules.
2. Push to entech via standard deploy: `systemctl stop odoo``odoo -d admin -u fusion_plating_configurator,fusion_plating_reports,fusion_plating_bridge_mrp --stop-after-init` → clear asset cache → `systemctl start odoo`.
3. Verify migrations ran: count backfilled records against expected.
4. Run the smoke + E2E + regression suites.
5. Commit + push when green.
---
## 11. Success Criteria
- Every part on the system has a non-empty `part_number` and `revision`.
- The Descriptions tab on every part shows the two-column repeater; old single-column layout is gone.
- Creating a sale order without filling both descriptions on each line is rejected.
- Customer-facing CoC / SO / invoice / packing slip / BoL never print Odoo's internal SKU.
- Setting a part's `certificate_requirement` to `none` suppresses CoC generation on MO done, even if the partner has `x_fc_send_coc=True`.
- Phase 13 QC regression suite stays fully green.
---
## 12. Open Questions (None Blocking)
All clarifying questions from the brainstorm (Q1Q6) are answered. No blocking open questions. Possible implementation-time discoveries (flagged for the plan, not for the spec):
- Exact list of QWeb reports that currently print `default_code` as a line-prefix (grep during implementation; the macro swap might touch one or two more than listed in §6.2).
- Whether any third-party portal template also renders line headers (unlikely; portal jobs show job-level data not line-level, but worth a sanity grep).
These are discovery items, not design decisions.

View File

@@ -356,3 +356,82 @@ See `K:\Github\RePackaged-Odoo\CLAUDE.md` for full details. Key points:
- 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) | Pending | Gap 1 |
| 2 | Part Data Model Overhaul (part#/rev required, dual descriptions, per-part cert requirement, SKU→Part Number on customer docs) | Design approved 2026-04-21 | 2b, 2c, 2d, 4 |
| 3 | Default Process + Composer per part (reuse recipe tree) | Pending | 2e, 2f |
| 4 | Contract Review two-portion workflow (QA Assistant + QA Manager; pre-production gate) | Pending | 2i |
| 5 | Order-line fields (serial, job#, thickness dropdown, revision picker) | Pending | 5, 6, Q2 |
| 6 | Contact Profiles & Communication Routing (sub-contacts + per-location notification lists + global contacts) | Pending | client transcript A/B/C |
| 7 | IoT tuning (configurable polling interval 1530 min, seed 610 tank sensors) | Pending | client transcript D |
| 8 | Receiving / Inspection / QC flow restructure (split receiving vs inspection; racking crew inspects, not receiver) | Pending | client transcript E |
| ∞ | 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 |
### 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 function** — `mrp.production._fp_resolve_cert_requirement(self)` returns `(want_coc, want_thickness)`. Every caller (cert cascade, QC gate, notification routing) goes through this. When Sub 6 restructures partner-level flags into location / contact permissions, one function updates — no call-site hunt.
2. **Shared QWeb line-header macro** — `fusion_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 fields** — `x_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.