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) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-04 21:24:08 -04:00
parent 0cb30f256d
commit 498963e83a

View File

@@ -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)