Tower: unpublish laundry_management — remove source from 19.0 branch

This commit is contained in:
2026-05-02 11:15:37 +00:00
parent 22162f9d47
commit 26ae0e14df
230 changed files with 0 additions and 17001 deletions

View File

@@ -1,228 +0,0 @@
"""
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')

View File

@@ -1,48 +0,0 @@
"""
Pre-migration for laundry_management 19.0.12.0.0
Changes handled:
1. Commission states: 'paid' only → now 'pending', 'confirmed', 'paid'
Existing 'paid' rows remain 'paid'. Existing 'pending' rows remain 'pending'.
'confirmed' is a new state — no existing rows use it, so no data migration needed.
2. New model: laundry.payment.wizard — just a new table, nothing to clean.
3. New groups: group_laundry_operator, group_laundry_cashier added to hierarchy.
Existing users with group_laundry_user keep all their permissions (implied).
4. Access control: new CSV entries for payment wizard and new groups.
Handled automatically by Odoo on upgrade.
No destructive operations needed in this migration.
The pre_migrate script from 19.0.11.0.0 already cleaned the legacy tables.
"""
import logging
_logger = logging.getLogger(__name__)
def migrate(cr, version):
if not version:
return # Fresh install
_logger.info('pre_migrate laundry_management 19.0.12.0.0: checking commission states')
# Ensure commission state column allows new 'confirmed' value.
# In Odoo, Selection fields are stored as VARCHAR — no schema change needed.
# Just verify the column exists and log the existing state distribution.
cr.execute("""
SELECT state, COUNT(*) FROM laundry_commission
GROUP BY state
ORDER BY state
""")
rows = cr.fetchall()
if rows:
_logger.info(
'pre_migrate: commission state distribution: %s',
{state: count for state, count in rows}
)
else:
_logger.info('pre_migrate: laundry_commission table is empty — fresh data')
_logger.info('pre_migrate laundry_management 19.0.12.0.0: complete')

View File

@@ -1,103 +0,0 @@
"""Pre-migration: 19.0.12.0.0 → 19.0.13.0.0
Removes three models that are being retired in this version:
- laundry.product.wizard (replaced by native sale.order.line product selection)
- laundry.product.wizard.line (child of above)
- laundry.whatsapp.wizard (replaced by one-click wa.me URL action)
If these ir.model records are left in the DB while the Python classes no longer
exist, Odoo will log warnings or fail on field-level checks during upgrade.
We clean them here, before the ORM loads.
"""
import logging
_logger = logging.getLogger(__name__)
_REMOVED_MODELS = [
'laundry.product.wizard',
'laundry.product.wizard.line',
'laundry.whatsapp.wizard',
]
_REMOVED_TABLES = [
'laundry_product_wizard',
'laundry_product_wizard_line',
'laundry_whatsapp_wizard',
]
def migrate(cr, version):
if not version:
return
_logger.info('pre_migrate 19.0.13.0.0: cleaning retired wizard models %s', _REMOVED_MODELS)
# 1. Drop physical tables (TransientModels do have real tables in Odoo)
for tbl in _REMOVED_TABLES:
cr.execute(f'DROP TABLE IF EXISTS "{tbl}" CASCADE')
_logger.info(' dropped table: %s', tbl)
# 2. Remove ir.model.access entries for these models
cr.execute("""
DELETE FROM ir_model_access
WHERE model_id IN (
SELECT id FROM ir_model WHERE model = ANY(%s)
)
""", (_REMOVED_MODELS,))
_logger.info(' deleted %d ir.model.access rows', cr.rowcount)
# 3. Remove ir.rule entries
cr.execute("""
DELETE FROM ir_rule
WHERE model_id IN (
SELECT id FROM ir_model WHERE model = ANY(%s)
)
""", (_REMOVED_MODELS,))
# 4. Remove ir.model.fields for these models
cr.execute("""
DELETE FROM ir_model_fields
WHERE model_id IN (
SELECT id FROM ir_model WHERE model = ANY(%s)
)
""", (_REMOVED_MODELS,))
_logger.info(' deleted ir.model.fields rows')
# 5. Remove ir.model.constraint
cr.execute("""
DELETE FROM ir_model_constraint
WHERE model_id IN (
SELECT id FROM ir_model WHERE model = ANY(%s)
)
""", (_REMOVED_MODELS,))
# 6. Remove ir.model.relation
cr.execute("""
DELETE FROM ir_model_relation
WHERE model_id IN (
SELECT id FROM ir_model WHERE model = ANY(%s)
)
""", (_REMOVED_MODELS,))
# 7. Remove ir.model.data (XML IDs) for these models
cr.execute("""
DELETE FROM ir_model_data
WHERE model = 'ir.model' AND name IN (
SELECT REPLACE(model, '.', '_') || '_' || id::text
FROM ir_model WHERE model = ANY(%s)
)
""", (_REMOVED_MODELS,))
# Also delete by res_id
cr.execute("""
DELETE FROM ir_model_data
WHERE model = 'ir.model'
AND res_id IN (SELECT id FROM ir_model WHERE model = ANY(%s))
""", (_REMOVED_MODELS,))
# 8. Finally remove ir.model entries themselves
cr.execute("""
DELETE FROM ir_model WHERE model = ANY(%s)
""", (_REMOVED_MODELS,))
_logger.info(' deleted ir.model entries for retired wizards')
_logger.info('pre_migrate 19.0.13.0.0: complete')

View File

@@ -1,75 +0,0 @@
"""Phase 1 financial model migration.
Before: laundry_order.amount_paid was blindly copied from pos_order.amount_paid,
which includes Customer Account / pay-later payments. Every deferred sale
appeared fully paid → amount_due was always 0 → Settle Dues was non-functional.
After: amount_paid_cash + amount_deferred are computed from pos_payment rows,
classified by pos_payment_method.split_transactions. amount_due is recomputed
as amount_deferred - amount_settled.
This script rebuilds the split for every existing laundry.order by replaying
its linked pos_order's payment history.
"""
import logging
_logger = logging.getLogger(__name__)
def migrate(cr, version):
if not version:
return
_logger.info('laundry_management: rebuilding financial split for existing orders')
# Ensure the new columns exist (ORM will have added them, but be defensive)
cr.execute("""
ALTER TABLE laundry_order
ADD COLUMN IF NOT EXISTS amount_paid_cash numeric DEFAULT 0,
ADD COLUMN IF NOT EXISTS amount_deferred numeric DEFAULT 0;
""")
# Rebuild amount_paid_cash / amount_deferred per laundry order
# from pos_payment rows classified by pos_payment_method.split_transactions.
cr.execute("""
WITH classified AS (
SELECT
lo.id AS lo_id,
COALESCE(SUM(CASE WHEN pm.split_transactions = FALSE THEN pp.amount ELSE 0 END), 0) AS cash,
COALESCE(SUM(CASE WHEN pm.split_transactions = TRUE THEN pp.amount ELSE 0 END), 0) AS deferred
FROM laundry_order lo
LEFT JOIN pos_payment pp ON pp.pos_order_id = lo.pos_order_id
LEFT JOIN pos_payment_method pm ON pm.id = pp.payment_method_id
GROUP BY lo.id
)
UPDATE laundry_order lo
SET amount_paid_cash = c.cash,
amount_deferred = c.deferred,
amount_settled = COALESCE(lo.amount_settled, 0)
FROM classified c
WHERE c.lo_id = lo.id;
""")
# amount_due is a stored compute (amount_deferred - amount_settled).
# Populate it directly here so the values are correct before the ORM
# recomputes (ORM recompute on install will overwrite with same result).
cr.execute("""
UPDATE laundry_order
SET amount_due = GREATEST(
COALESCE(amount_deferred, 0) - COALESCE(amount_settled, 0),
0
);
""")
cr.execute("""
SELECT COUNT(*),
SUM(amount_paid_cash),
SUM(amount_deferred),
SUM(amount_due)
FROM laundry_order
""")
row = cr.fetchone()
_logger.info(
'laundry_management migration: %s orders — cash=%.2f, deferred=%.2f, due=%.2f',
row[0] or 0, row[1] or 0.0, row[2] or 0.0, row[3] or 0.0,
)