Tower: upload laundry_management 19.0.19.0.4 (via marketplace)

This commit is contained in:
2026-05-01 15:01:01 +00:00
parent f77ab77c0b
commit 752c1ebe59

View File

@@ -0,0 +1,228 @@
"""
Pre-migration script for laundry_management 19.0.11.0.0
Architecture change: standalone laundry.order / laundry.order.line / laundry.payment
models are replaced by _inherit = 'sale.order' / 'sale.order.line'.
This script runs BEFORE Odoo's model sync so that:
1. FK constraints from old wizard tables referencing laundry_order are dropped
2. Old wizard transient records are purged (they reference non-existent rows)
3. Stale ir.model.fields records pointing to old models are removed
4. Old ir.model entries are deleted (unblocking the ORM delete check)
5. Old physical tables are dropped
Without this, the ORM raises:
"Another model is using the record you are trying to delete.
Blocking model: Laundry Print Wizard (laundry.print.wizard),
Blocking field: order_id"
"""
import logging
_logger = logging.getLogger(__name__)
# Old standalone models being removed in this version
_OLD_MODELS = [
'laundry.order',
'laundry.order.line',
'laundry.payment',
'laundry.order.line.addon',
'laundry.register.payment.wizard',
'laundry.product.wizard',
'laundry.product.wizard.line',
'laundry.category',
'laundry.item.type',
]
# Physical tables that correspond to the old models
_OLD_TABLES = [
'laundry_order',
'laundry_order_line',
'laundry_payment',
'laundry_order_line_addon',
'laundry_register_payment_wizard',
'laundry_product_wizard',
'laundry_product_wizard_line',
'laundry_category',
'laundry_item_type',
]
# Transient/wizard tables that may hold rows with FK refs to laundry_order
_WIZARD_TABLES = [
'laundry_print_wizard',
'laundry_session_wizard',
'laundry_whatsapp_wizard',
'laundry_register_payment_wizard',
'laundry_product_wizard',
'laundry_product_wizard_line',
]
def _table_exists(cr, table):
cr.execute(
"SELECT 1 FROM information_schema.tables "
"WHERE table_schema = 'public' AND table_name = %s",
(table,),
)
return bool(cr.fetchone())
def _column_exists(cr, table, column):
cr.execute(
"SELECT 1 FROM information_schema.columns "
"WHERE table_schema = 'public' AND table_name = %s AND column_name = %s",
(table, column),
)
return bool(cr.fetchone())
def _drop_fk_constraints_referencing(cr, referenced_table):
"""Drop all FK constraints in the DB that point at referenced_table."""
cr.execute(
"""
SELECT tc.table_name, tc.constraint_name
FROM information_schema.table_constraints tc
JOIN information_schema.referential_constraints rc
ON tc.constraint_name = rc.constraint_name
AND tc.table_schema = rc.constraint_schema
JOIN information_schema.table_constraints tc2
ON rc.unique_constraint_name = tc2.constraint_name
AND rc.unique_constraint_schema = tc2.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc2.table_name = %s
AND tc.table_schema = 'public'
""",
(referenced_table,),
)
rows = cr.fetchall()
for src_table, constraint_name in rows:
_logger.info(
'pre_migrate: dropping FK constraint %s on %s (referenced %s)',
constraint_name, src_table, referenced_table,
)
cr.execute(
'ALTER TABLE "%s" DROP CONSTRAINT IF EXISTS "%s"' %
(src_table, constraint_name)
)
def migrate(cr, version):
if not version:
# Fresh install — nothing to clean up
return
_logger.info('pre_migrate laundry_management %s → 19.0.11.0.0 : start', version)
# ── Step 1: Purge all transient wizard records ────────────────────────────
# TransientModel rows expire naturally but DB rows persist during upgrade;
# they hold FK refs that block constraint drops and table drops.
for tbl in _WIZARD_TABLES:
if _table_exists(cr, tbl):
_logger.info('pre_migrate: truncating wizard table %s', tbl)
cr.execute('TRUNCATE TABLE "%s" CASCADE' % tbl)
# ── Step 2: Drop FK constraints pointing at old tables ────────────────────
for tbl in _OLD_TABLES:
if _table_exists(cr, tbl):
_drop_fk_constraints_referencing(cr, tbl)
# Also drop FKs on wizard tables that point at laundry_order (the primary blocker)
# Example: laundry_print_wizard.order_id -> laundry_order.id
for wizard_tbl in _WIZARD_TABLES:
if not _table_exists(cr, wizard_tbl):
continue
# Find and drop any FK on this wizard table that references an old table
cr.execute(
"""
SELECT kcu.column_name, ccu.table_name AS foreign_table_name, tc.constraint_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'public'
AND tc.table_name = %s
AND ccu.table_name = ANY(%s)
""",
(wizard_tbl, _OLD_TABLES),
)
for col, ref_tbl, constraint_name in cr.fetchall():
_logger.info(
'pre_migrate: dropping FK %s on %s.%s -> %s',
constraint_name, wizard_tbl, col, ref_tbl,
)
cr.execute(
'ALTER TABLE "%s" DROP CONSTRAINT IF EXISTS "%s"' %
(wizard_tbl, constraint_name)
)
# ── Step 3: Remove stale ir.model.fields that ref old models ─────────────
# These are the records that cause "Blocking model: laundry.print.wizard,
# Blocking field: order_id" — the field record itself still has
# relation = 'laundry.order', which makes the ORM think laundry.print.wizard
# still depends on laundry.order.
cr.execute(
"""
DELETE FROM ir_model_fields
WHERE relation = ANY(%s)
""",
(_OLD_MODELS,),
)
deleted = cr.rowcount
_logger.info('pre_migrate: deleted %d stale ir.model.fields rows', deleted)
# Also remove fields whose model itself is one of the old models
cr.execute(
"""
DELETE FROM ir_model_fields
WHERE model IN %s
""",
(tuple(_OLD_MODELS),),
)
_logger.info('pre_migrate: deleted %d ir.model.fields for old models', cr.rowcount)
# ── Step 4: Remove stale ir.model.fields_by_name cache ───────────────────
# In some Odoo versions there is a separate constraint/index table.
# Safe to attempt; ignore if table doesn't exist.
if _table_exists(cr, 'ir_model_constraint'):
cr.execute(
"""
DELETE FROM ir_model_constraint imc
USING ir_model im
WHERE imc.model = im.id
AND im.model = ANY(%s)
""",
(_OLD_MODELS,),
)
_logger.info('pre_migrate: removed %d ir.model.constraint rows', cr.rowcount)
if _table_exists(cr, 'ir_model_relation'):
cr.execute(
"""
DELETE FROM ir_model_relation imr
USING ir_model im
WHERE imr.model = im.id
AND im.model = ANY(%s)
""",
(_OLD_MODELS,),
)
_logger.info('pre_migrate: removed %d ir.model.relation rows', cr.rowcount)
# ── Step 5: Remove ir.model entries for the old models ───────────────────
cr.execute(
"DELETE FROM ir_model WHERE model = ANY(%s)",
(_OLD_MODELS,),
)
_logger.info('pre_migrate: deleted %d ir.model rows', cr.rowcount)
# ── Step 6: Drop old physical tables ─────────────────────────────────────
for tbl in reversed(_OLD_TABLES): # reverse to respect FK order
if _table_exists(cr, tbl):
_logger.info('pre_migrate: dropping table %s', tbl)
cr.execute('DROP TABLE IF EXISTS "%s" CASCADE' % tbl)
_logger.info('pre_migrate laundry_management: complete')