diff --git a/fusion_accounting_ai/services/data_adapters/__init__.py b/fusion_accounting_ai/services/data_adapters/__init__.py index b6cdbfcb..df70bf1d 100644 --- a/fusion_accounting_ai/services/data_adapters/__init__.py +++ b/fusion_accounting_ai/services/data_adapters/__init__.py @@ -1,7 +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 +from . import bank_rec # noqa: F401 +from . import reports # noqa: F401 __all__ = ['DataAdapter', 'AdapterMode', 'get_adapter', 'register_adapter'] diff --git a/fusion_accounting_ai/services/data_adapters/reports.py b/fusion_accounting_ai/services/data_adapters/reports.py new file mode 100644 index 00000000..37e71d40 --- /dev/null +++ b/fusion_accounting_ai/services/data_adapters/reports.py @@ -0,0 +1,56 @@ +"""Reports data adapter. + +Routes report-data lookups across: +- FUSION: fusion.account.report (added by fusion_accounting_reports, Phase 2) +- ENTERPRISE: account.report from account_reports +- COMMUNITY: raw aggregations on account.move.line +""" + +from .base import DataAdapter +from ._registry import register_adapter + + +class ReportsAdapter(DataAdapter): + FUSION_MODEL = 'fusion.account.report' + ENTERPRISE_MODULE = 'account_reports' + + def trial_balance(self, date_to=None, company_ids=None): + return self._dispatch('trial_balance', date_to=date_to, company_ids=company_ids) + + def trial_balance_via_fusion(self, date_to=None, company_ids=None): + # Phase 2 will implement; for now defer to community. + return self.trial_balance_via_community(date_to=date_to, company_ids=company_ids) + + def trial_balance_via_enterprise(self, date_to=None, company_ids=None): + # Enterprise account_reports has rich filters; for AI-tool consumption, + # the community shape suffices and avoids brittle coupling to Odoo's + # report-line internals. + return self.trial_balance_via_community(date_to=date_to, company_ids=company_ids) + + def trial_balance_via_community(self, date_to=None, company_ids=None): + domain = [('parent_state', '=', 'posted')] + if date_to: + domain.append(('date', '<=', date_to)) + if company_ids: + domain.append(('company_id', 'in', list(company_ids))) + + Line = self.env['account.move.line'].sudo() + groups = Line._read_group( + domain=domain, + groupby=['account_id'], + aggregates=['debit:sum', 'credit:sum'], + ) + return [ + { + 'account_id': account.id, + 'account_code': account.code, + 'account_name': account.name, + 'debit': debit_sum, + 'credit': credit_sum, + 'balance': debit_sum - credit_sum, + } + for account, debit_sum, credit_sum in groups + ] + + +register_adapter('reports', ReportsAdapter) diff --git a/fusion_accounting_ai/tests/test_data_adapters.py b/fusion_accounting_ai/tests/test_data_adapters.py index a22be0db..4bff52aa 100644 --- a/fusion_accounting_ai/tests/test_data_adapters.py +++ b/fusion_accounting_ai/tests/test_data_adapters.py @@ -58,3 +58,19 @@ class TestBankRecAdapter(TransactionCase): ids = [r['id'] for r in rows] self.assertIn(self.line.id, ids, f"Expected line {self.line.id} in unreconciled list, got: {ids}") + + +@tagged('post_install', '-at_install') +class TestReportsAdapter(TransactionCase): + """Verify the reports adapter computes a trial-balance-shaped result.""" + + def test_trial_balance_returns_rows_in_pure_community(self): + adapter = get_adapter(self.env, 'reports') + # Compute an empty-filter trial balance for the current company. Should + # return a list (possibly empty in a fresh test DB) without errors. + result = adapter.trial_balance() + self.assertIsInstance(result, list) + # Each row should have account_id and balance keys + for row in result: + self.assertIn('account_id', row) + self.assertIn('balance', row)