"""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)