feat(fusion_accounting_ai): add BankRecAdapter for tri-mode bank-rec lookups
Made-with: Cursor
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
from .base import DataAdapter, AdapterMode
|
||||
from ._registry import get_adapter, register_adapter
|
||||
|
||||
# Side-effect imports: each adapter module calls register_adapter at module load.
|
||||
from . import bank_rec # noqa: F401
|
||||
|
||||
__all__ = ['DataAdapter', 'AdapterMode', 'get_adapter', 'register_adapter']
|
||||
|
||||
53
fusion_accounting_ai/services/data_adapters/bank_rec.py
Normal file
53
fusion_accounting_ai/services/data_adapters/bank_rec.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Bank reconciliation data adapter.
|
||||
|
||||
Routes bank-rec data lookups across:
|
||||
- FUSION: fusion.bank.rec.widget (added by fusion_accounting_bank_rec, Phase 1)
|
||||
- ENTERPRISE: account_accountant's bank_rec_widget JS service
|
||||
- COMMUNITY: pure search on account.bank.statement.line
|
||||
"""
|
||||
|
||||
from .base import DataAdapter
|
||||
from ._registry import register_adapter
|
||||
|
||||
|
||||
class BankRecAdapter(DataAdapter):
|
||||
FUSION_MODEL = 'fusion.bank.rec.widget'
|
||||
ENTERPRISE_MODULE = 'account_accountant'
|
||||
|
||||
def list_unreconciled(self, journal_id, limit=100):
|
||||
"""Return unreconciled bank statement lines for a journal."""
|
||||
return self._dispatch('list_unreconciled', journal_id=journal_id, limit=limit)
|
||||
|
||||
def list_unreconciled_via_fusion(self, journal_id, limit=100):
|
||||
# Phase 1 will add fusion.bank.rec.widget; this method becomes the primary path.
|
||||
# For now: even when the model exists, delegate to community read shape.
|
||||
return self.list_unreconciled_via_community(journal_id=journal_id, limit=limit)
|
||||
|
||||
def list_unreconciled_via_enterprise(self, journal_id, limit=100):
|
||||
# Enterprise's bank rec uses a JS-side service; from Python the cleanest
|
||||
# backend access is the same Community search (the data lives in
|
||||
# account.bank.statement.line either way). This adapter's purpose is
|
||||
# to expose a stable shape to AI tools regardless of which UI the user has.
|
||||
return self.list_unreconciled_via_community(journal_id=journal_id, limit=limit)
|
||||
|
||||
def list_unreconciled_via_community(self, journal_id, limit=100):
|
||||
Line = self.env['account.bank.statement.line'].sudo()
|
||||
records = Line.search([
|
||||
('journal_id', '=', journal_id),
|
||||
('is_reconciled', '=', False),
|
||||
], limit=limit, order='date desc, id desc')
|
||||
return [
|
||||
{
|
||||
'id': r.id,
|
||||
'date': r.date,
|
||||
'payment_ref': r.payment_ref,
|
||||
'amount': r.amount,
|
||||
'partner_id': r.partner_id.id if r.partner_id else None,
|
||||
'partner_name': r.partner_id.name if r.partner_id else None,
|
||||
'currency_id': r.currency_id.id if r.currency_id else None,
|
||||
}
|
||||
for r in records
|
||||
]
|
||||
|
||||
|
||||
register_adapter('bank_rec', BankRecAdapter)
|
||||
@@ -2,6 +2,7 @@ from odoo.tests.common import TransactionCase, tagged
|
||||
from odoo.addons.fusion_accounting_ai.services.data_adapters.base import (
|
||||
DataAdapter, AdapterMode,
|
||||
)
|
||||
from odoo.addons.fusion_accounting_ai.services.data_adapters import get_adapter
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
@@ -25,3 +26,35 @@ class TestDataAdapterBase(TransactionCase):
|
||||
enterprise_module='also_does_not_exist',
|
||||
)
|
||||
self.assertEqual(mode, AdapterMode.COMMUNITY)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestBankRecAdapter(TransactionCase):
|
||||
"""Verify the bank-rec adapter returns rows in any install profile."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.journal = self.env['account.journal'].create({
|
||||
'name': 'Test Bank',
|
||||
'type': 'bank',
|
||||
'code': 'TBNK',
|
||||
})
|
||||
self.statement = self.env['account.bank.statement'].create({
|
||||
'name': 'Test Statement',
|
||||
'journal_id': self.journal.id,
|
||||
})
|
||||
self.line = self.env['account.bank.statement.line'].create({
|
||||
'statement_id': self.statement.id,
|
||||
'journal_id': self.journal.id,
|
||||
'date': '2026-04-18',
|
||||
'payment_ref': 'Test Payment',
|
||||
'amount': 100.0,
|
||||
})
|
||||
|
||||
def test_list_unreconciled_returns_our_test_line(self):
|
||||
"""The adapter should find the unreconciled line we just created."""
|
||||
adapter = get_adapter(self.env, 'bank_rec')
|
||||
rows = adapter.list_unreconciled(journal_id=self.journal.id, limit=10)
|
||||
ids = [r['id'] for r in rows]
|
||||
self.assertIn(self.line.id, ids,
|
||||
f"Expected line {self.line.id} in unreconciled list, got: {ids}")
|
||||
|
||||
Reference in New Issue
Block a user