cleanup_demo_data.py: deletes ALL fp.job, fp.job.step, timelogs, mrp.production, mrp.workorder, and dependent records (deliveries, certs, holds, portal jobs, racking inspections, uninvoiced SOs). Resets the fp.job sequence. Preserves masters. Force-cancels MOs/SOs via SQL UPDATE before unlink to bypass Odoo's _unlink_except_done and _unlink_except_draft_or_cancel guards. seed_demo_data.py: creates 31 fp.job rows distributed across all 6 states (draft=5, confirmed=6, in_progress=8, on_hold=3, done=6, cancelled=3). In_progress jobs have mixed step states with real timelogs to simulate a live shop floor. Falls back to direct fp.job creation when a customer's parts have no coating/recipe, ensuring customer variety even with sparse coating data. Both scripts: idempotent (safe to re-run), commit at end, walk dependents bottom-up to avoid FK violations. Used to reset entech demo data after the migration trial. Part of: native job model migration (spec 2026-04-25) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
149 lines
5.6 KiB
Python
149 lines
5.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
#
|
|
# DESTRUCTIVE: deletes ALL fp.job, fp.job.step, fp.job.step.timelog,
|
|
# mrp.production, mrp.workorder records and their dependent data
|
|
# (deliveries, certs, thickness readings, holds, portal jobs, racking
|
|
# inspections). Preserves masters (partners, parts, recipes, coating
|
|
# configs, baths, tanks, work centres, users, groups, settings).
|
|
#
|
|
# Use only on demo/dev environments. Take a Proxmox snapshot first.
|
|
|
|
def run(env):
|
|
print('=== Cleanup starting ===')
|
|
|
|
# Walk dependents bottom-up so FK cascades don't bite us.
|
|
# 1. Time logs (cascades on step delete, but be explicit)
|
|
n = env['fp.job.step.timelog'].search_count([])
|
|
env['fp.job.step.timelog'].sudo().search([]).unlink()
|
|
print(' Deleted %d fp.job.step.timelog rows' % n)
|
|
|
|
# 2. fp.job.node.override (cascades on job delete)
|
|
n = env['fp.job.node.override'].search_count([])
|
|
env['fp.job.node.override'].sudo().search([]).unlink()
|
|
print(' Deleted %d fp.job.node.override rows' % n)
|
|
|
|
# 3. Deliveries linked to jobs OR with job_ref set OR linked to a SO that
|
|
# we will delete. Delete ALL deliveries — they're test data.
|
|
if 'fusion.plating.delivery' in env:
|
|
deliveries = env['fusion.plating.delivery'].sudo().search([])
|
|
n = len(deliveries)
|
|
deliveries.unlink()
|
|
print(' Deleted %d fusion.plating.delivery rows' % n)
|
|
|
|
# 4. Certificates linked to jobs/MOs
|
|
if 'fp.certificate' in env:
|
|
certs = env['fp.certificate'].sudo().search([])
|
|
n = len(certs)
|
|
certs.unlink()
|
|
print(' Deleted %d fp.certificate rows' % n)
|
|
|
|
# 5. Thickness readings
|
|
if 'fp.thickness.reading' in env:
|
|
tr = env['fp.thickness.reading'].sudo().search([])
|
|
n = len(tr)
|
|
tr.unlink()
|
|
print(' Deleted %d fp.thickness.reading rows' % n)
|
|
|
|
# 6. Quality holds linked to jobs/MOs
|
|
if 'fusion.plating.quality.hold' in env:
|
|
holds = env['fusion.plating.quality.hold'].sudo().search([])
|
|
n = len(holds)
|
|
holds.unlink()
|
|
print(' Deleted %d fusion.plating.quality.hold rows' % n)
|
|
|
|
# 7. Portal jobs (linked to jobs OR legacy production)
|
|
if 'fusion.plating.portal.job' in env:
|
|
portals = env['fusion.plating.portal.job'].sudo().search([])
|
|
n = len(portals)
|
|
portals.unlink()
|
|
print(' Deleted %d fusion.plating.portal.job rows' % n)
|
|
|
|
# 8. Racking inspections — required FK to mrp.production, so delete
|
|
# BEFORE we kill the productions.
|
|
if 'fp.racking.inspection' in env:
|
|
insps = env['fp.racking.inspection'].sudo().search([])
|
|
n = len(insps)
|
|
insps.unlink()
|
|
print(' Deleted %d fp.racking.inspection rows' % n)
|
|
|
|
# 9. Receiving records (required FK to sale.order — delete before SOs)
|
|
if 'fp.receiving' in env:
|
|
recs = env['fp.receiving'].sudo().search([])
|
|
n = len(recs)
|
|
recs.unlink()
|
|
print(' Deleted %d fp.receiving rows' % n)
|
|
|
|
# 10. fp.job.step (cascade-safe via job_id, but be explicit)
|
|
n = env['fp.job.step'].search_count([])
|
|
env['fp.job.step'].sudo().search([]).unlink()
|
|
print(' Deleted %d fp.job.step rows' % n)
|
|
|
|
# 11. fp.job
|
|
n = env['fp.job'].search_count([])
|
|
env['fp.job'].sudo().search([]).unlink()
|
|
print(' Deleted %d fp.job rows' % n)
|
|
|
|
# 12. mrp.workorder (legacy)
|
|
n = env['mrp.workorder'].search_count([])
|
|
env['mrp.workorder'].sudo().search([]).unlink()
|
|
print(' Deleted %d mrp.workorder rows' % n)
|
|
|
|
# 13. mrp.production (legacy) — force state via SQL so unlink() bypasses
|
|
# Odoo's _unlink_except_done guard (which forbids deleting done MOs)
|
|
# and the action_cancel guard (which forbids cancelling done MOs).
|
|
# Demo data only.
|
|
n = env['mrp.production'].search_count([])
|
|
if n:
|
|
# 'cancel' state is the only state mrp.production._unlink_except_done
|
|
# explicitly permits.
|
|
env.cr.execute("UPDATE mrp_production SET state='cancel'")
|
|
# Also clear stock moves' state so cascaded checks pass
|
|
env.cr.execute(
|
|
"UPDATE stock_move SET state='cancel' "
|
|
"WHERE raw_material_production_id IN (SELECT id FROM mrp_production) "
|
|
"OR production_id IN (SELECT id FROM mrp_production)"
|
|
)
|
|
env.invalidate_all()
|
|
env['mrp.production'].sudo().search([]).unlink()
|
|
print(' Deleted %d mrp.production rows' % n)
|
|
|
|
# 14. Test SOs — delete ALL non-invoiced sale orders. Force state to
|
|
# 'cancel' via SQL because Odoo's _unlink_except_draft_or_cancel
|
|
# guard otherwise blocks the unlink. Demo data only.
|
|
so_ids = env['sale.order'].sudo().search([
|
|
('invoice_ids', '=', False),
|
|
]).ids
|
|
n = len(so_ids)
|
|
if so_ids:
|
|
env.cr.execute(
|
|
"UPDATE sale_order SET state='cancel' WHERE id = ANY(%s)",
|
|
(so_ids,),
|
|
)
|
|
# Also cancel stock pickings the SOs may have created (forces
|
|
# Odoo's cascade-aware unlink to pass)
|
|
env.cr.execute(
|
|
"UPDATE stock_picking SET state='cancel' "
|
|
"WHERE sale_id = ANY(%s)",
|
|
(so_ids,),
|
|
)
|
|
env.invalidate_all()
|
|
env['sale.order'].sudo().browse(so_ids).unlink()
|
|
print(' Deleted %d sale.order rows (uninvoiced)' % n)
|
|
|
|
# 15. Reset fp.job sequence so new ones start from JOB/00001
|
|
seq = env['ir.sequence'].sudo().search([('code', '=', 'fp.job')], limit=1)
|
|
if seq:
|
|
seq.number_next = 1
|
|
print(' Reset fp.job sequence to start at 1')
|
|
|
|
env.cr.commit()
|
|
print('=== Cleanup complete ===')
|
|
|
|
|
|
try:
|
|
run(env)
|
|
except NameError:
|
|
print('Run inside `odoo shell`.')
|