fix(plating-perms): deploy-time cascade fixes from entech I3
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>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Phase A bootstrap: rename old configurator's Shop Manager before new
|
||||
core group_fp_shop_manager_v2 tries to claim the 'Shop Manager' display name.
|
||||
|
||||
Load order:
|
||||
1. fusion_plating loads -> fp_security.xml renames its own old groups (Operator,
|
||||
Supervisor, Manager, Administrator) to '[DEPRECATED] X'. Then fp_security_v2.xml
|
||||
creates new groups (Technician, ..., Shop Manager v2 with display name 'Shop Manager').
|
||||
2. fusion_plating_configurator loads later -> would rename its own
|
||||
group_fp_shop_manager to '[DEPRECATED] Shop Manager'.
|
||||
|
||||
But step 1 crashes because the OLD configurator's group is still named just
|
||||
'Shop Manager' in the DB (the rename in step 2 hasn't run yet), and the unique
|
||||
constraint res_groups_name_uniq blocks the new 'Shop Manager'.
|
||||
|
||||
This pre-migrate script runs BEFORE any of fusion_plating's data files reload,
|
||||
patching the old configurator row's display name via SQL. After that, the
|
||||
constraint is clear and fp_security_v2.xml can create its new groups safely.
|
||||
The configurator's later -u will then push the canonical '[DEPRECATED] Shop
|
||||
Manager' display name from its XML data.
|
||||
"""
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
# Find old configurator Shop Manager row via ir.model.data and rename
|
||||
# its display name to avoid the constraint collision.
|
||||
cr.execute("""
|
||||
UPDATE res_groups
|
||||
SET name = jsonb_build_object('en_US', '[DEPRECATED] Shop Manager (Mgr+Estimator bundle)')
|
||||
WHERE id IN (
|
||||
SELECT res_id FROM ir_model_data
|
||||
WHERE module = 'fusion_plating_configurator'
|
||||
AND name = 'group_fp_shop_manager'
|
||||
AND model = 'res.groups'
|
||||
)
|
||||
AND (name IS NULL OR name->>'en_US' NOT LIKE '[DEPRECATED]%');
|
||||
""")
|
||||
rows = cr.rowcount
|
||||
if rows:
|
||||
_logger.info(
|
||||
'Fusion Plating: pre-migrate renamed %d old configurator Shop Manager '
|
||||
'row(s) to clear name collision with new group_fp_shop_manager_v2',
|
||||
rows,
|
||||
)
|
||||
@@ -101,7 +101,7 @@ class FpMigrationPreview(models.Model):
|
||||
owner_grp = self.env.ref('fusion_plating.group_fp_owner', raise_if_not_found=False)
|
||||
if not owner_grp:
|
||||
return
|
||||
owners = owner_grp.users.filtered(lambda u: u.active and not u.share)
|
||||
owners = owner_grp.user_ids.filtered(lambda u: u.active and not u.share)
|
||||
if not owners:
|
||||
_logger.warning('Fusion Plating migration preview %s: no Owner users to notify', self.id)
|
||||
return
|
||||
@@ -228,7 +228,7 @@ class FpMigrationPreview(models.Model):
|
||||
safe_to_unlink = []
|
||||
skipped = []
|
||||
for old_group in self.env['res.groups'].browse(old_group_ids).exists():
|
||||
active_users = old_group.users.filtered(lambda u: u.active and not u.share)
|
||||
active_users = old_group.user_ids.filtered(lambda u: u.active and not u.share)
|
||||
if active_users:
|
||||
skipped.append((old_group.name, active_users.mapped('login')))
|
||||
else:
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
<page string="Plating Designated Officials"
|
||||
groups="fusion_plating.group_fp_owner">
|
||||
<group>
|
||||
<field name="x_fc_cgp_designated_official_id"
|
||||
domain="[('all_group_ids', 'in', [ref('fusion_plating.group_fp_quality_manager'), ref('fusion_plating.group_fp_owner')])]"/>
|
||||
<field name="x_fc_nadcap_authority_user_id"
|
||||
domain="[('all_group_ids', 'in', [ref('fusion_plating.group_fp_quality_manager'), ref('fusion_plating.group_fp_owner')])]"/>
|
||||
<!-- No domain on the picker: Owner picks freely.
|
||||
ref() in XML domains trips Odoo 19's view validator
|
||||
(interpreted as field access on res.company).
|
||||
The QM/Owner eligibility constraint is enforced
|
||||
in Python via @api.constrains on res.company. -->
|
||||
<field name="x_fc_cgp_designated_official_id"/>
|
||||
<field name="x_fc_nadcap_authority_user_id"/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
Reference in New Issue
Block a user