From 512467788b12d66a5eba4a4fac231c1141e135f1 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 00:29:33 -0400 Subject: [PATCH] fix(fusion_accounting_core): add pre-migration for security group rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 16's security group rehoming (fusion_accounting → fusion_accounting_core) only existed in post-migration. That flow fails on fresh pre-Phase-0 upgrades: data-load runs before post-migration and looks up group xml-ids by (module, name); if the row still has module='fusion_accounting', Odoo creates a duplicate res.groups record under module='fusion_accounting_core'. The subsequent post-migration UPDATE...SET module='fusion_accounting_core' then trips the (module, name) unique constraint on ir_model_data, rolling back the whole transaction. Pre-migration runs BEFORE data-load, renames the five security xml-ids (module_category, privilege, three groups) to the new module, so data-load finds the existing rows and UPDATEs them in place. Existing user-group links via res_groups_users_rel are preserved. The post-migration is kept as an idempotent safety net (docstring updated to reflect the new division of labour). Verified on westin-v19 by simulating the pre-Phase-0 state (UPDATE ir_model_data SET module='fusion_accounting' ...) and re-running the upgrade: 5 rows renamed cleanly, zero duplicates, no errors. Made-with: Cursor --- .../migrations/19.0.1.0.0/post-migration.py | 29 +++++---- .../migrations/19.0.1.0.0/pre-migration.py | 62 +++++++++++++++++++ 2 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py diff --git a/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py b/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py index 5564954a..9240b7c0 100644 --- a/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py +++ b/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py @@ -1,17 +1,15 @@ -"""Reassign security group/category/privilege xml-ids from the old module name. +"""Safety-net reassignment of security xml-ids to fusion_accounting_core. -Pre-Phase-0, the three fusion security groups (user, manager, admin), the -module category and the privilege all lived in module='fusion_accounting'. -Post-Phase-0 (Task 16) they moved into module='fusion_accounting_core'. +The actual rename lives in pre-migration.py — it MUST run before data-load +to avoid creating duplicate res.groups records and hitting the (module, +name) unique constraint on ir_model_data. This post-migration is a +belt-and-suspenders no-op for the common case: if pre-migration already +ran, this UPDATE matches zero rows. -Odoo loads the XML from the new location on upgrade, but the existing -ir_model_data rows still reference the old module. This script rewrites them. - -Both fusion_accounting_core and fusion_accounting_ai ship an equivalent -UPDATE — whichever post-migration runs first wins the rehoming, the other -is a no-op. This redundancy protects the common case where the two modules -are upgraded in either order (as well as the case where only one is -installed in a given database). +It also catches a rare edge case: fusion_accounting_ai.post-migration.py +runs an identical UPDATE to cover cross-module upgrade ordering, so both +modules redundantly ensure the rows land in the right module regardless +of which upgrade runs first. Idempotent: running it a second time matches zero rows. """ @@ -21,7 +19,7 @@ import logging _logger = logging.getLogger(__name__) -CORE_SECURITY_NAMES = ( +SECURITY_NAMES = ( 'module_category_fusion_accounting', 'res_groups_privilege_fusion_accounting', 'group_fusion_accounting_user', @@ -38,11 +36,12 @@ def migrate(cr, version): WHERE module = 'fusion_accounting' AND name = ANY(%s) """, - (list(CORE_SECURITY_NAMES),), + (list(SECURITY_NAMES),), ) moved = cr.rowcount _logger.info( "fusion_accounting_core post-migration: reassigned %d security rows " - "from module='fusion_accounting' to module='fusion_accounting_core'", + "from module='fusion_accounting' to module='fusion_accounting_core' " + "(usually zero; pre-migration already handled the rename)", moved, ) diff --git a/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py b/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py new file mode 100644 index 00000000..9970b7ae --- /dev/null +++ b/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py @@ -0,0 +1,62 @@ +"""Rehome the fusion security xml-ids to fusion_accounting_core BEFORE data-load. + +Pre-Phase-0, the three fusion security groups (user, manager, admin), the +module category and the privilege all lived in module='fusion_accounting'. +Post-Phase-0 (Task 16) they moved into module='fusion_accounting_core'. + +Running this rename in pre-migration (rather than post-migration) is +essential: Odoo's XML data-load looks up records by (module, name) in +ir_model_data. If the old row still has module='fusion_accounting' when +data-load runs, Odoo will not find a match for +'fusion_accounting_core.group_fusion_accounting_user' and will CREATE a +brand-new res.groups record plus a new ir_model_data row. That leaves the +database with two groups per name: + +1. The ORIGINAL group (still tagged module='fusion_accounting') that every + existing user is linked to via res_groups_users_rel. +2. A FRESH empty group (newly tagged module='fusion_accounting_core'). + +The subsequent post-migration UPDATE...SET module='fusion_accounting_core' +would then violate the (module, name) unique constraint on ir_model_data +and the upgrade transaction would roll back. + +By renaming ir_model_data rows BEFORE data-load, Odoo finds the existing +row (now tagged fusion_accounting_core.*), UPDATEs the res.groups record +in place with the XML-defined values, and the user-group links are +preserved untouched. + +Idempotent: running this a second time matches zero rows. +""" + +import logging + +_logger = logging.getLogger(__name__) + + +SECURITY_NAMES = ( + 'module_category_fusion_accounting', + 'res_groups_privilege_fusion_accounting', + 'group_fusion_accounting_user', + 'group_fusion_accounting_manager', + 'group_fusion_accounting_admin', +) + + +def migrate(cr, version): + cr.execute( + """ + UPDATE ir_model_data + SET module = 'fusion_accounting_core' + WHERE module = 'fusion_accounting' + AND name = ANY(%s) + """, + (list(SECURITY_NAMES),), + ) + moved = cr.rowcount + _logger.info( + "fusion_accounting_core pre-migration: renamed %d security rows " + "from module='fusion_accounting' to module='fusion_accounting_core' " + "before data-load (idempotent; non-zero only on first upgrade from " + "pre-Phase-0)", + moved, + )