fix(jobs): cleanup script — delete SOs, invoices, payments, pickings, quotes
The original cleanup script left behind 33 sale.order, 31 invoice account.move, 13 payments, 1 picking, 17 quote requests. Extended to nuke them too — SOs in any state, invoices regardless of posted/draft (force-cancels via SQL when ORM blocks it), payments forced to cancel before delete, stock.move + stock.move.line force-deleted, quote requests unlinked. Section ordering: payments -> invoices -> pickings/moves -> SOs (FK direction). Reconciliation links cleared via direct SQL. Sequences for sale.order and account.move.invoice reset to 1 so fresh SOs start at S00001. Re-seeded 31 fp.jobs across all 6 states after running the extended cleanup. Part of: native job model migration (spec 2026-04-25) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,10 +3,12 @@
|
|||||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
#
|
#
|
||||||
# DESTRUCTIVE: deletes ALL fp.job, fp.job.step, fp.job.step.timelog,
|
# DESTRUCTIVE: deletes ALL fp.job, fp.job.step, fp.job.step.timelog,
|
||||||
# mrp.production, mrp.workorder records and their dependent data
|
# mrp.production, mrp.workorder, sale.order, account.move (invoices),
|
||||||
# (deliveries, certs, thickness readings, holds, portal jobs, racking
|
# account.payment, stock.picking, stock.move, fusion.plating.quote.request
|
||||||
# inspections). Preserves masters (partners, parts, recipes, coating
|
# records and their dependent data (deliveries, certs, thickness readings,
|
||||||
# configs, baths, tanks, work centres, users, groups, settings).
|
# 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.
|
# Use only on demo/dev environments. Take a Proxmox snapshot first.
|
||||||
|
|
||||||
@@ -109,30 +111,172 @@ def run(env):
|
|||||||
env['mrp.production'].sudo().search([]).unlink()
|
env['mrp.production'].sudo().search([]).unlink()
|
||||||
print(' Deleted %d mrp.production rows' % n)
|
print(' Deleted %d mrp.production rows' % n)
|
||||||
|
|
||||||
# 14. Test SOs — delete ALL non-invoiced sale orders. Force state to
|
# 14. Account payments (must come before invoices — payment is reconciled
|
||||||
# 'cancel' via SQL because Odoo's _unlink_except_draft_or_cancel
|
# against move lines)
|
||||||
# guard otherwise blocks the unlink. Demo data only.
|
Payment = env['account.payment'].sudo()
|
||||||
so_ids = env['sale.order'].sudo().search([
|
payments = Payment.search([])
|
||||||
('invoice_ids', '=', False),
|
n = len(payments)
|
||||||
]).ids
|
if payments:
|
||||||
n = len(so_ids)
|
for p in payments:
|
||||||
if so_ids:
|
if p.state == 'paid':
|
||||||
|
try:
|
||||||
|
p.action_draft()
|
||||||
|
except Exception:
|
||||||
|
env.cr.execute(
|
||||||
|
"UPDATE account_payment SET state='draft' WHERE id=%s",
|
||||||
|
(p.id,),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
p.action_cancel()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Clear reconciliation links pointing at the payment moves
|
||||||
env.cr.execute(
|
env.cr.execute(
|
||||||
"UPDATE sale_order SET state='cancel' WHERE id = ANY(%s)",
|
"DELETE FROM account_partial_reconcile "
|
||||||
(so_ids,),
|
"WHERE debit_move_id IN (SELECT id FROM account_move_line WHERE move_id IN ("
|
||||||
|
" SELECT move_id FROM account_payment WHERE id = ANY(%s))) "
|
||||||
|
"OR credit_move_id IN (SELECT id FROM account_move_line WHERE move_id IN ("
|
||||||
|
" SELECT move_id FROM account_payment WHERE id = ANY(%s)))",
|
||||||
|
(payments.ids, payments.ids),
|
||||||
)
|
)
|
||||||
# Also cancel stock pickings the SOs may have created (forces
|
|
||||||
# Odoo's cascade-aware unlink to pass)
|
|
||||||
env.cr.execute(
|
env.cr.execute(
|
||||||
"UPDATE stock_picking SET state='cancel' "
|
"DELETE FROM account_payment WHERE id = ANY(%s)",
|
||||||
"WHERE sale_id = ANY(%s)",
|
(payments.ids,),
|
||||||
(so_ids,),
|
|
||||||
)
|
)
|
||||||
env.invalidate_all()
|
print(' Deleted %d account.payment rows' % n)
|
||||||
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
|
# 15. Invoices (account.move with out_invoice / out_refund / in_invoice
|
||||||
|
# / in_refund move types). Posted ones must be drafted/cancelled first.
|
||||||
|
Move = env['account.move'].sudo()
|
||||||
|
invoices = Move.search([
|
||||||
|
('move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')),
|
||||||
|
])
|
||||||
|
n = len(invoices)
|
||||||
|
if invoices:
|
||||||
|
for inv in invoices:
|
||||||
|
if inv.state == 'posted':
|
||||||
|
try:
|
||||||
|
inv.button_draft()
|
||||||
|
except Exception:
|
||||||
|
env.cr.execute(
|
||||||
|
"UPDATE account_move SET state='draft' WHERE id=%s",
|
||||||
|
(inv.id,),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
inv.button_cancel()
|
||||||
|
except Exception:
|
||||||
|
env.cr.execute(
|
||||||
|
"UPDATE account_move SET state='cancel' WHERE id=%s",
|
||||||
|
(inv.id,),
|
||||||
|
)
|
||||||
|
env.invalidate_all()
|
||||||
|
# Force-clear reconciliation links so unlink doesn't trip on
|
||||||
|
# partial_reconcile_id
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM account_partial_reconcile "
|
||||||
|
"WHERE debit_move_id IN (SELECT id FROM account_move_line WHERE move_id = ANY(%s)) "
|
||||||
|
"OR credit_move_id IN (SELECT id FROM account_move_line WHERE move_id = ANY(%s))",
|
||||||
|
(invoices.ids, invoices.ids),
|
||||||
|
)
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM account_move_line WHERE move_id = ANY(%s)",
|
||||||
|
(invoices.ids,),
|
||||||
|
)
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM account_move WHERE id = ANY(%s)",
|
||||||
|
(invoices.ids,),
|
||||||
|
)
|
||||||
|
print(' Deleted %d account.move (invoice) rows' % n)
|
||||||
|
|
||||||
|
# 16. Stock pickings + moves (any leftovers from MOs / SOs)
|
||||||
|
pickings = env['stock.picking'].sudo().search([])
|
||||||
|
n = len(pickings)
|
||||||
|
if pickings:
|
||||||
|
for pk in pickings:
|
||||||
|
if pk.state not in ('cancel', 'draft'):
|
||||||
|
try:
|
||||||
|
pk.action_cancel()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
env.cr.execute(
|
||||||
|
"UPDATE stock_picking SET state='cancel' WHERE id = ANY(%s)",
|
||||||
|
(pickings.ids,),
|
||||||
|
)
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM stock_move_line WHERE picking_id = ANY(%s)",
|
||||||
|
(pickings.ids,),
|
||||||
|
)
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM stock_move WHERE picking_id = ANY(%s)",
|
||||||
|
(pickings.ids,),
|
||||||
|
)
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM stock_picking WHERE id = ANY(%s)",
|
||||||
|
(pickings.ids,),
|
||||||
|
)
|
||||||
|
print(' Deleted %d stock.picking rows' % n)
|
||||||
|
|
||||||
|
# Any remaining orphan stock.move rows
|
||||||
|
moves = env['stock.move'].sudo().search([])
|
||||||
|
n = len(moves)
|
||||||
|
if moves:
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM stock_move_line WHERE move_id = ANY(%s)",
|
||||||
|
(moves.ids,),
|
||||||
|
)
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM stock_move WHERE id = ANY(%s)",
|
||||||
|
(moves.ids,),
|
||||||
|
)
|
||||||
|
print(' Deleted %d stock.move rows' % n)
|
||||||
|
|
||||||
|
# 17. Sale orders (cancel any non-cancel state first). Delete ALL —
|
||||||
|
# demo data only.
|
||||||
|
sos = env['sale.order'].sudo().search([])
|
||||||
|
n = len(sos)
|
||||||
|
if sos:
|
||||||
|
for so in sos:
|
||||||
|
if so.state not in ('cancel', 'draft'):
|
||||||
|
try:
|
||||||
|
so.action_cancel()
|
||||||
|
except Exception:
|
||||||
|
env.cr.execute(
|
||||||
|
"UPDATE sale_order SET state='cancel' WHERE id=%s",
|
||||||
|
(so.id,),
|
||||||
|
)
|
||||||
|
env.invalidate_all()
|
||||||
|
# Drop SO lines explicitly to avoid FK trip on unlink
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM sale_order_line WHERE order_id = ANY(%s)",
|
||||||
|
(sos.ids,),
|
||||||
|
)
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM sale_order WHERE id = ANY(%s)",
|
||||||
|
(sos.ids,),
|
||||||
|
)
|
||||||
|
print(' Deleted %d sale.order rows' % n)
|
||||||
|
|
||||||
|
# 18. Quote requests
|
||||||
|
if 'fusion.plating.quote.request' in env:
|
||||||
|
qrs = env['fusion.plating.quote.request'].sudo().search([])
|
||||||
|
n = len(qrs)
|
||||||
|
if qrs:
|
||||||
|
try:
|
||||||
|
qrs.unlink()
|
||||||
|
except Exception:
|
||||||
|
env.cr.execute(
|
||||||
|
"DELETE FROM fusion_plating_quote_request WHERE id = ANY(%s)",
|
||||||
|
(qrs.ids,),
|
||||||
|
)
|
||||||
|
print(' Deleted %d fusion.plating.quote.request rows' % n)
|
||||||
|
|
||||||
|
# 19. Reset sequences for SO and invoices so new ones start fresh
|
||||||
|
for code in ('sale.order', 'account.move.invoice'):
|
||||||
|
seq = env['ir.sequence'].sudo().search([('code', '=', code)], limit=1)
|
||||||
|
if seq:
|
||||||
|
seq.number_next = 1
|
||||||
|
|
||||||
|
# 20. Reset fp.job sequence so new ones start from JOB/00001
|
||||||
seq = env['ir.sequence'].sudo().search([('code', '=', 'fp.job')], limit=1)
|
seq = env['ir.sequence'].sudo().search([('code', '=', 'fp.job')], limit=1)
|
||||||
if seq:
|
if seq:
|
||||||
seq.number_next = 1
|
seq.number_next = 1
|
||||||
|
|||||||
Reference in New Issue
Block a user