test(fusion_accounting_assets): engine integration tests for full lifecycle

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 17:06:55 -04:00
parent 54922a0b32
commit 345c971d59
3 changed files with 153 additions and 1 deletions

View File

@@ -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': """

View File

@@ -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

View File

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