fix(plating-sec): move cross-module implied_ids out of fp_security_v2.xml

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>
This commit is contained in:
gsinghpal
2026-05-24 00:59:20 -04:00
parent a53b03265d
commit 6c7e11db4d
10 changed files with 108 additions and 79 deletions

View File

@@ -208,6 +208,14 @@ Use only: `name`, `model_id`, `state`, `code` (or `function`/`model`), `interval
11. **XML data ordering**: Window actions must be defined BEFORE `<menuitem>` elements that reference them in the same file.
12. **Module install on new modules**: Use `--update=base` alongside `-i MODULE` to ensure Odoo rescans the addons path and finds the new module directory.
13. **Implied group cascade**: `implied_ids` on `res.groups` does NOT reliably propagate to users on module install. Always include `user_ids` to explicitly assign admin, or fix via SQL post-install.
13a. **Cross-module xmlid refs — base modules CANNOT forward-ref downstream xmlids**: A BASE module's data XML cannot `ref('downstream_module.some_xmlid')` because at fresh install, the base module loads FIRST and `ir.model.data` has no row for the downstream xmlid yet → `ValueError: External ID not found`. This bites on entech (existing DB has the row) but breaks fresh CI/test/demo/new-client installs. **Fix pattern: relocate the cross-module link to the downstream module's own security/data file, using an additive write to the BASE module's record:**
```xml
<!-- In downstream module's security XML -->
<record id="fusion_plating.group_fp_sales_rep" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_configurator.group_fp_estimator'))]"/>
</record>
```
Odoo's XML loader treats `id="other_module.xmlid"` as an additive update to the existing record, and `(4, ref(...))` (Command.link) stacks idempotently across install/-u cycles. Use this whenever a base module group/record needs to imply or reference something defined in a downstream module. Caught 2026-05-24 when `fusion_plating/security/fp_security_v2.xml` referenced groups from configurator/receiving/invoicing/cgp — worked on entech, would have broken fresh installs.
14a. **FP report palette + border rendering**: `fusion_plating_reports/report/report_base_styles.xml` uses **`#c1c1c1`** for section-header backgrounds and **`#1d1f1e`** (th text on grey) / **`#4e4e4e`** (h2/h4 on white) — NOT `res.company.primary_color`. Per-customer request (2026-05-17) the FP reports stopped following the company brand colour so every shop gets the same neutral look. The `fp_primary` template variable is still computed in the styles block so per-report templates can opt back in if needed, but the default `.fp-report` / `.fp-landscape` rules use the hardcoded greys. **Don't "fix" this back to `fp_primary` without confirming.**
**Border-rendering gotcha** (entech wkhtmltopdf): with the standard `border-collapse: collapse` + `border: 1px solid #000` pattern, vertical borders can render slightly softer than horizontal borders because of how wkhtmltopdf rounds sub-pixels in its collapse-adjudication. Cells with a `background-color` also paint over the border edge unless clipped. Mitigations in place:

View File

