"""Reassign ir_model_data ownership from fusion_accounting to fusion_accounting_ai. Pre-Phase-0, all fusion code lived in module='fusion_accounting'. Post-Phase-0, fusion_accounting is the meta-module and the AI code lives in 'fusion_accounting_ai'. Odoo loads the Python from the new location, but existing ir_model_data rows still record the old module name. This script rewrites them. Special case: if the data-load phase of this very upgrade already created a new row in module='fusion_accounting_ai' with the same `name` as an old orphan (because the orphan lived under the old module name when data-load looked for it, missed it, and re-created the record), the UPDATE below would violate the unique constraint on (module, name). For those conflicts we delete the old orphan — the newly-created row is the one that records and the runtime will actually use going forward. Idempotent: running it a second time does nothing because the WHERE clauses find no matches. """ import logging _logger = logging.getLogger(__name__) # Exact xml-id names (model_ prefix, one per fusion.* model) that belonged to # the AI module. Each corresponds to a auto-created # by Odoo when the model class loads. AI_MODEL_PREFIXES = ( 'model_fusion_accounting_session', 'model_fusion_accounting_match_history', 'model_fusion_accounting_rule', 'model_fusion_accounting_tool', 'model_fusion_accounting_dashboard', 'model_fusion_accounting_recurring_pattern', 'model_fusion_accounting_vendor_tax_profile', 'model_fusion_accounting_rule_wizard', ) # XML-id name patterns for views/data/security/wizard/etc. that belong to # the AI sub-module. These cover every xml-id the AI module declares in its # data files (cron.xml, default_rules.xml, tool_definitions.xml, views/*.xml, # wizards/*.xml, report/*.xml) plus the ACL entries in ir.model.access.csv. # # Patterns use SQL LIKE syntax; '%' matches anything. These are broad on # purpose: we want to catch every past and present xml-id declared by the AI # data files, including Odoo-auto-generated companions (e.g. ir.cron auto- # creates an ir.actions.server with xml-id '_ir_actions_server'). AI_NAME_LIKE = ( 'view_fusion_%', 'action_fusion_%', 'menu_fusion_%', 'fusion_tool_%', 'fusion_rule_%', 'cron_fusion_%', 'seq_fusion_%', 'access_fusion_%', 'rule_fusion_%', 'paperformat_fusion_%', 'report_fusion_%', 'audit_report_template', ) def migrate(cr, version): # Step 1: Delete orphan rows that conflict with an already-existing row in # fusion_accounting_ai (data-load artifact). The new row is the survivor. cr.execute(""" DELETE FROM ir_model_data AS old WHERE old.module = 'fusion_accounting' AND (old.name = ANY(%s) OR old.name LIKE ANY(%s)) AND EXISTS ( SELECT 1 FROM ir_model_data AS new WHERE new.module = 'fusion_accounting_ai' AND new.name = old.name ) """, (list(AI_MODEL_PREFIXES), list(AI_NAME_LIKE))) deleted_conflicts = cr.rowcount # Step 2: Reassign the non-conflicting orphans. cr.execute(""" UPDATE ir_model_data SET module = 'fusion_accounting_ai' WHERE module = 'fusion_accounting' AND ( name = ANY(%s) OR name LIKE ANY(%s) ) """, (list(AI_MODEL_PREFIXES), list(AI_NAME_LIKE))) moved = cr.rowcount _logger.info( "fusion_accounting_ai post-migration: deleted %d conflicting orphans, " "reassigned %d ir_model_data rows from module='fusion_accounting' " "to module='fusion_accounting_ai'", deleted_conflicts, moved, )