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>
18 KiB
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.notesHtml — 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 forcertificate_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
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
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 referencingfp.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_codeas "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_pdfinfusion_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_idif set, elsewant_coc=True, want_thickness=Falseas 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:
<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.catalogwith emptypart_number→part_number = nameafter migrationfp.part.catalogwith emptyrevision→revision = 'A'after migrationfp.sale.description.templaterows with text indescription→ bothinternal_descriptionANDcustomer_facing_descriptionhold that same text; old column gone- Confirmed/done SO lines get
x_fc_internal_description = namebackfilled - 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
nameANDx_fc_internal_descriptionmust be non-empty - Cert resolution: part
coc_thickness+ partnersend_coc=False→ result(True, True)— part wins - Cert resolution: part
inherit+ partnersend_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 hassend_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-001at different revisions → no collision (different fp.part.catalog records; default_code on the service product is unchanged)
7.4 Regression on Phase 1–3 (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)
- Single-source cert resolution (§5.1) —
_fp_resolve_cert_requirementis 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. - 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.
- Idempotent migration (§3) — safe to re-run; doesn't fight future sub-project migrations.
- Additive SO line fields —
x_fc_internal_description,x_fc_description_template_idsit alongside future Sub 5 fields (x_fc_serial_number,x_fc_job_number,x_fc_thickness,x_fc_revision_snapshot) with zero touchpoints. - Clean removal of old
descriptioncolumn — migrated then dropped 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 splitfusion_plating_configurator/models/sale_order_line.py— addx_fc_internal_description,x_fc_description_template_idfusion_plating_bridge_mrp/models/mrp_production.py— add_fp_resolve_cert_requirement; rewire_fp_generate_cert_pdfto call it
Views
fusion_plating_configurator/views/fp_part_catalog_views.xml— required markers, cert requirement field, relabelsfusion_plating_configurator/views/fp_part_catalog_views.xml(Descriptions tab) — two-column repeaterfusion_plating_configurator/views/sale_order_views.xml— SO line internal-description fieldfusion_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 macrofusion_plating_reports/report/report_fp_invoice.xml— use macrofusion_plating_reports/report/report_fp_packing_slip.xml— use macrofusion_plating_reports/report/report_fp_bol.xml— use macrofusion_plating_reports/report/report_fp_work_order.xml— add internal description, keep product/default_codefusion_plating_reports/report/report_fp_job_traveller.xml— same as WOfusion_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 stepsfusion_plating_configurator/hooks.py— registerpost_init_hookif not already present
Security
- No new models, so no new ACL rows needed. Existing
fp.part.catalogandfp.sale.description.templateACLs already cover the new fields.
Manifest
fusion_plating_configurator/__manifest__.py— bump version; register new view / wizard changesfusion_plating_reports/__manifest__.py— register new macro filefusion_plating_bridge_mrp/__manifest__.py— bump version
10. Rollout
- Bump version on all three affected modules.
- 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. - Verify migrations ran: count backfilled records against expected.
- Run the smoke + E2E + regression suites.
- Commit + push when green.
11. Success Criteria
- Every part on the system has a non-empty
part_numberandrevision. - 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_requirementtononesuppresses CoC generation on MO done, even if the partner hasx_fc_send_coc=True. - Phase 1–3 QC regression suite stays fully green.
12. Open Questions (None Blocking)
All clarifying questions from the brainstorm (Q1–Q6) 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_codeas 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.