TransientModel + view + binding action so users can select bank lines from any list view and bulk-apply either engine.reconcile_batch or a chosen reconcile model. Made-with: Cursor
94 lines
3.7 KiB
Python
94 lines
3.7 KiB
Python
"""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,
|
|
}
|