108 lines
4.1 KiB
Python
108 lines
4.1 KiB
Python
"""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')
|