Adds two Integer fields to res.partner:
- x_fc_default_lead_time_min_days
- x_fc_default_lead_time_max_days
Set once on the customer's Plating Defaults tab (Fulfilment group);
auto-copies onto every new Express Order via the existing
_onchange_partner_id hook. Operator can still override per-order
since the onchange only fills when the wizard field is still blank.
Field declaration lives in fusion_plating_configurator (alongside
the rest of the partner cascade reads). View edit lives in
fusion_plating_invoicing where the Plating Defaults tab already
hosts the other partner-level defaults (invoice strategy, deposit
%, delivery method, deadline-days). Invoicing depends on
configurator, so the fields are registered before the view loads.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
FpExpressActionBtns.onOpen called action_open_part which returned an
ir.actions.act_window dict without a 'views' key. Odoo 19's
_preprocessAction in the web client tries to .map over action.views
and throws TypeError: Cannot read properties of undefined (reading 'map').
Fix: include 'views': [[False, 'form']] alongside view_mode='form' on
both copies of action_open_part (wizard line + sale.order.line).
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
My .o_fp_xpr_cell rule set width/height: 18px on every input[type=
checkbox], which broke Bootstrap's .form-switch slider proportions
(switches need width: 2em / height: 1em). Result: PO Pending and
other boolean_toggle widgets rendered as a single grey circle with
no visible track.
Excluded .o_field_boolean_toggle from the checkbox override and added
explicit Bootstrap form-switch styling — width: 2em, height: 1.2em,
accent colour on checked state, accent-bg focus ring. Non-switch
checkboxes (Blanket SO, Block partial shipments etc.) keep the 18px
square treatment.
H1 — Recipe propagation hardening for multi-part orders. The G3 onchange
fires when material_process changes, but a newly-added line (especially
via inline part create) sometimes didn't pick up the recipe before
confirm. In action_create_order, just BEFORE building so_vals, force
line.process_variant_id = wizard.material_process if the line is missing
one. Also added the same fallback inside the so_vals dict so the SO line
always carries the right recipe even if the wizard line missed it.
H2 — Strip 'spec - PART Rev X (xN)' header from customer-facing
description. Per user feedback, the customer-facing reports (SO
confirmation, Invoice, CoC, packing slip, BoL) should show ONLY the
typed description + thickness in the Description column. The legacy
header that prepended part metadata to line.name duplicated info from
the Part Number column. Wizard now writes ONLY the customer description
to line.name; the Part Number column owns the part-rev-name display.
H3 — Uppercase customer-facing description in reports. The shared
customer_line_description macro now wraps the description, serial,
and thickness in text-transform: uppercase divs. All reports that use
the macro (SO confirmation, Invoice, CoC, packing slip, BoL) get the
caps treatment automatically. Non-part lines (freight, rush fees)
keep their natural casing.
Manually cleaned up DOD-00154/SO-30062:
- Backfilled line 682 with the header recipe (ENP ALUM BASIC HP)
- Stripped the legacy 'No spec - PART Rev (xN)' header from both
lines' names; descriptions now read 'THIS IS TEST SPECIFICATIONS...'
and 'THIS IS BLB ABLA BOLL' cleanly.
Three cascading bugs caused DOD-00153/WO-30061 to confirm with zero
steps (and DOD-00150 to keep masking/bake even with overrides):
1. _is_node_included() in fp_job._generate_steps_from_recipe consulted
the per-job override_map ONLY when node.opt_in_out was 'opt_in' or
'opt_out'. Default is 'disabled' (mandatory), so overrides on
mandatory recipe nodes (Masking, De-Masking, Oven baking) were
silently ignored. Fix: consult override_map FIRST — explicit per-job
override always wins, regardless of node's opt_in_out value.
2. fp.direct.order.line.recipe_choice_ids didn't include the wizard's
material_process recipe (Express Orders order-level recipe), so the
line's process_variant_id domain rejected propagation. Added a 4th
tier to the compute that pulls the order's header recipe in.
3. sale_order._fp_resolve_recipe_for_line fell back from line picker
to part.default_process_id with nothing between. Added Express
header recipe (self.x_fc_material_process) as a 2nd-priority
fallback — catches cases where G3 propagation failed to reach the
line but the SO header has the recipe set.
Also fixed an unrelated G4 bug: _FP_PART_SYNC_FIELDS mapped
process_variant_id → 'default_process_variant_id' which doesn't
exist. Real field is 'default_process_id' (singular).
Cleaned up DOD-00153/WO-30061 manually: backfilled line +
job.recipe_id, regenerated steps with overrides respected. 8 steps
now visible, masking/bake correctly omitted.
Root cause: spec + plan assumed fusion.plating.process.node.default_kind
values for masking/baking nodes were 'masking', 'de_masking', 'baking'.
Actual values per inspection of WO-30060 / recipe ENP-ALUM-BASIC:
- 'mask' (Masking step)
- 'demask' (De-Masking step)
- 'bake' (Oven baking / Oven bake post de-rack)
So _fp_apply_express_overrides_to_job was searching for nodes that
don't exist → no override rows created → step generation included
masking + bake even when the SO line had x_fc_masking_enabled=False
and x_fc_bake_instructions=empty.
Fixed all 4 occurrences in _fp_apply_express_overrides_to_job:
- pre-deletion search uses ('mask','demask','bake')
- masking opt-out walker calls ('mask','demask')
- bake opt-out walker calls ('bake',)
- bake step instructions filter uses default_kind == 'bake'
Manually cleaned up DOD-00150 / WO-30060:
- Deleted the 4 masking/bake steps that were wrongly created
- Created 4 override rows so any re-generation respects the opt-outs
Future orders with masking off / bake empty will correctly skip these
recipe nodes at step-generation time.
Regression from G2 conversion (Char → Many2One). The wizard's
action_create_order built so_vals with 'x_fc_material_process':
self.material_process (the recordset) instead of .id. Passing a
recordset where an integer FK is expected raised:
psycopg2.ProgrammingError: can't adapt type 'fusion.plating.process.node'
at sale.order create time, breaking Confirm Order.
Python-only fix — no module upgrade needed, systemctl restart picks
it up.
Root cause: my @api.depends_context('fp_express_part_picker') decorator
on _compute_display_name was not honored by Odoo. Verified via odoo
shell — display_name returns the full 'PART (Rev X) — Name' regardless
of context. Reason: display_name is defined on the base Model class
and Odoo registers the field metadata (incl. _depends_context) when
the field is FIRST declared. Subclass redefinitions of the compute
method don't update _depends_context after the fact.
Workaround: don't rely on display_name context override. Instead,
overlay a custom span on top of the Many2OneField that shows JUST
the part_number_display value. CSS overlay uses:
- position: absolute / inset: 0
- background: $xpr-card (matches list row background)
- z-index: 2 over the picker
- pointer-events: none so clicks pass through to the picker
When the picker is focused (:focus-within parent), the overlay
hides so the user sees the autocomplete input value as they type.
When not focused, the overlay covers display_name with just the
part number.
Row 1 now reads 'ENG-1042 / B' — picker on the left (showing only
part_number_display), separator, revision on the right. Matches the
mockup pixel layout the user requested.
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
A bad replacement in the previous commit left an extra '}' that
prematurely closed the .o_fp_xpr block, dumping all the legend bar /
PO status pill / part cell / bake pill styles OUTSIDE the namespace.
SCSS compile silently produced an unusable bundle and the form
rendered without any of the new visual treatment.
Brace balance now verified at 0.
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.
D1: action_open_draft method routes drafts-list click to the matching
form view by view_source. view_source badge column added to the drafts
list (Express=blue, Legacy=muted).
D2: Deprecation banner on the legacy direct-order form pointing operators
to the new Express view, plus a Switch-to-Express header button. Legacy
action context defaults new drafts to view_source='legacy' (Express
action defaults to 'express') so newly-created drafts open in the right
view automatically.
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 G of permissions overhaul.
G2: sale.order.action_confirm now requires group_fp_sales_manager
(spec Section 2.B). Sales Reps can save drafts but cannot move SOs
to 'sale' state. UserError raised with clear message if attempted.
G3: Fixed audit-finding-11 typo bug in 2 files. The original code
checked has_group('fusion_plating.group_fusion_plating_administrator'),
an xmlid that has NEVER existed - so the gate always returned False
and only the Manager-side check actually fired. Fixed both:
- fusion_plating_invoicing/models/res_partner.py:34
- fusion_plating_configurator/wizard/fp_direct_order_wizard.py:467
Both now check has_group('fusion_plating.group_fp_manager') which
transitively includes Owner via implied_ids.
G4: Swept all Python has_group() calls to reference new group xmlids.
Backward-compat keeps old refs working today (Phase A's implied_ids),
but the sweep ensures correctness after the 30-day rollback window
deletes old groups. Replacements:
group_fusion_plating_operator -> group_fp_technician
group_fusion_plating_supervisor -> group_fp_shop_manager_v2
group_fusion_plating_manager -> group_fp_manager
group_fusion_plating_admin -> group_fp_owner
group_fusion_plating_cgp_officer -> group_fp_quality_manager
group_fusion_plating_cgp_designated_official -> group_fp_owner
group_fp_estimator -> group_fp_sales_rep
group_fp_accounting -> group_fp_manager
group_fp_receiving -> group_fp_shop_manager_v2
group_fp_shop_manager (legacy) -> group_fp_manager
G1: test_sales_manager_gate.py covers the new confirm gate (SR
blocked, SMg allowed, Manager allowed via diamond implication).
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>
Phase B of permissions overhaul. Mechanical text replacement across
11 ir.model.access.csv files:
- group_fusion_plating_operator -> fusion_plating.group_fp_technician
- group_fusion_plating_supervisor -> fusion_plating.group_fp_shop_manager_v2
- group_fusion_plating_manager -> fusion_plating.group_fp_manager
- group_fusion_plating_admin -> fusion_plating.group_fp_owner
- group_fp_estimator (configurator)-> fusion_plating.group_fp_sales_rep
- group_fp_accounting -> fusion_plating.group_fp_manager
- group_fp_receiving -> fusion_plating.group_fp_shop_manager_v2
- group_fp_shop_manager (legacy) -> fusion_plating.group_fp_manager
- group_fusion_plating_cgp_officer -> fusion_plating.group_fp_quality_manager
- group_fusion_plating_cgp_designated_official -> fusion_plating.group_fp_owner
Backward-compat: old group xmlids still resolve (Phase A's implied_ids
chains keep old ACLs working for users still holding old groups).
This sweep ensures future-state correctness: when old groups are deleted
after the 30-day rollback window, ACLs continue resolving via the new
group xmlids.
Also adds fusion_plating/tests/test_acl_migration.py with sample-based
per-role access checks. The 2 CAPA tests are expected to fail until
Phase C implements the Manager/QM quality split.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit (a53b0326) added implied_ids in fp_security_v2.xml
that referenced 5 xmlids from downstream modules (configurator/receiving/
invoicing/cgp). Since fusion_plating is the BASE module and loads first
at fresh install, those refs raised External-ID-not-found at install.
Fix: relocate the 5 cross-module implications into each downstream module's
own security file via additive (4, ref()) writes to the core group's
implied_ids. Odoo's XML data loader treats these as additive updates so
they stack cleanly across install + -u cycles.
Also: drop redundant <data noupdate="0"> wrapper in fp_security_v2.xml
to match sibling fp_security.xml's bare <odoo> shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A of permissions overhaul (see docs/superpowers/specs/2026-05-23-*).
New groups (technician/sales_rep/shop_manager_v2/sales_manager/manager/
quality_manager/owner) defined in fp_security_v2.xml with implied_ids
chains that include old groups for backward-compat during 30-day rollback
window. Old groups display as [DEPRECATED] in user form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported 2026-05-20: the Template dropdown in the Part > Process
Composer's 'Add Variant from Template' row truncated long recipe
names to 4 characters ("Cher" instead of "Chemical Conversion …").
The hard-coded max-width: 280px was set before the curated template
catalog grew names like "Chemical Conversion — Iridite Type II Cl 3"
and "ENP-STEEL-BASIC — Standard Heavy Phos".
Fix: replace the rigid max-width with a flex sizing that gives the
dropdown room to grow:
- min-width: 360px (full common recipe name fits)
- flex: 1 1 360px (grows to fill available space)
- max-width: 560px (cap so it doesn't push the buttons off-screen)
Same flex pattern applied to the Variant label input (slightly
narrower min/max).
Also: pulled the entech-side version of fp_part_process_composer.xml
back into the local repo — local was stale (one 'Add Variant' button;
entech had the dual 'Add — Tree' / 'Add — Simple' buttons that
landed in an out-of-band edit).
Module: fusion_plating_configurator 19.0.21.5.0 → 19.0.21.5.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>