@@ -1,85 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- Phase 1 Permissions Overhaul: 8 consolidated roles -->
<!-- See docs/superpowers/specs/2026-05-23-permissions-overhaul-design.md -->
<!-- Phase 1 Permissions Overhaul: 8 consolidated roles -->
<!-- See docs/superpowers/specs/2026-05-23-permissions-overhaul-design.md -->
<!-- Cross-module implications (estimator, receiving, accounting, cgp_officer,
cgp_designated_official) live in the downstream modules' security files
to avoid fresh-install forward-ref errors. -->
<record id="group_fp_technician" model="res.groups">
<field name="name">Technician</field>
<field name="sequence">10</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('base.group_user')),
(4, ref('fusion_plating.group_fusion_plating_operator')),
]"/>
</record>
<record id="group_fp_technician" model="res.groups">
<field name="name">Technician</field>
<field name="sequence">10</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('base.group_user')),
(4, ref('fusion_plating.group_fusion_plating_operator')),
]"/>
</record>
<record id="group_fp_sales_rep" model="res.groups">
<field name="name">Sales Representative</field>
<field name="sequence">20</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('base.group_user')),
(4, ref('fusion_plating_configurator.group_fp_estimator')),
]"/>
</record>
<record id="group_fp_sales_rep" model="res.groups">
<field name="name">Sales Representative</field>
<field name="sequence">20</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('base.group_user')),
]"/>
</record>
<record id="group_fp_shop_manager_v2" model="res.groups">
<field name="name">Shop Manager</field>
<field name="sequence">30</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_technician')),
(4, ref('fusion_plating.group_fusion_plating_supervisor')),
(4, ref('fusion_plating_receiving.group_fp_receiving')),
]"/>
</record>
<record id="group_fp_shop_manager_v2" model="res.groups">
<field name="name">Shop Manager</field>
<field name="sequence">30</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_technician')),
(4, ref('fusion_plating.group_fusion_plating_supervisor')),
]"/>
</record>
<record id="group_fp_sales_manager" model="res.groups">
<field name="name">Sales Manager</field>
<field name="sequence">40</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_sales_rep')),
]"/>
</record>
<record id="group_fp_sales_manager" model="res.groups">
<field name="name">Sales Manager</field>
<field name="sequence">40</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_sales_rep')),
]"/>
</record>
<record id="group_fp_manager" model="res.groups">
<field name="name">Manager</field>
<field name="sequence">50</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_shop_manager_v2')),
(4, ref('group_fp_sales_manager')),
(4, ref('fusion_plating.group_fusion_plating_manager')),
(4, ref('fusion_plating_invoicing.group_fp_accounting')),
]"/>
</record>
<record id="group_fp_manager" model="res.groups">
<field name="name">Manager</field>
<field name="sequence">50</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_shop_manager_v2')),
(4, ref('group_fp_sales_manager')),
(4, ref('fusion_plating.group_fusion_plating_manager')),
]"/>
</record>
<record id="group_fp_quality_manager" model="res.groups">
<field name="name">Quality Manager</field>
<field name="sequence">60</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_manager')),
(4, ref('fusion_plating_cgp.group_fusion_plating_cgp_officer')),
]"/>
</record>
<record id="group_fp_quality_manager" model="res.groups">
<field name="name">Quality Manager</field>
<field name="sequence">60</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_manager')),
]"/>
</record>
<record id="group_fp_owner" model="res.groups">
<field name="name">Owner</field>
<field name="sequence">70</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_quality_manager')),
(4, ref('fusion_plating.group_fusion_plating_admin')),
(4, ref('fusion_plating_cgp.group_fusion_plating_cgp_designated_official')),
(4, ref('base.group_system')),
]"/>
<field name="user_ids" eval="[
(4, ref('base.user_root')),
(4, ref('base.user_admin')),
]"/>
</record>
</data>
<record id="group_fp_owner" model="res.groups">
<field name="name">Owner</field>
<field name="sequence">70</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_quality_manager')),
(4, ref('fusion_plating.group_fusion_plating_admin')),
(4, ref('base.group_system')),
]"/>
<field name="user_ids" eval="[
(4, ref('base.user_root')),
(4, ref('base.user_admin')),
]"/>
</record>
</odoo>

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Controlled Goods Program',
'version': '19.0.1.1.0',
'version': '19.0.1.2.0',
'category': 'Manufacturing/Plating',
'summary': 'Canadian Controlled Goods Program (CGP) compliance for plating '
'shops handling defence work: registration, authorized individuals, '

View File

@@ -35,6 +35,15 @@
eval="[(4, ref('group_fusion_plating_cgp_officer'))]"/>
</record>
<!-- Backward-compat: new Quality Manager implies old CGP Officer; new Owner implies old CGP DO. -->
<record id="fusion_plating.group_fp_quality_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_cgp.group_fusion_plating_cgp_officer'))]"/>
</record>
<record id="fusion_plating.group_fp_owner" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_cgp.group_fusion_plating_cgp_designated_official'))]"/>
</record>
<!-- ================================================================== -->
<!-- RECORD RULES -->
<!-- -->

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Configurator',
'version': '19.0.21.7.2',
'version': '19.0.21.8.0',
'category': 'Manufacturing/Plating',
'summary': 'Quotation configurator with part catalog, coating configs, and formula-based pricing engine.',
'description': """

View File

@@ -24,4 +24,10 @@
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<!-- Backward-compat: new Sales Rep role implies old Estimator group so existing ACLs still resolve.
Lives here (not in fusion_plating core) to avoid fresh-install forward-ref. -->
<record id="fusion_plating.group_fp_sales_rep" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_configurator.group_fp_estimator'))]"/>
</record>
</odoo>

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Invoicing',
'version': '19.0.3.5.0',
'version': '19.0.3.6.0',
'category': 'Manufacturing/Plating',
'summary': 'Invoice strategy engine with deposit, progress billing, net terms, COD/prepay, and account holds.',
'description': """

View File

@@ -14,4 +14,9 @@
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<!-- Backward-compat: new Manager role implies old Accounting group. -->
<record id="fusion_plating.group_fp_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_invoicing.group_fp_accounting'))]"/>
</record>
</odoo>

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Receiving & Inspection',
'version': '19.0.3.27.0',
'version': '19.0.3.28.0',
'category': 'Manufacturing/Plating',
'summary': 'Parts receiving, inspection, damage logging, and manufacturing gate.',
'description': """

View File

@@ -14,4 +14,9 @@
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<!-- Backward-compat: new Shop Manager v2 role implies old Receiving group. -->
<record id="fusion_plating.group_fp_shop_manager_v2" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_receiving.group_fp_receiving'))]"/>
</record>
</odoo>