feat(fusion_accounting_reports): fusion.report.engine 5-method API
The engine orchestrator. compute_pnl, compute_balance_sheet, compute_trial_balance, compute_gl, drill_down. All controllers, wizards, AI tools must route through these methods; no direct SQL aggregation from anywhere else. Internal pipeline: validate -> fetch hierarchy -> SQL aggregate -> resolve line_specs -> optional comparison + anomaly. Uses raw SQL for the per-account aggregate (the perf-critical step), ORM for everything else. Per-company report lookup with global fallback (company_id desc nulls last). Balance sheet uses 1970 epoch as date_from for cumulative-since-inception semantics. 7 new tests, 42 total passing. Made-with: Cursor
This commit is contained in:
@@ -3,3 +3,4 @@ from . import test_currency_conversion
|
||||
from . import test_fusion_report
|
||||
from . import test_line_resolver
|
||||
from . import test_drill_down_resolver
|
||||
from . import test_fusion_report_engine
|
||||
|
||||
109
fusion_accounting_reports/tests/test_fusion_report_engine.py
Normal file
109
fusion_accounting_reports/tests/test_fusion_report_engine.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""Tests for fusion.report.engine AbstractModel."""
|
||||
|
||||
from datetime import date
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
from odoo.addons.fusion_accounting_reports.services.date_periods import Period
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestFusionReportEngine(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.pnl_report = self.env['fusion.report'].create({
|
||||
'name': 'Test P&L Engine',
|
||||
'code': 'test_pnl_engine',
|
||||
'report_type': 'pnl',
|
||||
'line_specs': [
|
||||
{'label': 'Revenue', 'account_type_prefix': 'income_', 'sign': 1},
|
||||
{'label': 'Expenses', 'account_type_prefix': 'expense_', 'sign': -1},
|
||||
{'label': 'Net Profit', 'compute': 'subtotal', 'above': 2},
|
||||
],
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
def test_engine_model_exists(self):
|
||||
self.assertIn('fusion.report.engine', self.env.registry)
|
||||
|
||||
def test_compute_pnl_returns_dict_with_rows(self):
|
||||
period = Period(date(2026, 1, 1), date(2026, 12, 31), 'Test 2026')
|
||||
result = self.env['fusion.report.engine'].compute_pnl(
|
||||
period, company_id=self.env.company.id,
|
||||
)
|
||||
self.assertIn('rows', result)
|
||||
self.assertIn('report_type', result)
|
||||
self.assertEqual(result['report_type'], 'pnl')
|
||||
|
||||
def test_compute_balance_sheet(self):
|
||||
self.env['fusion.report'].create({
|
||||
'name': 'Test BS',
|
||||
'code': 'test_bs_engine',
|
||||
'report_type': 'balance_sheet',
|
||||
'line_specs': [
|
||||
{'label': 'Assets', 'account_type_prefix': 'asset_', 'sign': 1},
|
||||
],
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
result = self.env['fusion.report.engine'].compute_balance_sheet(
|
||||
date(2026, 4, 19), company_id=self.env.company.id,
|
||||
)
|
||||
self.assertEqual(result['report_type'], 'balance_sheet')
|
||||
self.assertEqual(result['period']['date_to'], '2026-04-19')
|
||||
|
||||
def test_compute_trial_balance(self):
|
||||
self.env['fusion.report'].create({
|
||||
'name': 'Test TB',
|
||||
'code': 'test_tb_engine',
|
||||
'report_type': 'trial_balance',
|
||||
'line_specs': [],
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
period = Period(date(2026, 1, 1), date(2026, 12, 31), 'Test 2026')
|
||||
result = self.env['fusion.report.engine'].compute_trial_balance(
|
||||
period, company_id=self.env.company.id,
|
||||
)
|
||||
self.assertEqual(result['report_type'], 'trial_balance')
|
||||
|
||||
def test_compute_pnl_with_comparison(self):
|
||||
period = Period(date(2026, 1, 1), date(2026, 12, 31), 'Test 2026')
|
||||
result = self.env['fusion.report.engine'].compute_pnl(
|
||||
period,
|
||||
comparison='previous_year',
|
||||
company_id=self.env.company.id,
|
||||
)
|
||||
self.assertIsNotNone(result.get('comparison_period'))
|
||||
self.assertEqual(result['comparison_period']['date_to'], '2025-12-31')
|
||||
|
||||
def test_drill_down_returns_list(self):
|
||||
line = self.env['account.move.line'].search([
|
||||
('parent_state', '=', 'posted'),
|
||||
], limit=1)
|
||||
if not line:
|
||||
self.skipTest("No posted lines in DB")
|
||||
period = Period(line.date, line.date, 'Single day')
|
||||
rows = self.env['fusion.report.engine'].drill_down(
|
||||
account_id=line.account_id.id,
|
||||
period=period,
|
||||
company_id=line.company_id.id,
|
||||
)
|
||||
self.assertIsInstance(rows, list)
|
||||
|
||||
def test_no_report_raises_validation_error(self):
|
||||
period = Period(date(2026, 1, 1), date(2026, 12, 31), 'Test 2026')
|
||||
# Inactivate any pre-existing GL definitions so the lookup
|
||||
# fails for this test, then restore them after.
|
||||
existing = self.env['fusion.report'].search(
|
||||
[('report_type', '=', 'general_ledger')]
|
||||
)
|
||||
prior_active = {r.id: r.active for r in existing}
|
||||
existing.write({'active': False})
|
||||
try:
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['fusion.report.engine'].compute_gl(
|
||||
period, company_id=self.env.company.id,
|
||||
)
|
||||
finally:
|
||||
for r in existing:
|
||||
r.active = prior_active.get(r.id, True)
|
||||
Reference in New Issue
Block a user