"""Bulk reconcile wizard — operates on user-selected records. Reads active_ids from context (selected bank lines). Two modes: 1. Auto (run engine on all selected with chosen strategy) 2. Apply reconcile model (apply a chosen account.reconcile.model to all) """ from odoo import _, api, fields, models from odoo.exceptions import UserError class FusionBulkReconcileWizard(models.TransientModel): _name = "fusion.bulk.reconcile.wizard" _description = "Bulk Reconcile Selected Bank Lines Wizard" statement_line_ids = fields.Many2many( 'account.bank.statement.line', string="Selected Bank Lines", default=lambda self: [(6, 0, self._default_line_ids())]) selected_count = fields.Integer( compute='_compute_selected_count', string="# Selected") mode = fields.Selection([ ('auto', 'Auto (engine reconcile_batch)'), ('reconcile_model', 'Apply Reconcile Model'), ], default='auto', required=True) strategy = fields.Selection([ ('auto', 'Auto'), ('amount_exact', 'Amount Exact only'), ('fifo', 'FIFO only'), ('multi_invoice', 'Multi-invoice'), ], default='auto') reconcile_model_id = fields.Many2one( 'account.reconcile.model', string="Reconcile Model", domain=[('rule_type', '=', 'writeoff_button')]) state = fields.Selection( [('draft', 'Draft'), ('done', 'Done')], default='draft') reconciled_count = fields.Integer(readonly=True) skipped_count = fields.Integer(readonly=True) error_count = fields.Integer(readonly=True) error_summary = fields.Text(readonly=True) @api.model def _default_line_ids(self): ctx = self.env.context if ctx.get('active_model') == 'account.bank.statement.line': return ctx.get('active_ids', []) return [] @api.depends('statement_line_ids') def _compute_selected_count(self): for w in self: w.selected_count = len(w.statement_line_ids) def action_run(self): self.ensure_one() if self.mode == 'auto': result = self.env['fusion.reconcile.engine'].reconcile_batch( self.statement_line_ids, strategy=self.strategy) elif self.mode == 'reconcile_model': if not self.reconcile_model_id: raise UserError(_("Pick a reconcile model first.")) # Phase 1 fallback: apply the model line-by-line via the engine's # write_off path (simplified — real reconcile-model semantics are # more nuanced; full integration in Task 38 follow-up). result = {'reconciled_count': 0, 'skipped': 0, 'errors': []} for line in self.statement_line_ids: try: self.reconcile_model_id._apply_lines_for_bank_statement_line(line) result['reconciled_count'] += 1 except Exception as e: # noqa: BLE001 result['errors'].append({'line_id': line.id, 'error': str(e)}) else: result = {'reconciled_count': 0, 'skipped': 0, 'errors': []} errors = result.get('errors', []) self.write({ 'state': 'done', 'reconciled_count': result.get('reconciled_count', 0), 'skipped_count': result.get('skipped', 0), 'error_count': len(errors), 'error_summary': '\n'.join( f"Line {e['line_id']}: {e['error']}" for e in errors[:20] ) or False, }) return { 'type': 'ir.actions.act_window', 'res_model': self._name, 'res_id': self.id, 'view_mode': 'form', 'target': 'new', 'context': self.env.context, }