Tower: unpublish laundry_management — remove source from 19.0 branch
This commit is contained in:
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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,
|
||||
)
|
||||
Reference in New Issue
Block a user