From 498963e83a28ce1b45b35e99955a6db398896299 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Thu, 4 Jun 2026 21:24:08 -0400 Subject: [PATCH] fix(fusion_clock): pre-migrate re-links orphaned config-param external ids Settings saved via set_param() have no ir_model_data; the noupdate config XML then collides on UNIQUE(key) during -u. Pre-migrate links existing params to their XML external id (value-preserving) so upgrades are robust. Found on the Entech clone-verify; affects prod (35 params vs 32 xmlids). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../migrations/19.0.5.0.0/pre-migrate.py | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 fusion_clock/migrations/19.0.5.0.0/pre-migrate.py diff --git a/fusion_clock/migrations/19.0.5.0.0/pre-migrate.py b/fusion_clock/migrations/19.0.5.0.0/pre-migrate.py new file mode 100644 index 00000000..ca8a9901 --- /dev/null +++ b/fusion_clock/migrations/19.0.5.0.0/pre-migrate.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# +# Defensive pre-migration: re-link orphaned fusion_clock config-parameter +# external IDs. +# +# Booleans/floats saved through the Settings UI go in via set_param(), which +# creates the ir_config_parameter row WITHOUT an ir_model_data external id. If a +# param later also appears in the noupdate data/ir_config_parameter_data.xml, +# a plain `-u` can't match it by external id, treats it as new, and the INSERT +# trips the UNIQUE(key) constraint -> "Failed to load registry". +# +# This runs BEFORE the data files load: for every config record in the XML whose +# param already exists but whose external id is missing, we create the external +# id pointing at the existing param. The noupdate load then matches + skips it, +# so the existing (possibly customised) value is preserved. + +import logging +import os + +from lxml import etree + +from odoo.modules.module import get_module_path + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + module_path = get_module_path('fusion_clock') + if not module_path: + return + xml_path = os.path.join(module_path, 'data', 'ir_config_parameter_data.xml') + if not os.path.exists(xml_path): + return + + tree = etree.parse(xml_path) + fixed = 0 + for rec in tree.findall('.//record[@model="ir.config_parameter"]'): + xmlid = rec.get('id') + key_node = rec.find('./field[@name="key"]') + if not xmlid or key_node is None or not (key_node.text or '').strip(): + continue + key = key_node.text.strip() + + cr.execute("SELECT id FROM ir_config_parameter WHERE key = %s", (key,)) + param = cr.fetchone() + if not param: + continue # not set yet -> the noupdate load will create it cleanly + + cr.execute( + "SELECT id FROM ir_model_data WHERE module = 'fusion_clock' AND name = %s", + (xmlid,)) + if cr.fetchone(): + continue # already linked + + cr.execute(""" + INSERT INTO ir_model_data (module, name, model, res_id, noupdate, create_date, write_date) + VALUES ('fusion_clock', %s, 'ir.config_parameter', %s, true, now(), now()) + """, (xmlid, param[0])) + fixed += 1 + + if fixed: + _logger.info( + "Fusion Clock: re-linked %s orphaned config-parameter external id(s).", fixed)