From 345c971d59a6fa37f82524a8c2ee17b601f17626 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 17:06:55 -0400 Subject: [PATCH] test(fusion_accounting_assets): engine integration tests for full lifecycle Made-with: Cursor --- fusion_accounting_assets/__manifest__.py | 2 +- fusion_accounting_assets/tests/__init__.py | 1 + .../tests/test_engine_integration.py | 151 ++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 fusion_accounting_assets/tests/test_engine_integration.py diff --git a/fusion_accounting_assets/__manifest__.py b/fusion_accounting_assets/__manifest__.py index 2fcb0486..429a476b 100644 --- a/fusion_accounting_assets/__manifest__.py +++ b/fusion_accounting_assets/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting Assets', - 'version': '19.0.1.0.12', + 'version': '19.0.1.0.13', 'category': 'Accounting/Accounting', 'summary': 'AI-augmented asset management with depreciation schedules.', 'description': """ diff --git a/fusion_accounting_assets/tests/__init__.py b/fusion_accounting_assets/tests/__init__.py index a9b35b10..d44b52b8 100644 --- a/fusion_accounting_assets/tests/__init__.py +++ b/fusion_accounting_assets/tests/__init__.py @@ -10,3 +10,4 @@ from . import test_fusion_asset_disposal from . import test_fusion_asset_anomaly from . import test_account_move_inherit from . import test_fusion_asset_engine +from . import test_engine_integration diff --git a/fusion_accounting_assets/tests/test_engine_integration.py b/fusion_accounting_assets/tests/test_engine_integration.py new file mode 100644 index 00000000..2627033a --- /dev/null +++ b/fusion_accounting_assets/tests/test_engine_integration.py @@ -0,0 +1,151 @@ +"""End-to-end engine integration tests. + +Each test creates a complete realistic asset (with category and accounts), +runs the engine through a full lifecycle, and asserts both the model state +and the journal entries (where category accounts are configured). +""" + +from datetime import date + +from odoo.tests.common import TransactionCase +from odoo.tests import tagged +from odoo.exceptions import ValidationError + + +@tagged('post_install', '-at_install', 'integration') +class TestAssetEngineIntegration(TransactionCase): + + def setUp(self): + super().setUp() + self.engine = self.env['fusion.asset.engine'] + Account = self.env['account.account'] + company_id = self.env.company.id + self.expense_account = Account.search([ + ('account_type', '=', 'expense_depreciation'), + ('company_ids', 'in', company_id), + ], limit=1) + if not self.expense_account: + self.expense_account = Account.create({ + 'name': 'Test Depreciation Expense', + 'code': '7180', + 'account_type': 'expense_depreciation', + 'company_ids': [(6, 0, [company_id])], + }) + self.dep_account = Account.search([ + ('account_type', '=', 'asset_fixed'), + ('company_ids', 'in', company_id), + ], limit=1) + if not self.dep_account: + self.dep_account = Account.create({ + 'name': 'Test Accumulated Depreciation', + 'code': '1690', + 'account_type': 'asset_fixed', + 'company_ids': [(6, 0, [company_id])], + }) + self.category = self.env['fusion.asset.category'].create({ + 'name': 'Test Category', + 'method': 'straight_line', + 'useful_life_years': 5, + 'asset_account_id': self.dep_account.id, + 'depreciation_account_id': self.dep_account.id, + 'expense_account_id': self.expense_account.id, + }) + + def _make_asset(self, **kwargs): + defaults = { + 'name': 'Integration Asset', + 'cost': 12000, + 'salvage_value': 0, + 'acquisition_date': date(2026, 1, 1), + 'in_service_date': date(2026, 1, 1), + 'method': 'straight_line', + 'useful_life_years': 4, + 'category_id': self.category.id, + } + defaults.update(kwargs) + return self.env['fusion.asset'].create(defaults) + + def test_full_lifecycle_straight_line(self): + asset = self._make_asset() + self.engine.compute_depreciation_schedule(asset) + self.assertEqual(len(asset.depreciation_line_ids), 4) + self.assertAlmostEqual( + sum(asset.depreciation_line_ids.mapped('amount')), 12000, places=2, + ) + + asset.action_set_running() + for _i in range(2): + result = self.engine.post_depreciation_entry(asset) + self.assertEqual(result['posted_count'], 1) + asset.invalidate_recordset(['book_value', 'total_depreciated']) + self.assertAlmostEqual(asset.total_depreciated, 6000, places=2) + + def test_post_creates_journal_entry_when_accounts_configured(self): + asset = self._make_asset() + self.engine.compute_depreciation_schedule(asset) + asset.action_set_running() + self.engine.post_depreciation_entry(asset) + first = asset.depreciation_line_ids.sorted('period_index')[0] + self.assertTrue(first.move_id, "Expected journal entry on posted line") + moves = first.move_id + self.assertAlmostEqual( + sum(moves.line_ids.mapped('debit')), + sum(moves.line_ids.mapped('credit')), + places=2, + ) + + def test_dispose_caps_future_lines(self): + asset = self._make_asset() + self.engine.compute_depreciation_schedule(asset) + asset.action_set_running() + self.engine.post_depreciation_entry(asset) + self.engine.dispose_asset( + asset, sale_amount=5000, sale_date=date(2027, 6, 1), + ) + self.assertEqual(asset.state, 'disposed') + unposted = asset.depreciation_line_ids.filtered(lambda l: not l.is_posted) + for line in unposted: + self.assertLessEqual(line.scheduled_date, date(2027, 6, 1)) + + def test_dispose_records_correct_book_value(self): + asset = self._make_asset() + self.engine.compute_depreciation_schedule(asset) + asset.action_set_running() + for _i in range(2): + self.engine.post_depreciation_entry(asset) + result = self.engine.dispose_asset( + asset, sale_amount=8000, sale_date=date(2028, 6, 1), + ) + # Book value at disposal = cost - accumulated = 12000 - 6000 = 6000. + self.assertAlmostEqual(result['book_value_at_disposal'], 6000, places=2) + # Gain = 8000 - 6000 = 2000. + self.assertAlmostEqual(result['gain_loss_amount'], 2000, places=2) + + def test_partial_sale_30pct(self): + asset = self._make_asset(cost=10000, salvage_value=0) + self.engine.compute_depreciation_schedule(asset) + asset.action_set_running() + result = self.engine.partial_sale( + asset, sold_amount=3500, sold_qty=0.3, + sale_date=date(2027, 1, 1), + ) + asset.invalidate_recordset(['cost']) + self.assertAlmostEqual(asset.cost, 7000, places=2) + child = self.env['fusion.asset'].browse(result['child_asset_id']) + self.assertAlmostEqual(child.cost, 3000, places=2) + self.assertEqual(child.state, 'disposed') + # Child has no posted depreciation; book_value at disposal = 3000. + # Gain = 3500 - 3000 = 500. + self.assertAlmostEqual(result['gain_loss_amount'], 500, places=0) + + def test_pause_then_resume_lifecycle(self): + asset = self._make_asset() + self.engine.compute_depreciation_schedule(asset) + asset.action_set_running() + self.engine.post_depreciation_entry(asset) + self.engine.pause_asset(asset) + with self.assertRaises(ValidationError): + self.engine.post_depreciation_entry(asset) + self.engine.resume_asset(asset) + result = self.engine.post_depreciation_entry(asset) + self.assertEqual(result['posted_count'], 1)