diff --git a/fusion_accounting_reports/__manifest__.py b/fusion_accounting_reports/__manifest__.py index 68de9744..39bf43dd 100644 --- a/fusion_accounting_reports/__manifest__.py +++ b/fusion_accounting_reports/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting Reports', - 'version': '19.0.1.0.17', + 'version': '19.0.1.0.18', 'category': 'Accounting/Accounting', 'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).', 'description': """ diff --git a/fusion_accounting_reports/tests/__init__.py b/fusion_accounting_reports/tests/__init__.py index 06f06b14..2eb7366a 100644 --- a/fusion_accounting_reports/tests/__init__.py +++ b/fusion_accounting_reports/tests/__init__.py @@ -14,3 +14,4 @@ from . import test_reports_controller from . import test_reports_adapter from . import test_fusion_report_tools from . import test_engine_property +from . import test_pnl_integration diff --git a/fusion_accounting_reports/tests/test_pnl_integration.py b/fusion_accounting_reports/tests/test_pnl_integration.py new file mode 100644 index 00000000..10d839e5 --- /dev/null +++ b/fusion_accounting_reports/tests/test_pnl_integration.py @@ -0,0 +1,107 @@ +"""Integration test: P&L produces correct totals against known fixtures. + +Creates a small set of known invoices/bills and verifies that compute_pnl +returns the expected Revenue, Expenses, Net Income.""" + +from datetime import date + +from odoo.tests.common import TransactionCase, tagged + +from odoo.addons.fusion_accounting_reports.services.date_periods import Period + + +@tagged('post_install', '-at_install', 'integration') +class TestPnlIntegration(TransactionCase): + + def setUp(self): + super().setUp() + self.partner = self.env['res.partner'].create( + {'name': 'P&L Test Partner'}) + self.income_account = self.env['account.account'].search( + [('account_type', '=', 'income'), + ('company_ids', 'in', self.env.company.id)], + limit=1, + ) + # Make a service product and pin an income account so invoice lines + # always book to a known revenue account regardless of localisation. + self.product = self.env['product.product'].create({ + 'name': 'Fusion P&L Test Service', + 'type': 'service', + }) + if self.income_account: + self.product.property_account_income_id = self.income_account + + def _create_invoice(self, amount, *, date_=None, move_type='out_invoice'): + line_vals = { + 'product_id': self.product.id, + 'name': 'Test', + 'quantity': 1, + 'price_unit': amount, + 'tax_ids': [(6, 0, [])], + } + if self.income_account: + line_vals['account_id'] = self.income_account.id + invoice = self.env['account.move'].create({ + 'move_type': move_type, + 'partner_id': self.partner.id, + 'invoice_date': date_ or date(2026, 6, 15), + 'invoice_line_ids': [(0, 0, line_vals)], + }) + invoice.action_post() + # The engine reads parent_state via raw SQL; force a flush so the + # field is materialised in the DB before we aggregate. + self.env.flush_all() + return invoice + + def test_pnl_includes_invoice_revenue(self): + period = Period(date(2026, 1, 1), date(2026, 12, 31), 'Test 2026') + baseline = self.env['fusion.report.engine'].compute_pnl( + period, company_id=self.env.company.id) + baseline_labels = [r.get('label') for r in baseline['rows']] + revenue_baseline = next( + (r['amount'] for r in baseline['rows'] + if r.get('label') == 'Revenue'), + None, + ) + self.assertIsNotNone( + revenue_baseline, + msg=f"Revenue row not found; got labels: {baseline_labels}", + ) + + self._create_invoice(1000) + + result = self.env['fusion.report.engine'].compute_pnl( + period, company_id=self.env.company.id) + revenue_after = next( + (r['amount'] for r in result['rows'] + if r.get('label') == 'Revenue'), + None, + ) + self.assertIsNotNone(revenue_after) + + delta = revenue_after - revenue_baseline + self.assertAlmostEqual( + delta, 1000, places=0, + msg=f"Expected Revenue +1000, got {delta:.2f}", + ) + + def test_pnl_with_comparison_returns_both_periods(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')) + for row in result['rows']: + if row.get('amount_comparison') is not None: + self.assertIsInstance(row['amount_comparison'], (int, float)) + return + # No row had comparison amounts -- still acceptable for empty periods. + + def test_pnl_net_income_is_subtotal(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) + last = result['rows'][-1] + self.assertTrue(last['is_subtotal']) + self.assertEqual(last['label'], 'Net Income')