import logging _logger = logging.getLogger('cleanup_duplicates') BSL = env['account.bank.statement.line'].sudo() AML = env['account.move.line'].sudo() AM = env['account.move'].sudo() # All 64 duplicate statement line IDs (the second import set, 18703-18767) dupe_ids = [ 18703, 18704, 18705, 18706, 18707, 18708, 18709, 18710, 18711, 18712, 18713, 18714, 18715, 18716, 18717, 18718, 18719, 18720, 18721, 18722, 18723, 18724, 18725, 18726, 18727, 18728, 18729, 18730, 18731, 18732, 18733, 18734, 18735, 18736, 18737, 18738, 18739, 18740, 18741, 18742, 18743, 18744, 18745, 18746, 18747, 18748, 18749, 18750, 18751, 18752, 18753, 18754, 18755, 18756, 18757, 18758, 18759, 18760, 18761, 18762, 18763, 18764, 18766, 18767, ] dupes = BSL.browse(dupe_ids) print(f'Processing {len(dupes)} duplicate statement lines', flush=True) reconciled_count = 0 unreconciled_count = 0 error_count = 0 for line in dupes: move = line.move_id if line.is_reconciled: # Step 1: Un-reconcile — remove partial reconcile entries # Find the statement line's AML and its partial reconciliations st_aml = move.line_ids.filtered(lambda l: l.statement_line_id == line) if st_aml: # Find and remove partial reconcile entries partials = env['account.partial.reconcile'].sudo().search([ '|', ('debit_move_id', 'in', st_aml.ids), ('credit_move_id', 'in', st_aml.ids), ]) if partials: partials.unlink() # Also check full reconcile full_recs = st_aml.mapped('full_reconcile_id') if full_recs: full_recs.unlink() reconciled_count += 1 # Step 2: Reset move to draft so we can delete it try: if move.state == 'posted': move.button_draft() # Step 3: Cancel and delete the move (which deletes the statement line too) move.button_cancel() move.with_context(force_delete=True).unlink() unreconciled_count += 1 except Exception as e: print(f' Error on line {line.id}: {e}', flush=True) error_count += 1 env.cr.rollback() continue if unreconciled_count % 20 == 0: env.cr.commit() print(f' Progress: {unreconciled_count} deleted...', flush=True) env.cr.commit() print(f'DONE: {unreconciled_count} deleted, {reconciled_count} were reconciled, {error_count} errors', flush=True) # Verify remaining = BSL.search_count([('id', 'in', dupe_ids)]) print(f'Verification: {remaining} duplicate lines still exist (should be 0)', flush=True)