# Copyright 2026 Criptomart # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging _logger = logging.getLogger(__name__) OLD_MODULE = "stock_picking_batch_custom" NEW_MODULE = "stock_picking_batch_collect" # Fields handed over to `website_sale_aplicoop`, which declares the very same # ones. Dropping just the xmlid is enough and is what we want: Odoo matches the # existing row again by (model, name) and re-registers it under aplicoop's # ownership later in the same run (aplicoop depends on us, so it loads after # us). Both are non-stored, so no column is touched. HANDED_OVER = ( "field_stock_move_line__home_delivery", "field_stock_move_line__consumer_group_id", ) # Artifacts that disappear for good, because aplicoop already stores the same # value as `stock.picking.consumer_group_id`. Here the record itself has to go # too, not only the xmlid: deleting an `ir_model_data` row does not cascade, so # the field would survive as an orphan manual field and the view as a custom # view still pointing at it. Mapped as table -> xmlids. DROPPED = { "ir_model_fields": ("field_stock_picking__batch_consumer_group_id",), "ir_ui_view": ("view_stock_picking_batch_picking_tree_consumer_group",), } def pre_init_hook(env): """Adopt the database records of the former ``stock_picking_batch_custom``. A renamed addon is a brand new module for Odoo, so a versioned ``migrations/`` script would never run. This hook does instead, on install, after the Python is imported but before ``registry.load()`` and ``init_models()``: remapping ``ir_model_data`` in that window makes Odoo reuse the existing tables and columns instead of creating new ones, so ``stock_move_line.is_collected``, the ``stock_picking_batch_summary_line`` table and the ``res_company.batch_*`` settings all survive untouched. A no-op on a database where the old module was never installed. """ cr = env.cr cr.execute("SELECT id FROM ir_module_module WHERE name = %s", (OLD_MODULE,)) old = cr.fetchone() if not old: return old_id = old[0] cr.execute("SELECT id FROM ir_module_module WHERE name = %s", (NEW_MODULE,)) new = cr.fetchone() if not new: # Should not happen: the module list is refreshed before installing. _logger.warning("%s: no module row to adopt %s into", NEW_MODULE, OLD_MODULE) return new_id = new[0] _logger.info( "Adopting %s (id=%s) as %s (id=%s)", OLD_MODULE, old_id, NEW_MODULE, new_id ) # 1. Hand every xmlid over to the new module name. cr.execute( "UPDATE ir_model_data SET module = %s WHERE module = %s", (NEW_MODULE, OLD_MODULE), ) _logger.info("Moved %s xmlids to %s", cr.rowcount, NEW_MODULE) # 2. Give back what is no longer ours, so aplicoop can claim it. cr.execute( "DELETE FROM ir_model_data WHERE module = %s AND name IN %s", (NEW_MODULE, HANDED_OVER), ) # 3. Drop for good what neither module defines any more, record included. for table, xmlids in DROPPED.items(): cr.execute( "DELETE FROM %s WHERE id IN (" " SELECT res_id FROM ir_model_data" " WHERE module = %%s AND name IN %%s" ")" % table, (NEW_MODULE, xmlids), ) if cr.rowcount: _logger.info("Dropped %s obsolete row(s) from %s", cr.rowcount, table) cr.execute( "DELETE FROM ir_model_data WHERE module = %s AND name IN %s", (NEW_MODULE, tuple(name for names in DROPPED.values() for name in names)), ) # 4. `ir_model_constraint.module` and `ir_model_relation.module` are integer # FKs to `ir_module_module` with ON DELETE CASCADE: they must be # repointed *before* the old row goes away, or the bookkeeping for our # SQL constraints is silently destroyed with it. cr.execute( "UPDATE ir_model_constraint SET module = %s WHERE module = %s", (new_id, old_id), ) cr.execute( "UPDATE ir_model_relation SET module = %s WHERE module = %s", (new_id, old_id), ) # 5. Drop the dependency rows, both ours and any pointing at the old name. cr.execute( "DELETE FROM ir_module_module_dependency WHERE module_id = %s OR name = %s", (old_id, OLD_MODULE), ) # 6. Finally the module row itself. We delete the *old* one and keep the # new one: the loader already holds the new row's id in memory, so # renaming the old row in place would leave Odoo writing to a record # that no longer exists. cr.execute("DELETE FROM ir_module_module WHERE id = %s", (old_id,))