Three related fixes on the Express Orders totals card:
1. Totals card now breaks out Subtotal / Tax / Tooling Charge /
Grand Total. Previously the "Subtotal" and "Grand Total" rows
both read from total_amount (same value rendered twice) and no
tax was shown at all. Customers on a fiscal position-mapped
tax rate (Ontario HST, etc.) had their taxes silently dropped
from the preview.
2. tooling_charge now feeds the Grand Total. The total_amount
compute previously summed line subtotals only. Added a real
SO line for the tooling charge in action_create_order so the
eventual sale.order.amount_total matches the preview AND the
invoice carries a "Tooling Charge" line item.
3. tax_ids is now visible as an optional column on the lines
list. Operator can see + override the auto-applied tax per
line. Default still comes from FP-SERVICE product mapped
through partner.property_account_position_id (fiscal position).
New compute fields on fp.direct.order.wizard:
- total_subtotal (sum of line.qty * line.unit_price, pre-tax)
- total_tax (sum of line + tooling taxes via compute_all)
- total_amount (subtotal + tax + tooling — was just subtotal)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous tightening removed the row-span but reintroduced a worse
problem: the tall PO block (with PO Pending + Expected By + chase
warning visible = ~250px) had only 2 small cells next to it
(Customer Job # / Job Sorting). 200px+ of vertical air below them
before row 3 started.
Layout now:
- Row 1: Customer (1-2) + Delivery Address (3-4)
- Rows 2-5 left: PO Block spans 4 grid rows (cols 1-2)
- Rows 2-5 right: 4 PAIRS of fields fill cols 3-4 in DOM order:
Row 2: Customer Job # + Job Sorting
Row 3: Material/Process + Lead Time
Row 4: Payment Terms + Delivery Method
Row 5: Pricelist + Quote Validity
- Row 6: Blanket SO + Invoice Strategy + conditional Deposit % / Progress %
(full 4-col width, kicks in after the PO block ends)
CSS Grid auto-flow places the right-side cells in the open positions
next to the row-span-4 PO block. Each grid row auto-sizes to the max
of the cells in that row (PO block top portion or the right pair),
so PO block height naturally aligns with the 4 right rows — no dead
air on either side.
User reported too much vertical air between fields. Two changes:
1. Removed grid-row: span 2 from the PO block. The row-span pattern
stretched each grid row to half the PO block's height (~125px each),
leaving empty space below Customer Job # / Job Sorting on row 2 and
below Material/Lead Time on row 3.
New layout:
- Row 1: Customer (1-2) + Delivery Address (3-4)
- Row 2: PO Block (1-2, naturally tall) + Customer Job # + Job Sorting
- Row 3: Material/Process + Lead Time + Payment Terms + Delivery Method
- Row 4: Pricelist + Quote Validity + Blanket SO + Invoice Strategy
- Row 5 (conditional): Deposit % or Progress % (when invoice strategy uses them)
PO block forces row 2 to be tall but cols 3-4 just sit at top — that
was the original mockup pattern, and it's denser overall because
rows 3+ are all the standard short height.
2. Tightened spacing in SCSS:
- Grid row gap 14px → 6px
- Cell label margin 0 (was 2px)
- Input padding 5px → 2px vertical, min-height 30px → 24px
- PO block padding 10px → 6/12/8px
- PO row gap 2px padding → 0 (min-height 28px keeps clickable target)
- PO chase text 11px → 10px, tighter line-height
Customer feedback: rows 2 (description) and 3 (serials) in the Part
cell rendered as read-only spans. User wanted to edit directly.
New writable computed fields on fp.direct.order.line:
- part_name_editable: compute reads part_catalog_id.name, inverse
writes back to part.name on the linked catalog record
- serials_text: compute joins serial_ids names with commas; inverse
parses the typed string and find-or-creates fp.serial records,
updates the line's serial_ids M2M
Removed the redundant rev separator (display_name already includes
'(Rev X)' so showing it twice was clutter). Rev edits happen by
editing the part record directly via the OPEN button.
OWL widget templates updated:
- Row 2: <input> bound to part_name_editable, t-on-change saves
- Row 3: <input> bound to serials_text, t-on-change parses + saves
SCSS:
- Row 2 input: italic, transparent border, focus tints background yellow
- Row 3 input: small grey text, comma-separated friendly placeholder
- Both disabled-look when no part is picked
Both inputs trigger the inverse method on blur. The G4 sync chain
takes over from there to push line.line_description etc. back to
the part as before — so editing in the line keeps the part defaults
fresh for future orders.
Four customer-feedback fixes (G1-G4):
G1 — Part cell display redundancy. fp.part.catalog.display_name was
showing 'PART (Rev X) — Name' which duplicated with my Part cell widget's
separately-rendered revision + name rows. Added @api.depends_context
('fp_express_part_picker') to _compute_display_name: when the context
flag is True, display_name returns JUST the part_number. The Express
view passes the flag on the part_catalog_id field, so the picker shows
'9876699373' and the widget's row 2/3 show the rev + name.
G2 — Material/Process Tag is now the order's RECIPE, not a free-text
shop tag. Converted material_process from Char to Many2One(fusion.
plating.process.node) with domain [('node_type','=','recipe')] on both
fp.direct.order.wizard AND sale.order. Pre-migration (19.0.22.1.0/
pre-migrate.py) drops the old VARCHAR column so Odoo recreates as
INTEGER FK. Per dev-stage policy, old tag data is dropped.
G3 — Auto-apply order recipe to every line. New onchange
_onchange_material_process_apply_to_lines on the wizard: when the
header recipe is picked / changed, propagate to every line's
process_variant_id (unless the line has an explicit per-line override
that doesn't match the previous header value).
Plus an override on fp.direct.order.line.create that seeds new lines'
process_variant_id from wizard.material_process. So a newly-added
line auto-inherits the order's recipe.
G4 — Auto-sync line edits back to the part catalog. New
_fp_sync_to_part method called from create() + write() on
fp.direct.order.line. Tracked fields:
- line_description → part.default_specification_text
- bake_instructions → part.default_bake_instructions
- thickness_range → part.x_fc_default_thickness_range
- masking_enabled → part.default_masking_enabled
- process_variant_id → part.default_process_variant_id
Future orders for the same part will auto-pull these updated defaults
via the existing _onchange_part_default_thickness chain. Last-write-
wins semantics across concurrent edits (acceptable per dev-stage).
Wasted-space audit revealed:
1. PO Block occupied a wide 2-col span but its inner inputs didn't
fill the available width (130px label column + narrow input area)
2. Customer Job # + Job Sorting placed in row 2 cols 3-4 next to the
tall PO block left empty vertical space below them
3. Row 2 col 3-4 was sparse because the PO block forced row 2 to be tall
Reorganized:
- PO Block now spans 2 grid ROWS (.row-span-2 class → grid-row: span 2)
- Customer Job # / Job Sorting flow into row 2 cols 3-4 (alongside PO top)
- Material/Process Tag / Lead Time flow into row 3 cols 3-4 (alongside
PO bottom) — filling the previously-empty space next to the PO block
- Row 4 (after PO ends): Payment Terms / Delivery Method / Pricelist /
Quote Validity — full 4-col width back
- Row 5: Blanket Sales Order + Invoice Strategy + conditional Deposit % /
Progress Initial % (only show when relevant invoice strategy picked)
Inside the PO block:
- Label column tightened 130px → 110px so the input takes more width
- Inputs + Many2One wrappers now have width: 100% propagated, so PO #
and Expected By inputs fill the available row width
- Upload button restyled with the accent colour (was the green default)
Net effect: same field count but tighter packing, no empty vertical
or horizontal space next to the PO block.
Root cause for column widths: Odoo 19's column_width_hook.js dynamically
sets inline widths on every cell at render time, overriding any CSS
width on td/th selectors. Confirmed by reading the hook source on
entech: 'A width can also be hardcoded in the arch (width="60px").'
Fix: set width='Npx' as an ARCH ATTRIBUTE on each <field> in the line
list:
- Part Number 230px, Line Job # 80px, Thickness 100px, Mask 55px,
Bake 120px, Qty 55px, Price 80px, Subtotal 90px, Action stack 60px
- Specification + Internal Notes get NO width → take remaining flex
space (responsive: layout adapts to viewport)
Root cause for missing checkbox: my SCSS underline-style override
selected ALL .o_field_widget input including type=checkbox, rendering
checkboxes as 30px-tall full-width transparent text inputs.
Fix: exclude type=checkbox/radio/file from the underline rule, and
add explicit rendering for type=checkbox (18px square, accent-coloured)
inside .o_fp_xpr_cell. The Blanket Sales Order checkbox + the inline
Block partial shipments checkbox are now both visible.
1. Blanket Sales Order — match legacy field shape. Renamed label from
'Blanket SO' to 'Blanket Sales Order' (matches legacy view), removed
the boolean_toggle widget (defaults to checkbox), and added the
sibling 'block_partial_shipments' field inline (only visible when
blanket is checked, with 'Block partial shipments' helper text).
2. Column widths — give roomier columns where data needs space, tighten
numeric columns. Part Number 230px, Specification min 220px,
Internal Notes min 140px, Qty 60px, Price 80px, Subtotal 90px,
Mask 55px, Bake 130px, Action stack 60px.
3. Stacked DWG / OPEN buttons — new OWL widget FpExpressActionBtns
(express_action_btns.js + .xml) renders both buttons vertically in
ONE column to save horizontal space. Widget binds to a new
action_btns_anchor field (related from part_catalog_id) on the
line. Each button shows tooltip + disabled state when no part is
picked; DWG triggers the native file picker, OPEN navigates to the
part record.
4. Field activation — clicking the cell anywhere now focuses the
input, not just clicking the label. Achieved via:
- cursor: text on .o_fp_xpr_cell
- cursor: pointer on labels
- min-height: 30px on all inputs (larger click target)
- width: 100% propagated through Many2One wrappers (.o-dropdown,
.o-autocomplete) so the input genuinely fills the cell
- box-sizing: border-box so widths are predictable
- Background tint on focus for visual feedback
NEW OWL widgets:
- FpExpressPartCell (static/src/js/express_part_cell.js + .xml) — multi-row
Part cell. Wraps Many2OneField for the part picker (row 1: part# / rev,
bold). Below it: row 2 part description (italic muted), row 3 serial #s
joined + '+ bulk' button that triggers the existing bulk-add wizard.
- FpExpressBakePill (static/src/js/express_bake_pill.js + .xml) — click-
to-edit Bake pill. Renders amber pill when set, italic muted 'no bake'
when empty. Click swaps to inline textarea + Save / Clear / Cancel.
NEW fields:
- fp.direct.order.line.part_number_display / part_revision_display /
part_name_display (related Char from fp.part.catalog) — fed to the
Part cell widget so it can render multi-row without RPC.
- fp.direct.order.wizard.tooling_charge (Monetary) — surfaced in the
Totals card on the Express form.
- fp.direct.order.wizard.po_status (computed Selection
received/pending/missing) — drives the PO Block status badge.
- sale.order.x_fc_tooling_charge (Monetary) — receives wizard.tooling_charge
at confirm.
View updates (fp_express_order_views.xml):
- PO block header now shows the PO status pill (green Received,
amber Pending, red Missing)
- Order Lines legend bar (Mask / Bake pill / DWG / OPEN explainers)
- Part Number column uses widget='fp_express_part_cell' — single cell
with 3 internal rows
- Bake column uses widget='fp_express_bake_pill' — interactive pill
- Totals card now has Subtotal / Tooling Charge / Total Lines / Total
Quantity / Grand Total + currency pill
SCSS adds:
- Multi-row part cell styles (internal borders, bold/italic/muted rows)
- Bake pill (has-bake amber, no-bake italic muted) + inline editor
- Legend bar (section background, gap-spaced explainer chips)
- PO status pill colour scheme
- Bulk button styling
Wizard's action_create_order now carries tooling_charge to the SO at
confirm so it persists on the resulting sale.order.
Previous rebuild used Odoo's <group col='4'> which renders as an HTML
table — colspan+nesting broke into a vertical stack. Replaced entirely
with raw <div> + CSS Grid (display: grid; grid-template-columns:
repeat(4, 1fr)) so the header layout matches the mockup exactly:
- Row 1: Customer (span 2) + Shipping Address (span 2)
- Row 2: PO block (span 2, accent-bordered card with PO#/PDF/Pending
toggle/Expected date stacked + chase warning) + Customer Job # + Job Sorting
- Row 3: Material/Process Tag + Lead Time (inline X to Y) + Payment
Terms + Delivery Method
- Row 4: Blanket SO + Currency/Pricelist + Quote Validity + Invoice Strategy
Footer also rebuilt as CSS Grid (1fr 320px) — Notes/Terms cards
stacked in the left column, Totals card with Grand Total + currency
pill in the right column. Each card has a title + subtitle + body
matching the mockup's card chrome.
SCSS overrides Odoo's default field chrome inside .o_fp_xpr_cell so
inputs render with the mockup's underline style (no Bootstrap form-
control border, just a 1px bottom-border that thickens on focus).
Restructures the Express form to align with the brainstorming mockup:
Header (4-column grid via <group col='4'>):
- Row 1: Customer (colspan=2) + Shipping Address (colspan=2)
- Row 2: Consolidated PO Block (colspan=2 with PO#/PDF/Pending toggle/
Expected date stacked + chase warning inline) + Customer Job # + Job Sorting
- Row 3: Material/Process Tag + Lead Time (X to Y inline) + Payment Terms + Delivery Method
- Row 4: Blanket SO + Currency/Pricelist + Quote Validity + Invoice Strategy
Lines: 13 inline columns including the Express-specific Line Job #,
masking toggle, bake text, plus per-line action buttons (DWG, OPEN,
+ bulk) wired to the Phase B helpers.
Footer: side-by-side cards — Notes + Terms stacked in the left card,
Totals card on the right with Total Lines / Total Qty / Grand Total
+ currency pill.
SCSS adds:
- PO block: accent-bordered card-within-card
- Lines: tight spreadsheet borders, hover row highlight
- Bake column: amber pill style, italic 'no bake' for empty
- Customer Line Job #: bold, uppercase, narrow column
- Inline action buttons: small uppercase bordered chips
- Footer cards with prominent Grand Total + currency pill
OWL multi-row Part cell (FpExpressPartCell) and click-to-edit Bake
pill (FpExpressBakePill) are still deferred — they need real OWL
components, separate pass.
C1: product.pricelist._compute_display_name override gated by the
'fp_express_currency_picker' context flag (set on the Express form's
pricelist_id field). When active, prefixes the dropdown label with
the currency code: 'CAD — Public Pricelist (CAD)'. Elsewhere the
standard display name is unchanged.
C3: SCSS tokens + base styles for the Express form. Tokens use the
compile-time @if $o-webclient-color-scheme branch per the project's
'Dark Mode' rule — same SCSS compiles into both bundles with different
hex values. Token vars wrapped in CSS custom properties so downstream
modules can override for per-shop branding without recompiling.
Base styles: spreadsheet-feel table borders, bake-cell inset-pill,
customer line ref bold/uppercase, accent section markers.
5 fixes discovered during the live deploy to entech LXC 111:
1. pre-migrate.py to rename old configurator's 'Shop Manager' group BEFORE
new core 'Shop Manager v2' XML loads (cross-module name collision on
res_groups_name_uniq).
2. res_company_views.xml: dropped ref() inside <field domain=> attribute
(Odoo 19 view validator interprets it as a field name).
3. sale_order_views.xml: replaced 3 separate xpaths for amount_total /
amount_untaxed / amount_tax with a single xpath on tax_totals widget
(Odoo 19 sale.view_order_form uses one widget instead of separate fields).
4. fp_cert_security.xml: certificate_type field, not cert_type. FAIR is a
separate model so the rule only restricts cert_type='nadcap_cert' now.
5. fp_certificate_views.xml + fp_capa_views.xml + fp_customer_spec_views.xml:
stripped user_has_groups() from invisible= / readonly= attrs (Odoo 19
view validator interprets as field name). Model-layer ACLs and ir.rules
already enforce the same restrictions.
Also fixed res.groups.users -> user_ids in fp_migration.py (Odoo 19 rename,
caught when manually invoking _fp_notify_owners post-deploy).
CLAUDE.md updated with 4 new rules (13e cross-module name collisions,
13f ref() in domain, 13g tax_totals widget, 13h user_has_groups in attrs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-deploy fixes for Phase 1 permissions overhaul branch (catches by
final-reviewer subagent + main session).
CRITICAL FIXES:
C1: groups_id -> group_ids (Odoo 19 field rename). Affected ~30 sites
across 4 model files, 1 view, 7 test files. Documented project
gotcha (feedback_odoo19_groups_id_renamed.md) that the implementer
subagents missed because they don't see user memory.
C2: action_fp_resolve_plating_landing server action now calls
env['ir.actions.act_window'].sudo()._fp_resolve_landing_for_current_user()
instead of the old inline priority chain. Phase E's role-based
dispatch was previously dead code.
C3: New migrations/19.0.21.1.0/post-migrate.py triggers
_fp_post_init_role_migration(env) on -u. post_init_hook only fires
on INSTALL in Odoo 19, not UPGRADE -- so Phase H's preview creation
wouldn't have auto-fired on entech without this script. Module
version bumped to 19.0.21.1.0 to match the migration directory.
C4: Team kanban template rewritten for Odoo 19 (<t t-name='card'> with
semantic <aside>/<main>) instead of legacy <t t-name='kanban-box'>.
Previous template threw 'Missing card template' at render.
IMPORTANT FIXES:
I1: SO state=sent Confirm button (id='action_confirm') now also gated
to group_fp_sales_manager. Previously only the state=draft button
was gated; Sales Reps could send-and-confirm via the secondary path.
I2: Designated Officials picker domain uses all_group_ids (transitive)
instead of group_ids (explicit only). Owner users now correctly
appear as eligible CGP DO candidates via the implied_ids chain.
I3: test_menu_visibility.py compliance hub xmlid corrected to
fusion_plating.menu_fp_compliance_hub (was
fusion_plating_compliance.menu_fp_compliance_hub which doesn't exist
-- the hub menu is defined in core's fp_menu.xml). Tests were
silently skipTest-ing.
I4: _inverse_plating_role chatter audit reads old role from DB via SQL
(bypasses cache) so 'old -> new' displays actual values, and
short-circuits no-op writes.
I5: _FP_ROLE_MAPPING_RULES reordered: cgp_designated_official fires
BEFORE admin/uid_1_or_2 so admin+DO users keep the capability_delta
marker that triggers res.company.x_fc_cgp_designated_official_id
auto-set during migration.
I6: _cron_purge_expired_migrations skips groups with active users
instead of unlink-ing unconditionally. Defense against rollback
safety being bypassed by manual role assignments post-migration.
CLAUDE.md updated with 3 new durable rules (13b kanban card template,
13c group_ids vs all_group_ids, 13d post_init_hook only on install).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase D Task D5 of permissions overhaul. Adds explicit groups= to
form-level elements so non-matching roles don't even SEE the buttons
they can't use:
- SO Confirm button → group_fp_sales_manager (Sales Rep sees the SO
in draft but no Confirm button — matches model-level gate from Phase G)
- SO pricing fields (price_unit/subtotal/total/untaxed/tax) →
group_fp_sales_rep (Technician/Shop Manager don't see pricing if
they navigate to an SO)
- Partner Account Hold tab → group_fp_manager (was the fold-in
group_fp_accounting; the audit-finding-11 _administrator typo lives
in res_partner.py and is Phase G's fix)
- CAPA Close + all state-transition buttons → group_fp_quality_manager;
edit fields use readonly="not user_has_groups(...)" so Manager
retains read+comment per spec section 2.C
- Audit Start/Findings/Close buttons → group_fp_quality_manager
- AVL Approve/Suspend/Reinstate/Remove → group_fp_quality_manager
(model uses Suspend+Remove instead of spec's literal 'Disqualify';
both surfaces gated, semantics match)
- Customer Spec edit fields → readonly for non-QM (Manager keeps
read access per spec; only inputs lock)
- FAIR Approve/Reject buttons → group_fp_quality_manager (Submit-
for-Review and Reset stay open to whoever created the FAIR)
- Certificate Issue button — Strategy B chosen: single button hidden
when cert_type=nadcap_cert AND user is not QM. Cleaner than splitting
into two buttons; no separate action_sign exists on fp.certificate
(Issue is the sign+publish action). FAIR lives in its own model;
fp.certificate only has nadcap_cert as a special type. The ir.rule
from Phase C enforces model-level writes independently.
- CGP form buttons (7 view files: ai, controlled_good, psa,
receipt_shipment, registration, security_incident, visitor) →
group_fp_quality_manager on every action button
Defense in depth: ir.rules and ACLs (from Phases B + C) already
restrict model access. These view gates are the UI layer that
matches.
Concerns:
- Spec line 192 names 'sale.order view — x_fc_account_hold_override'
but no such field exists in the codebase. Closest practical match
was the partner-side Account Hold management tab, which already had
a group= attribute. Re-gated there; no SO-side field to gate.
- AVL model has no action_disqualify per spec; uses suspend+remove.
Both gated to QM.
- fp.certificate has no action_sign (only action_issue). FAIR's
approve/reject covers the FAIR side; nadcap-cert Issue covers the
cert side via Strategy B.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase D Tasks D1-D4 of permissions overhaul. Adds explicit groups=
attributes to:
- 9 top-level Plating menus (matrix per spec Section 2.E)
- Quality submenus: Audits, Customer Specs, AVL → QM-only
- Compliance hub child submenus (CGP, General, Safety, Aerospace,
Nuclear) → QM-only
- Operations submenus: Maintenance, Move Log, Labor History → Shop
Manager+; Replenishment Suggestions → Manager+
Replaces fragile inheritance + action-ACL-based visibility with
explicit per-menu gates. Now every role's menu tree is deterministic.
Also adds fusion_plating/tests/test_menu_visibility.py — per-role
matrix tests using ir.ui.menu.search_count with the test user.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per client direction: every order is a thickness RANGE (e.g.
"0.0005-0.0008 mils" or "5-10 mils"), never a single value. The
old picker model (fp.recipe.thickness with a single 'value' Float)
was modelling the wrong concept and overcrowding the order entry
UI. Replaced with one free-text Char field that auto-fills from
last-used or part default.
DELETED entirely:
- fp.recipe.thickness model (file + view + ACL + manifest entry)
- recipe.thickness_option_ids One2many (the picker source)
- "Thickness Options" inline list on the recipe form
- sale.order.line.x_fc_thickness_id (M2O picker)
- account.move.line.x_fc_thickness_id
- fp.delivery.x_fc_thickness_id
- fp.direct.order.line.thickness_id
ADDED:
- sale.order.line.x_fc_thickness_range (Char) — operator types range
- account.move.line.x_fc_thickness_range — for invoice rendering
- fp.delivery.x_fc_thickness_range — for packing slip
- fp.direct.order.line.thickness_range — for the wizard
- fp.part.catalog.x_fc_default_thickness_range — part default
AUTO-FILL CHAIN (sale.order.line + wizard line):
1. Operator already typed → keep
2. Most recent SO line for (this part, this customer) with a
non-empty thickness_range → copy that
3. part.x_fc_default_thickness_range → copy
4. Blank — operator types
Implemented as both an @api.onchange (interactive) AND a
create() override (programmatic — wizard, sale_mrp bridge,
imports). Same logic in both paths.
WIZARD push-to-defaults: when "Save as Default" toggle is ticked
on a wizard line, persist the line's thickness_range to
part.x_fc_default_thickness_range so future first-customer orders
get a sensible starting point.
REPORTS: customer_line_header.xml + report_fp_wo_sticker.xml now
print the Char range as-typed (no display_name lookup needed).
KEPT (admin documentation only — doesn't affect order entry):
- recipe.thickness_min, thickness_max, thickness_uom on the recipe
root: documents the recipe's CAPABILITY range. No UI gate; just
for spec authors to record what the chemistry can produce.
JOB GROUPING: fp.job auto-create groups SO lines by (recipe, part,
spec, thickness, serial). Updated to key on the thickness_range
Char (stripped) instead of the deleted thickness_id integer.
DB cleanup: --update=base ran on the upgrade, dropping the
fp_recipe_thickness table + the four x_fc_thickness_id columns.
Existing data was already nulled in earlier dev work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After Phase E removed Coating Config + Treatments + Customer Price List
+ Coating Thickness from the Configurator submenu, only 3 admin items
remained — not enough to justify a top-level menu just for an
estimator.
Re-homed:
- Pricing Rules → Configuration → Pricing & Billing
(sequence 40, joins Invoice Strategy
Defaults + Account Holds)
- Materials → Configuration → Materials & Tanks
(sequence 40, joins Bath Parameters,
Replenishment Rules, Chemicals,
Rack Tags, Calibration Equipment)
- Line Description Templates → Configuration → Quality & Documents
(sequence 90, joins Notification
Templates — same "templates" pattern)
All three keep estimator visibility (group_fp_estimator) plus manager
access. Top-level menu count under "Plating" drops from 9 visible to 8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec-side picker (x_fc_customer_spec_id / customer_spec_id) added on:
- sale.order.line (via quality inherit — onchange autofill, create()
fallback to part default, _prepare_invoice_line carry)
- account.move.line (via quality inherit — invoice rendering)
- fp.part.catalog (via quality inherit — x_fc_default_customer_spec_id)
- fp.direct.order.line (via quality inherit — wizard picker + autofill)
- fp.direct.order.wizard (action_create_order post-creates spec on SO line)
Thickness picker switched to fp.recipe.thickness (replaces coating-scoped):
- sale.order.line.x_fc_thickness_id comodel + domain rewired to recipe
- account.move.line + fp.delivery same
- fp.direct.order.line.thickness_id same
View inherits in quality add Specification picker next to legacy
Primary Treatment column on:
- SO form line tree
- part catalog Default Treatments block
- direct-order wizard line tree + drawer
Wizard files (fp.contract.review.client.email.wizard) pulled from
entech into the repo — they were ahead of the repo. Quality __init__
now imports wizards/.
Legacy x_fc_coating_config_id + treatment_ids remain visible during
transition; Phase E removes them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements 2026-04-29-step-library-audit-design.md. Bumps fusion_plating
to 19.0.18.7.0, fusion_plating_jobs to 19.0.8.12.0, fusion_plating_reports
to 19.0.10.2.0.
LIBRARY EXPANSION
- 8 new Step Kinds: Receiving, Electroclean, Strike, Salt Spray,
Adhesion Test, Hardness Test, Packaging, Tank Replenishment
- 4 new input types: photo, multi_point_thickness, bath_chemistry_panel, ph
- DEFAULT_INPUTS_BY_KIND rewritten to seed audit-grade prompts on every
kind (bath IDs, photos, multi-point thickness, signatures, etc.)
- + Common Audit Fields one-click button on the library template form
- Default Operator Instructions relabel + alert callout
PER-RECIPE CONFIGURABILITY
- collect (Boolean) per recipe-step input prompt — opt out without delete
- collect_measurements (Boolean) master switch on recipe step — when off,
wizard skips entirely
- template_input_id (Many2one) traceability link from recipe to library
- Recipe-step backend form view exposes the new fields with handle drag,
toggle, target range, and library-source column
RUNTIME WIRING
- Step input wizard filters node.input_ids to step_input AND collect=True;
short-circuits on collect_measurements=False
- New input types: photo (image widget + ir.attachment), multi-point
thickness (5 readings + auto avg, skips empty cells), bath chemistry
panel (pH/conc/temp/bath bundle), pH (0-14 numeric)
- Composite values JSON-serialized into value_text; photo via attachment
CoC REPORT
- Filters captured prompts to collect=True only
- Renders new input types with appropriate format
MIGRATION (post-migrate.py for 19.0.18.7.0)
- Backfills collect=True on recipe-step inputs
- Backfills collect_measurements=True on recipe steps
- Re-runs action_seed_default_inputs on every existing template
(idempotent, preserves user edits)
- Backfills template_input_id by name-matching against source library
template (handles JSONB vs varchar name columns)
SEED DATA
- 8 example templates (one per new kind) in fp_step_template_data.xml
with noupdate=1
BATTLE TEST
- bt_step_library_audit.py: 29 assertions all PASS on entech
OWL EDITOR EXTENSION DEFERRED
- The simple recipe editor's per-step Instructions/Measurements
expansions were not implemented in this pass; users configure via the
backend recipe-step form. Track follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 collapses the Plating app's 17 top-level menus down to 6
domains (Sales, Operations, Receiving & Shipping, Quality,
Compliance, Configuration) so users no longer scroll a 17-item
sidebar to find one thing.
Re-parented (no XML id changes — bookmarks still work):
- fusion_plating_compliance.menu_fp_compliance_root
→ menu_fp_compliance_hub (renamed 'General')
- fusion_plating_safety.menu_fp_safety_root
→ menu_fp_compliance_hub (renamed 'Safety / WHMIS')
- fusion_plating_aerospace.menu_fp_aerospace
→ menu_fp_compliance_hub (renamed 'Aerospace (AS9100 / Nadcap)')
- fusion_plating_nuclear.menu_fp_nuclear
→ menu_fp_compliance_hub (renamed 'Nuclear (CSA N299 / CNSC)')
- fusion_plating_cgp.menu_fp_cgp
→ menu_fp_compliance_hub (renamed 'Controlled Goods (CGP)')
- fusion_plating_certificates.menu_fp_certificates
→ menu_fp_quality (Certs are a Quality output, not a separate
top-level concern)
- fusion_plating_bridge_maintenance.menu_fp_maintenance
→ menu_fp_operations
- fusion_plating.menu_fp_job_step_move (Move Log)
→ menu_fp_operations
- fusion_plating.menu_fp_job_step_timelog (Labor History)
→ menu_fp_operations
The new menu_fp_compliance_hub is supervisor-gated; underlying
verticals retain their own group locks (CGP officer, etc.).
Settings menu remains manager-gated through inheritance from
menu_fp_config (already in place).
NEW — Plating landing-page resolver:
- ir.actions.act_window.x_fc_pickable_landing (Boolean tag for
curated picklist; flagged on Sale Orders, Quotations, Process
Recipes for Phase 1; more in Phase 2)
- res.company.x_fc_default_landing_action_id (admin sets fallback)
- res.users.x_fc_plating_landing_action_id (per-user override)
- ir.actions.server action_fp_resolve_plating_landing — picks
user → company → Sale Orders fallback at click time
- menu_fp_root rewired to call the resolver
- User profile + Settings tabs surface the dropdowns
Configurator's earlier menu_fp_root override (action_fp_sale_orders
direct) removed — core's resolver now owns the routing.
Versions bumped: fusion_plating 19.0.11.0.0, configurator
19.0.17.16.0, plus 7 dependent modules patched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fusion_plating: tank field labels (Code → Tank Number, Tank → Tank Name)
+ state-control header buttons (Mark Empty/Filled/In Use/Draining/
Maintenance/Out of Service) with chatter audit logging.
- fusion_plating_configurator: Plating app default landing screen = Sale
Orders, while keeping menu name as 'Plating'.
- fusion_plating_jobs: SO smart-button label 'Plating Jobs' → 'WO'.
Already deployed and verified on entech earlier in the session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Customer feedback: some customers don't send their PO with the
initial order — they send it days or weeks later. The system was
blocking SO confirmation without a PO, which forced the shop to
either wait on paperwork or ask a manager for a formal override.
New estimator-level path: a "PO Pending" boolean on sale.order +
an optional "PO Expected By" date.
* Confirm without a PO# / PO document when PO Pending is ticked.
* action_confirm skips the hard error if po_pending OR po_override
is set (keeps the existing manager-override path too).
* On confirm with PO Pending, the system schedules a chase
activity for po_expected_date (or +3 days if blank), assigned
via mail.activity so it shows up in the sales user's activity
list. Chatter note logged so audit is obvious.
* Direct-order wizard: po_number and po_attachment_file become
optional. Ticking "PO Pending" in the wizard is the trade-in;
a help note under the toggle explains the chase behaviour.
* Once the PO arrives, user fills in the PO# / uploads the doc,
and turns PO Pending off — existing downstream flow resumes.
Difference from x_fc_po_override (kept):
* PO Override = manager waiver, permanent ("handshake deal").
* PO Pending = estimator flag, time-boxed ("customer will send it
by Friday").
fusion_plating_configurator → 19.0.14.0.0
fusion_plating_invoicing → 19.0.3.0.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two user-reported gaps:
1. The Process Recipes list was about to be flooded by part-scoped
clones as soon as parts started carrying customised processes —
thousands of clones would bury the handful of real shared
templates (General Processing, Anodize, etc.).
Fix: the main Process Recipes action now narrows to
part_catalog_id = False so shared templates stay alone in that
view. A sibling menu "Part Processes" (Plating → Operations →
Part Processes) shows the inverse list — every part-cloned
process, grouped by part — so admins can audit clones without
cluttering the templates list.
Search view gains two toggle filters (Shared Templates /
Part-Scoped) and a "Group by Part" option. The node list gains
an optional "Part" column.
Split across modules: core owns the base search / tree / action
(unchanged); configurator owns all the part_catalog_id-dependent
pieces (filter extensions, list column, narrower domain,
"Part Processes" action + menu). Keeps the dependency direction
clean — configurator always depends on core, never the other way.
2. Added a "Process" smart button to the part form's button box.
Shows either a green check (composed) or "None" (not yet
composed) and opens the part-scoped Composer on click. Gives
users one-tap access from any part form without hunting through
the Process tab.
fusion_plating → 19.0.8.0.0
fusion_plating_configurator → 19.0.13.1.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four new fields on every sale.order.line, propagated through to MO,
Delivery, and Invoice for end-to-end traceability:
- fp.serial registry (new model in configurator) with smart-button
traceability to Sale Order, MO, Delivery, Invoice, Part. M2O on SO
line; optional; user types a customer serial or clicks Generate
Serial for a sequence-backed one. Reverse O2M links split across
configurator (invoice) / bridge_mrp (MO) / logistics (delivery) so
module load order is respected.
- x_fc_job_number on SO line, auto-sequenced FP-JOB-NNNNN on SO
confirm. Editable — shops can override for customer/legacy schemes.
- fp.coating.thickness (new child of fp.coating.config) with per-
config discrete thickness options; x_fc_thickness_id on SO line
domain-filtered to the line's coating. Auto-clears when coating
changes.
- x_fc_revision_snapshot Char on SO line, frozen from
x_fc_part_catalog_id.revision at save. Protects historical SOs from
later catalog edits. Secondary "Revision" picker on the tree view
lets users switch between prior revisions of the same part number;
the Part M2O still surfaces only is_latest_revision rows.
Reports (CoC, packing slip, invoice, BoL) pick up all four via the
Sub 2 customer_line_header macro — one macro edit, four reports.
Smoke on entech: 11 assertions pass including revision snapshot,
generate-serial button, typed-serial create-on-fly, coating→thickness
domain reset, SO confirm auto job#, and MO traceability carry.
Module version bumps:
fusion_plating_configurator → 19.0.12.0.0
fusion_plating_bridge_mrp → 19.0.11.0.0
fusion_plating_logistics → 19.0.2.0.0 (+depends configurator)
fusion_plating_reports → 19.0.5.1.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add fp_part_composer_controller with 3 JSON-RPC endpoints:
/fp/part/composer/state, /fp/part/composer/templates,
/fp/part/composer/load_template (deep-clones a shared template
into a part-owned tree inside a cr.savepoint, sets
fp.part.catalog.default_process_id atomically)
- _clone_subtree copies name/sequence/opt_in_out/treatment_uom plus
description/notes/icon/color/timing/behaviour/work_center/process_type
and stamps part_catalog_id + cloned_from_id on every node
- Add fp_part_process_composer OWL client action (JS + XML + SCSS):
picks template from dropdown, clones, hands off to existing
fp_recipe_tree_editor via context={recipe_id, part_id}
- Add Process tab on part form with readonly default_process_id
field and Compose button calling action_open_part_composer
- Register new assets in web.assets_backend, bump configurator
version to 19.0.11.0.0
Removes the `description` field from `fp.sale.description.template` now
that all readers (reports, wizard, sale line) consume the new
`internal_description` + `customer_facing_description` pair.
- Model: drop `description = fields.Text(...)` declaration
- Migration 19.0.9.0.0 Step 6: `ALTER TABLE ... DROP COLUMN IF EXISTS description`
- Template form/search views: swap `description` for the two new fields
- Seed data: write new fields instead of legacy column (dupes old text into both)
- Direct-order wizard: remove `tpl.description` fallback in both onchange handlers
Entech column dropped via Odoo's auto-schema-sync during module upgrade
(migration step is for fresh installs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the per-part description template on the SO line list alongside
a hidden-by-default internal description column. Picking a template
fires an onchange that copies `customer_facing_description` into Odoo's
standard `name` (customer-visible) and `internal_description` into
x_fc_internal_description (shop-floor / WO only). Estimator can edit
either field after the template is applied.
The template picker's domain filters by the line's part, and the field
stays hidden until a part is chosen — avoids showing every global
template when the line is blank.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five fixes from the end-to-end UAT debrief:
1. Menu discoverability (HIGH)
Added a prominent "+ New Direct Order" button in the Sale Orders
list header toolbar (class=btn-primary, display=always). The
existing menuitem at Plating > Sales > New Direct Order was
buried in a submenu that didn't always expand; the toolbar
button is a guaranteed entry point from the most common screen.
2. Escape/X destroys wizard state (HIGH)
Added a prominent info banner at the top of the wizard form:
"Changes are not saved until you click Create & Confirm Order.
Closing this window (Esc or X) discards your entries." The
Cancel button now has confirm="Discard this order? All header
data and line items will be lost." so the intentional-cancel
path also prompts.
3. Shell/cron crash in _fp_auto_create_mo (MEDIUM)
bridge_mrp/models/sale_order.py:232-264 used _() inside list
comprehensions to format the internal chatter summary of newly
created / adopted MOs. _() resolves language from env.context,
which is empty in odoo-shell and cron contexts — triggering a
translate.get_text_alias crash AFTER the MOs had been created.
These strings are internal audit log text, not user-facing UI;
dropped the _() wrappers so the message builds safely from any
context. Same for the per-group error-message on savepoint
rollback.
4. Misleading "100%" margin (MEDIUM)
x_fc_margin_percent displayed 100% on every SO because the cost
rollup from fp.coating.config.unit_cost isn't populated yet.
Added x_fc_margin_available Boolean (True only when at least
one line's coating has a non-zero unit_cost). The SO Plating
tab now hides the margin numbers when margin_available=False
and shows an inline muted note: "Margin n/a — coating cost
rollup not yet populated on any line's treatment."
5. Account Hold banner too loud (LOW)
fusion_plating_invoicing was injecting a full-height danger
alert above every SO header. Slimmed it to a one-line compact
alert with icon: "Account Hold — SO confirmation, invoicing
and shipping are blocked for non-managers." Half the vertical
footprint, less visual competition with the Plating chip bar.
Verified via UAT on S00071.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ships the remaining items from the Sales UX Uplift plan:
D2 BOM Items kanban
New view_sale_order_line_bom_kanban grouped by x_fc_part_catalog_id.
Smart button 'BOM Items' on SO form opens it.
D5 Archive line
x_fc_archived Boolean on sale.order.line plus action_archive_line /
action_unarchive_line. Acknowledgement report filters out archived
lines.
D6 Add Quoted Lines sub-wizard
New fp.add.from.quote.wizard parallel to fp.add.from.so.wizard. Pick
quotes for this customer and clone them into direct-order lines
carrying part, coating, qty, unit price (from calculated or
override), and notes. Button '+ Add From Quotes' on wizard Lines tab.
D7 SO Acknowledgement PDF
New ir.actions.report + QWeb template in configurator/report/.
Header shows customer / contact / PO / Customer Job #, Bill-To,
Ship-To, planned start + customer deadline + ship-via. Line table
skips archived lines. Includes external notes, blanket-order
callout, and customer-signature + vendor-signature blocks.
Binding added to sale.order so it shows up under Print menu.
D9 Quick-nav chip bar
New smart buttons on SO form: Invoices / Pickings / NCRs / Files
with counts and icons. Each opens a filtered list. NCR button
appears only when fusion_plating_quality is installed.
D10 SO/WO perspective toggle
view_sale_order_line_wo_kanban grouped by x_fc_wo_group_tag. Smart
button 'By WO' on SO form.
D11 Assemblies minimal model
fp.sale.assembly + fp.sale.assembly.line with name, ship_to, count,
procured_count, completed_at. UX (forms / kanbans / integration
into receiving) deferred — model only for now.
D14 Uploaded Files
Files smart button on SO form opens ir.attachment kanban filtered
to this SO. Count appears in the chip bar.
F4 Signed tracking
x_fc_signed_at / x_fc_signed_by / x_fc_is_signed on sale.order +
action_mark_signed helper. Signed column on quotes list view.
F10 New Quote
Kept on existing action_fp_quotations (already surfaces the
default New button).
E5/F9 Action icons per row
Deferred — requires a custom widget; the native PDF action via the
Print menu covers 80% of the use case.
Bumped to 19.0.8.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
F1 follow-up: x_fc_follow_up_date + x_fc_follow_up_user_id fields on
sale.order, surfaced in the quotations list + a 'Needs Follow-Up'
preset filter.
F2 expires: native validity_date exposed as togglable column on the
quotes list + an 'Expired' preset filter.
F3 email status pills: x_fc_email_status computed (draft / sent /
opened / won). 'Opened' detects via mail.notification.is_read on any
email-type mail.message attached to this SO.
F5 part numbers summary: x_fc_part_numbers_summary ("PN1, PN2 (+3
more)") across order_line parts, togglable column.
F7 from-RFQ filter reuses existing x_fc_rfq_attachment_id.
Views:
- view_sale_order_list_fp_quotes (new list dedicated to quotes).
- view_sale_order_search_fp_quotes with filters Draft / Sent / Won /
From RFQ / Needs Follow-Up / Expired + group-bys.
- action_fp_quotations rewired to both of the above.
Bumped to 19.0.7.2.0. Closes all six phases originally planned.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
E1/E2/E3/E4: list view gets new togglable columns for
- x_fc_wo_completion (e.g. '3/5'): count of completed vs total WOs
- x_fc_invoiced_amount (Monetary): sum of posted customer invoices
minus credit notes
- x_fc_margin_amount + x_fc_margin_percent: reuses Phase D8 computes
- x_fc_is_blanket_order toggle
New sale.order.search view (sale.order.search.fp) with preset
filters: My Orders / Open / Confirmed / Done / Blanket / Has Rush /
Overdue, plus group-bys for Customer / Status / Customer Deadline.
Bumped to 19.0.7.1.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
D3 first half: x_fc_workorder_count computes live count of active MRP
work orders linked to this SO (via mo.origin = so.name). Adds a
'Active WOs' smart button next to the existing PO / RFQ buttons on
the sale.order form. Clicking opens a filtered mrp.workorder list
grouped by MO.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>