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>
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>
Phase E removed the coating-rollup loop but left a stale `has_cost_data`
reference in the percent computation. NameError on every SO list /
form load.
Margin is "not available" until recipe-level cost data exists
(backlog item). Set all three margin fields to 0 / False explicitly
so no stale references remain.
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>
_clone_subtree() in fp_part_composer_controller built node vals
manually and never copied source.input_ids — so 'Load Template'
copied the recipe tree structure but dropped every custom prompt,
leaving operators with empty data-capture screens. The fix iterates
input_ids and calls .copy({'node_id': new_node.id}) so kind,
target_min/max/unit, compliance_tag, hint, selection_options,
sequence — every field on the input model — flows through.
Verified on entech: ENP-ALUM-BASIC clone now shows all 105 prompts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two compounding issues introduced by the persistence audit work:
1. fields.Text mismatch: sale.order.x_fc_internal_note and
x_fc_external_note are actually fields.Html. I declared the
sale.order.line related mirrors and the fp.job stored copies as
fields.Text, so setup_related raised:
TypeError: Type of related field
sale.order.line.x_fc_internal_note is inconsistent with
sale.order.x_fc_internal_note
Fixed by switching both Note fields on fp.job and sale.order.line
to fields.Html.
2. Module-load-order: Tier 3 fields (x_fc_delivery_method,
x_fc_ship_via, x_fc_invoice_strategy) are defined in
fusion_plating_jobs (related to sale.order via _inherit), but I
referenced them in fusion_plating core's fp_job_views.xml — which
loads BEFORE fusion_plating_jobs registers the fields. View
validator raised "Field x_fc_delivery_method does not exist".
Fixed by removing those 3 fields from the core view group and
adding them via xpath in fusion_plating_jobs's fp_job_form_inherit
(which loads after the fields are registered).
Both fixes deployed and verified — registry loads in 2s, all field
types match, related path resolves correctly. No data loss; the
fp.job rows that already had stored Text content for internal_note /
external_note will carry over into the Html field intact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tier 3 of the SO->fp.job persistence audit. Three logistics/billing
fields surface on fp.job as related read-only (not stored) mirrors:
- x_fc_delivery_method - Local Delivery / Shipping Partner / Customer
Pickup. Cargo classification used by logistics planning.
- x_fc_ship_via - Carrier name (UPS, FedEx, customer pickup, etc.).
- x_fc_invoice_strategy - Deposit / Progress / Net Terms / COD-Prepay.
Read by the invoicing module's hooks; mirroring on the WO is for
manager visibility only.
These were intentionally chosen as related (not stored persisted)
because the SO is the authoritative source - the existing downstream
code (delivery + invoicing modules) already reads them off SO directly.
A stored copy would risk drift. Related auto-follows SO updates.
Same three fields also mirrored on sale.order.line as stored related
for per-line list visibility.
Closes the SO->fp.job persistence audit. All 10 operational fields
identified now flow through to the WO (7 stored + populated at confirm,
3 related read-only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>