refactor(fusion_accounting_ai): route legacy reconcile tools through engine
When fusion_accounting_bank_rec is installed, match_bank_line_to_payments and auto_reconcile_bank_lines now use fusion.reconcile.engine via the BankRecAdapter, gaining precedent recording, AI suggestion superseding, and shared validation. Legacy paths preserved for Enterprise/Community- only installs (engine model absent -> fall back to set_line_bank_statement_line and _try_auto_reconcile_statement_lines). Also wraps engine.reconcile_batch's per-line loop in a savepoint so a single bad line's DB error (e.g. check-constraint violation) no longer poisons the whole batch transaction; the existing per-line try/except now isolates failures as originally intended. Made-with: Cursor
This commit is contained in:
@@ -67,7 +67,16 @@ def match_bank_line_to_payments(env, params):
|
||||
st_line = env['account.bank.statement.line'].browse(st_line_id)
|
||||
if not st_line.exists():
|
||||
return {'error': 'Statement line not found'}
|
||||
st_line.set_line_bank_statement_line(move_line_ids)
|
||||
# Phase 1 Task 23: route through engine when available
|
||||
if 'fusion.reconcile.engine' in env.registry:
|
||||
cands = env['account.move.line'].browse(move_line_ids).exists()
|
||||
if not cands:
|
||||
return {'error': 'No valid move_line_ids'}
|
||||
env['fusion.reconcile.engine'].reconcile_one(
|
||||
st_line, against_lines=cands)
|
||||
st_line.invalidate_recordset(['is_reconciled'])
|
||||
else:
|
||||
st_line.set_line_bank_statement_line(move_line_ids)
|
||||
return {
|
||||
'status': 'matched',
|
||||
'statement_line_id': st_line_id,
|
||||
@@ -83,7 +92,12 @@ def auto_reconcile_bank_lines(env, params):
|
||||
('company_id', '=', int(company_id)),
|
||||
])
|
||||
before_count = len(lines)
|
||||
lines._try_auto_reconcile_statement_lines(company_id=int(company_id))
|
||||
# Phase 1 Task 23: route through engine when available
|
||||
if 'fusion.reconcile.engine' in env.registry:
|
||||
env['fusion.reconcile.engine'].reconcile_batch(
|
||||
lines, strategy='auto')
|
||||
else:
|
||||
lines._try_auto_reconcile_statement_lines(company_id=int(company_id))
|
||||
still_unreconciled = env['account.bank.statement.line'].search([
|
||||
('is_reconciled', '=', False),
|
||||
('company_id', '=', int(company_id)),
|
||||
|
||||
@@ -177,14 +177,19 @@ class FusionReconcileEngine(models.AbstractModel):
|
||||
if line.is_reconciled:
|
||||
skipped += 1
|
||||
continue
|
||||
# Per-line savepoint so a single DB-level failure (e.g. a
|
||||
# check-constraint violation on one bad line) doesn't poison
|
||||
# the whole batch's transaction.
|
||||
try:
|
||||
candidates = self._fetch_candidates(line)
|
||||
picked = self._apply_strategy(line, candidates, strategy)
|
||||
if picked:
|
||||
self.reconcile_one(line, against_lines=picked)
|
||||
reconciled += 1
|
||||
else:
|
||||
skipped += 1
|
||||
with self.env.cr.savepoint():
|
||||
candidates = self._fetch_candidates(line)
|
||||
picked = self._apply_strategy(
|
||||
line, candidates, strategy)
|
||||
if picked:
|
||||
self.reconcile_one(line, against_lines=picked)
|
||||
reconciled += 1
|
||||
else:
|
||||
skipped += 1
|
||||
except Exception as e: # noqa: BLE001
|
||||
errors.append({'line_id': line.id, 'error': str(e)})
|
||||
_logger.warning(
|
||||
|
||||
@@ -12,3 +12,4 @@ from . import test_reconcile_engine_integration
|
||||
from . import test_bank_rec_prompt
|
||||
from . import test_bank_rec_adapter
|
||||
from . import test_bank_rec_tools
|
||||
from . import test_legacy_tools_refactor
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Tests verifying legacy tools route through fusion.reconcile.engine when present.
|
||||
|
||||
These tests run in the fusion_accounting_bank_rec context where the engine IS
|
||||
available, so they assert the engine path is taken and produces correct
|
||||
results. The fallback path is exercised by the existing fusion_accounting_ai
|
||||
tests when fusion_accounting_bank_rec is not installed."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
from odoo.addons.fusion_accounting_ai.services.tools import bank_reconciliation as tools
|
||||
from . import _factories as f
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestLegacyToolsRefactor(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.partner = self.env['res.partner'].create({'name': 'Refactor Test Partner'})
|
||||
|
||||
def test_match_bank_line_to_payments_uses_engine(self):
|
||||
"""When engine is present, match_bank_line_to_payments must produce
|
||||
a partial reconcile via the engine, not via set_line_bank_statement_line."""
|
||||
bank_line, recv_lines = f.make_reconcileable_pair(
|
||||
self.env, amount=180.00, partner=self.partner)
|
||||
result = tools.match_bank_line_to_payments(self.env, {
|
||||
'statement_line_id': bank_line.id,
|
||||
'move_line_ids': recv_lines.ids,
|
||||
})
|
||||
self.assertEqual(result.get('status'), 'matched')
|
||||
bank_line.invalidate_recordset(['is_reconciled'])
|
||||
self.assertTrue(bank_line.is_reconciled)
|
||||
# Verify a precedent was recorded - engine-only behaviour
|
||||
Precedent = self.env['fusion.reconcile.precedent']
|
||||
precedents = Precedent.search([('partner_id', '=', self.partner.id)])
|
||||
self.assertGreater(len(precedents), 0,
|
||||
"Engine path should record a precedent; legacy path would not")
|
||||
|
||||
def test_auto_reconcile_bank_lines_uses_engine(self):
|
||||
"""When engine is present, auto_reconcile_bank_lines must call
|
||||
fusion.reconcile.engine.reconcile_batch (not the Enterprise-only
|
||||
_try_auto_reconcile_statement_lines fallback). We patch
|
||||
reconcile_batch to verify routing without running the real engine
|
||||
across every legacy unreconciled line in the test DB."""
|
||||
Engine = type(self.env['fusion.reconcile.engine'])
|
||||
with patch.object(
|
||||
Engine, 'reconcile_batch', autospec=True,
|
||||
return_value={'reconciled_count': 2, 'skipped': 0, 'errors': []},
|
||||
) as engine_call:
|
||||
result = tools.auto_reconcile_bank_lines(self.env, {
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.assertEqual(result['status'], 'completed')
|
||||
self.assertTrue(engine_call.called,
|
||||
"Engine path must invoke fusion.reconcile.engine.reconcile_batch")
|
||||
# Verify the engine was passed the strategy='auto' kwarg per spec
|
||||
_self, _lines = engine_call.call_args.args[0], engine_call.call_args.args[1]
|
||||
self.assertEqual(engine_call.call_args.kwargs.get('strategy'), 'auto')
|
||||
Reference in New Issue
Block a user