From 75850aad7343577c1c049ae29de555825e2a8404 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 13:16:06 -0400 Subject: [PATCH] feat(fusion_accounting_bank_rec): auto-reconcile wizard TransientModel that filters unreconciled bank lines by journal + date range + strategy and runs engine.reconcile_batch. Shows reconciled_count / skipped_count / error_summary in result view. Made-with: Cursor --- fusion_accounting_bank_rec/__manifest__.py | 3 +- .../security/ir.model.access.csv | 1 + fusion_accounting_bank_rec/tests/__init__.py | 1 + .../tests/test_auto_reconcile_wizard.py | 50 ++++++++++++ .../wizards/__init__.py | 1 + .../wizards/auto_reconcile_wizard.py | 78 +++++++++++++++++++ .../wizards/auto_reconcile_wizard_views.xml | 39 ++++++++++ 7 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 fusion_accounting_bank_rec/tests/test_auto_reconcile_wizard.py create mode 100644 fusion_accounting_bank_rec/wizards/auto_reconcile_wizard.py create mode 100644 fusion_accounting_bank_rec/wizards/auto_reconcile_wizard_views.xml diff --git a/fusion_accounting_bank_rec/__manifest__.py b/fusion_accounting_bank_rec/__manifest__.py index 87f3ea80..64ce0b62 100644 --- a/fusion_accounting_bank_rec/__manifest__.py +++ b/fusion_accounting_bank_rec/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting — Bank Reconciliation', - 'version': '19.0.1.0.18', + 'version': '19.0.1.0.19', 'category': 'Accounting/Accounting', 'sequence': 28, 'summary': 'Native V19 bank reconciliation widget with AI confidence scoring + behavioural learning.', @@ -31,6 +31,7 @@ Built by Nexa Systems Inc. 'data': [ 'security/ir.model.access.csv', 'data/cron.xml', + 'wizards/auto_reconcile_wizard_views.xml', ], 'assets': { 'web.assets_backend': [ diff --git a/fusion_accounting_bank_rec/security/ir.model.access.csv b/fusion_accounting_bank_rec/security/ir.model.access.csv index e105c296..5a8b64be 100644 --- a/fusion_accounting_bank_rec/security/ir.model.access.csv +++ b/fusion_accounting_bank_rec/security/ir.model.access.csv @@ -8,3 +8,4 @@ access_fusion_reconcile_suggestion_admin,suggestion admin,model_fusion_reconcile access_fusion_bank_rec_widget_user,bank rec widget user,model_fusion_bank_rec_widget,fusion_accounting_core.group_fusion_accounting_user,1,1,1,1 access_fusion_unreconciled_bank_line_mv_user,unreconciled bank line mv user,model_fusion_unreconciled_bank_line_mv,fusion_accounting_core.group_fusion_accounting_user,1,0,0,0 access_fusion_unreconciled_bank_line_mv_admin,unreconciled bank line mv admin,model_fusion_unreconciled_bank_line_mv,fusion_accounting_core.group_fusion_accounting_admin,1,0,0,0 +access_fusion_auto_reconcile_wizard_user,fusion.auto.reconcile.wizard.user,model_fusion_auto_reconcile_wizard,base.group_user,1,1,1,0 diff --git a/fusion_accounting_bank_rec/tests/__init__.py b/fusion_accounting_bank_rec/tests/__init__.py index 2bb89b97..271d7566 100644 --- a/fusion_accounting_bank_rec/tests/__init__.py +++ b/fusion_accounting_bank_rec/tests/__init__.py @@ -16,3 +16,4 @@ from . import test_legacy_tools_refactor from . import test_mv_unreconciled from . import test_cron_methods from . import test_controller +from . import test_auto_reconcile_wizard diff --git a/fusion_accounting_bank_rec/tests/test_auto_reconcile_wizard.py b/fusion_accounting_bank_rec/tests/test_auto_reconcile_wizard.py new file mode 100644 index 00000000..6b552ff2 --- /dev/null +++ b/fusion_accounting_bank_rec/tests/test_auto_reconcile_wizard.py @@ -0,0 +1,50 @@ +"""Tests for fusion.auto.reconcile.wizard.""" + +from odoo.tests.common import TransactionCase, tagged +from . import _factories as f + + +@tagged('post_install', '-at_install') +class TestAutoReconcileWizard(TransactionCase): + + def setUp(self): + super().setUp() + self.partner = self.env['res.partner'].create({'name': 'Auto Wizard Partner'}) + self.journal = f.make_bank_journal(self.env, name='Auto Bank', code='AUBK') + + def test_wizard_runs_and_reconciles_matchable_lines(self): + statement = f.make_bank_statement(self.env, journal=self.journal) + for amount in [100.00, 200.00]: + f.make_invoice(self.env, partner=self.partner, amount=amount) + f.make_bank_line( + self.env, statement=statement, amount=amount, partner=self.partner) + + wizard = self.env['fusion.auto.reconcile.wizard'].create({ + 'journal_id': self.journal.id, + 'strategy': 'auto', + 'only_with_partner': True, + }) + wizard.action_run() + self.assertEqual(wizard.state, 'done') + self.assertGreaterEqual(wizard.reconciled_count, 2) + + def test_wizard_filters_by_date_range(self): + wizard = self.env['fusion.auto.reconcile.wizard'].create({ + 'journal_id': self.journal.id, + 'date_from': '2099-01-01', + 'date_to': '2099-12-31', + 'strategy': 'auto', + }) + wizard.action_run() + self.assertEqual(wizard.reconciled_count, 0) + + def test_wizard_skips_when_only_with_partner_excludes_orphans(self): + statement = f.make_bank_statement(self.env, journal=self.journal) + f.make_bank_line(self.env, statement=statement, amount=999, partner=None) + wizard = self.env['fusion.auto.reconcile.wizard'].create({ + 'journal_id': self.journal.id, + 'strategy': 'auto', + 'only_with_partner': True, + }) + wizard.action_run() + self.assertEqual(wizard.reconciled_count, 0) diff --git a/fusion_accounting_bank_rec/wizards/__init__.py b/fusion_accounting_bank_rec/wizards/__init__.py index e69de29b..ace9d80b 100644 --- a/fusion_accounting_bank_rec/wizards/__init__.py +++ b/fusion_accounting_bank_rec/wizards/__init__.py @@ -0,0 +1 @@ +from . import auto_reconcile_wizard diff --git a/fusion_accounting_bank_rec/wizards/auto_reconcile_wizard.py b/fusion_accounting_bank_rec/wizards/auto_reconcile_wizard.py new file mode 100644 index 00000000..1923bdd6 --- /dev/null +++ b/fusion_accounting_bank_rec/wizards/auto_reconcile_wizard.py @@ -0,0 +1,78 @@ +"""Auto-reconcile wizard. + +Lets the user pick filters (journal, date range, strategy) and runs +fusion.reconcile.engine.reconcile_batch on all matching unreconciled +bank lines. Shows summary of results. +""" + +from odoo import _, fields, models + + +class FusionAutoReconcileWizard(models.TransientModel): + _name = "fusion.auto.reconcile.wizard" + _description = "Auto-Reconcile Bank Statement Lines Wizard" + + journal_id = fields.Many2one( + 'account.journal', string="Bank Journal", + domain=[('type', '=', 'bank')], required=True) + date_from = fields.Date(string="Date From") + date_to = fields.Date(string="Date To", default=fields.Date.today) + strategy = fields.Selection([ + ('auto', 'Auto (try amount-exact, then multi-invoice, then FIFO)'), + ('amount_exact', 'Amount Exact only'), + ('fifo', 'FIFO only'), + ('multi_invoice', 'Multi-invoice combination only'), + ], default='auto', required=True) + only_with_partner = fields.Boolean( + string="Only lines with a partner", + default=True, + help="Most safer matches require a known partner. Untick to attempt " + "matching for orphan lines too (uses memo tokenization).") + + 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) + + def _build_domain(self): + self.ensure_one() + domain = [ + ('journal_id', '=', self.journal_id.id), + ('is_reconciled', '=', False), + ] + if self.date_from: + domain.append(('date', '>=', self.date_from)) + if self.date_to: + domain.append(('date', '<=', self.date_to)) + if self.only_with_partner: + domain.append(('partner_id', '!=', False)) + return domain + + def action_run(self): + self.ensure_one() + Line = self.env['account.bank.statement.line'] + lines = Line.search(self._build_domain(), limit=1000) + result = self.env['fusion.reconcile.engine'].reconcile_batch( + lines, strategy=self.strategy) + 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, + } diff --git a/fusion_accounting_bank_rec/wizards/auto_reconcile_wizard_views.xml b/fusion_accounting_bank_rec/wizards/auto_reconcile_wizard_views.xml new file mode 100644 index 00000000..1cd99340 --- /dev/null +++ b/fusion_accounting_bank_rec/wizards/auto_reconcile_wizard_views.xml @@ -0,0 +1,39 @@ + + + + + fusion.auto.reconcile.wizard.form + fusion.auto.reconcile.wizard + +
+ + + + + + + + + + + + + + +
+
+ +
+
+ + + Auto-Reconcile + fusion.auto.reconcile.wizard + form + new + + +