# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # # Phase 1 multi-serial — backfill the new M2M relations from the # pre-existing single-M2O column on sale.order.line and account.move.line. # # x_fc_serial_id was historically a stored Many2one. Phase 1 made it a # computed alias of `x_fc_serial_ids` (the new M2M). Existing rows have # the old FK column populated but no rows in the M2M relation table. # This migration walks the legacy column and inserts one M2M row per # (line, serial) pair so smart buttons / reverse links continue to find # the linked records. import logging _logger = logging.getLogger(__name__) def migrate(cr, version): """Backfill fp_sale_order_line_serial_rel + fp_account_move_line_serial_rel.""" backfill_table(cr, 'sale_order_line', 'fp_sale_order_line_serial_rel', 'line_id') backfill_table(cr, 'account_move_line', 'fp_account_move_line_serial_rel', 'line_id') def backfill_table(cr, source_table, m2m_table, line_col): cr.execute( "SELECT 1 FROM information_schema.columns " "WHERE table_name = %s AND column_name = 'x_fc_serial_id'", (source_table,), ) if not cr.fetchone(): _logger.info("Phase 1 multi-serial: %s has no x_fc_serial_id column, skip", source_table) return # Make sure the M2M table exists (Odoo creates it on registry load, # but the migration runs BEFORE the registry comes up on upgrade — # use IF NOT EXISTS to be safe). cr.execute( f""" CREATE TABLE IF NOT EXISTS "{m2m_table}" ( "{line_col}" integer NOT NULL REFERENCES "{source_table}"(id) ON DELETE CASCADE, "serial_id" integer NOT NULL REFERENCES "fp_serial"(id) ON DELETE CASCADE, PRIMARY KEY ("{line_col}", "serial_id") ) """ ) cr.execute( f""" INSERT INTO "{m2m_table}" ("{line_col}", "serial_id") SELECT id, x_fc_serial_id FROM "{source_table}" WHERE x_fc_serial_id IS NOT NULL ON CONFLICT DO NOTHING """ ) _logger.info( "Phase 1 multi-serial: backfilled %s rows from %s.x_fc_serial_id into %s", cr.rowcount, source_table, m2m_table, )