Three service modules with no Odoo dependencies: - date_periods: fiscal year/month/quarter bounds + comparison derivation - account_hierarchy: parent-child tree walker with type filtering - totaling: move-line aggregation primitives 18 unit tests covering edge cases (December rollover, Feb 29, fiscal- year-before-start, balance check tolerance). Made-with: Cursor
143 lines
5.6 KiB
Python
143 lines
5.6 KiB
Python
"""Unit tests for date_periods, account_hierarchy, totaling services."""
|
|
|
|
from datetime import date
|
|
|
|
from odoo.tests.common import TransactionCase, tagged
|
|
from odoo.addons.fusion_accounting_reports.services.date_periods import (
|
|
Period, fiscal_year_bounds, month_bounds, quarter_bounds, comparison_period,
|
|
)
|
|
from odoo.addons.fusion_accounting_reports.services.account_hierarchy import (
|
|
build_tree, walk, filter_by_account_type,
|
|
)
|
|
from odoo.addons.fusion_accounting_reports.services.totaling import (
|
|
aggregate, aggregate_per_account, is_balanced,
|
|
)
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestDatePeriods(TransactionCase):
|
|
|
|
def test_fiscal_year_calendar_default(self):
|
|
period = fiscal_year_bounds(date(2026, 6, 15))
|
|
self.assertEqual(period.date_from, date(2026, 1, 1))
|
|
self.assertEqual(period.date_to, date(2026, 12, 31))
|
|
|
|
def test_fiscal_year_april_start(self):
|
|
period = fiscal_year_bounds(date(2026, 6, 15), fy_start_month=4)
|
|
self.assertEqual(period.date_from, date(2026, 4, 1))
|
|
self.assertEqual(period.date_to, date(2027, 3, 31))
|
|
|
|
def test_fiscal_year_before_start_returns_prior(self):
|
|
period = fiscal_year_bounds(date(2026, 2, 15), fy_start_month=4)
|
|
self.assertEqual(period.date_from, date(2025, 4, 1))
|
|
self.assertEqual(period.date_to, date(2026, 3, 31))
|
|
|
|
def test_month_bounds(self):
|
|
period = month_bounds(date(2026, 4, 19))
|
|
self.assertEqual(period.date_from, date(2026, 4, 1))
|
|
self.assertEqual(period.date_to, date(2026, 4, 30))
|
|
|
|
def test_month_bounds_december(self):
|
|
period = month_bounds(date(2026, 12, 19))
|
|
self.assertEqual(period.date_from, date(2026, 12, 1))
|
|
self.assertEqual(period.date_to, date(2026, 12, 31))
|
|
|
|
def test_quarter_bounds_q2(self):
|
|
period = quarter_bounds(date(2026, 5, 15))
|
|
self.assertEqual(period.date_from, date(2026, 4, 1))
|
|
self.assertEqual(period.date_to, date(2026, 6, 30))
|
|
|
|
def test_comparison_previous_year(self):
|
|
period = Period(date(2026, 1, 1), date(2026, 12, 31), 'FY 2026')
|
|
comp = comparison_period(period, 'previous_year')
|
|
self.assertEqual(comp.date_from, date(2025, 1, 1))
|
|
self.assertEqual(comp.date_to, date(2025, 12, 31))
|
|
|
|
def test_comparison_previous_period_same_length(self):
|
|
period = Period(date(2026, 4, 1), date(2026, 4, 30), 'Apr 2026')
|
|
comp = comparison_period(period, 'previous_period')
|
|
self.assertEqual(comp.date_to, date(2026, 3, 31))
|
|
self.assertEqual(comp.days, period.days)
|
|
|
|
def test_period_validates_bounds(self):
|
|
with self.assertRaises(ValueError):
|
|
Period(date(2026, 12, 31), date(2026, 1, 1), 'invalid')
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestAccountHierarchy(TransactionCase):
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.flat = [
|
|
{'id': 1, 'code': '1', 'name': 'Assets', 'account_type': 'asset_root', 'parent_id': None},
|
|
{'id': 2, 'code': '11', 'name': 'Cash', 'account_type': 'asset_cash', 'parent_id': 1},
|
|
{'id': 3, 'code': '12', 'name': 'AR', 'account_type': 'asset_receivable', 'parent_id': 1},
|
|
{'id': 4, 'code': '2', 'name': 'Liabilities', 'account_type': 'liability_root', 'parent_id': None},
|
|
{'id': 5, 'code': '21', 'name': 'AP', 'account_type': 'liability_payable', 'parent_id': 4},
|
|
]
|
|
|
|
def test_build_tree_returns_two_roots(self):
|
|
roots = build_tree(self.flat)
|
|
self.assertEqual(len(roots), 2)
|
|
|
|
def test_walk_yields_all_nodes(self):
|
|
roots = build_tree(self.flat)
|
|
ids = [n.id for n, _, _ in walk(roots)]
|
|
self.assertEqual(set(ids), {1, 2, 3, 4, 5})
|
|
|
|
def test_walk_depth_correct(self):
|
|
roots = build_tree(self.flat)
|
|
depths = {n.id: depth for n, depth, _ in walk(roots)}
|
|
self.assertEqual(depths[1], 0)
|
|
self.assertEqual(depths[2], 1)
|
|
self.assertEqual(depths[3], 1)
|
|
|
|
def test_filter_by_type_prefix(self):
|
|
roots = build_tree(self.flat)
|
|
assets = filter_by_account_type(roots, 'asset_')
|
|
self.assertEqual(len(assets), 3)
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestTotaling(TransactionCase):
|
|
|
|
def test_aggregate_empty(self):
|
|
result = aggregate([])
|
|
self.assertEqual(result.debit, 0.0)
|
|
self.assertEqual(result.line_count, 0)
|
|
|
|
def test_aggregate_simple(self):
|
|
lines = [
|
|
{'debit': 100, 'credit': 0, 'balance': 100, 'account_id': 1},
|
|
{'debit': 0, 'credit': 50, 'balance': -50, 'account_id': 1},
|
|
]
|
|
result = aggregate(lines)
|
|
self.assertEqual(result.debit, 100)
|
|
self.assertEqual(result.credit, 50)
|
|
self.assertEqual(result.balance, 50)
|
|
|
|
def test_aggregate_per_account_groups_correctly(self):
|
|
lines = [
|
|
{'debit': 100, 'credit': 0, 'balance': 100, 'account_id': 1},
|
|
{'debit': 50, 'credit': 0, 'balance': 50, 'account_id': 1},
|
|
{'debit': 0, 'credit': 25, 'balance': -25, 'account_id': 2},
|
|
]
|
|
result = aggregate_per_account(lines)
|
|
self.assertEqual(result[1].debit, 150)
|
|
self.assertEqual(result[2].credit, 25)
|
|
|
|
def test_is_balanced_true(self):
|
|
lines = [
|
|
{'debit': 100, 'credit': 0, 'balance': 100},
|
|
{'debit': 0, 'credit': 100, 'balance': -100},
|
|
]
|
|
self.assertTrue(is_balanced(lines))
|
|
|
|
def test_is_balanced_false(self):
|
|
lines = [
|
|
{'debit': 100, 'credit': 0, 'balance': 100},
|
|
{'debit': 0, 'credit': 50, 'balance': -50},
|
|
]
|
|
self.assertFalse(is_balanced(lines))
|