diff --git a/fusion_plating/fusion_plating_jobs/scripts/cleanup_demo_data.py b/fusion_plating/fusion_plating_jobs/scripts/cleanup_demo_data.py index c3f5a9b8..cb560c69 100644 --- a/fusion_plating/fusion_plating_jobs/scripts/cleanup_demo_data.py +++ b/fusion_plating/fusion_plating_jobs/scripts/cleanup_demo_data.py @@ -3,10 +3,12 @@ # 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). +# mrp.production, mrp.workorder, sale.order, account.move (invoices), +# account.payment, stock.picking, stock.move, fusion.plating.quote.request +# 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. @@ -109,30 +111,172 @@ def run(env): 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: + # 14. Account payments (must come before invoices — payment is reconciled + # against move lines) + Payment = env['account.payment'].sudo() + payments = Payment.search([]) + n = len(payments) + if payments: + for p in payments: + 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( - "UPDATE sale_order SET state='cancel' WHERE id = ANY(%s)", - (so_ids,), + "DELETE FROM account_partial_reconcile " + "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( - "UPDATE stock_picking SET state='cancel' " - "WHERE sale_id = ANY(%s)", - (so_ids,), + "DELETE FROM account_payment WHERE id = ANY(%s)", + (payments.ids,), ) - env.invalidate_all() - env['sale.order'].sudo().browse(so_ids).unlink() - print(' Deleted %d sale.order rows (uninvoiced)' % n) + print(' Deleted %d account.payment rows' % 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) if seq: seq.number_next = 1