Files
Odoo-Modules/fusion_accounting_assets/tests/test_fusion_asset_engine.py
gsinghpal 54922a0b32 feat(fusion_accounting_assets): fusion.asset.engine 7-method API
The orchestrator AbstractModel for asset depreciation lifecycle.
compute_depreciation_schedule, post_depreciation_entry, dispose_asset,
partial_sale, pause_asset, resume_asset, reverse_disposal.

All controllers, AI tools, wizards, and cron must route through these
methods; no direct ORM writes to fusion.asset.depreciation.line or
account.move from anywhere else.

Made-with: Cursor
2026-04-19 17:06:12 -04:00

116 lines
4.9 KiB
Python

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')
class TestFusionAssetEngine(TransactionCase):
def setUp(self):
super().setUp()
self.engine = self.env['fusion.asset.engine']
self.asset = self.env['fusion.asset'].create({
'name': 'Test Engine Asset',
'cost': 10000,
'salvage_value': 1000,
'acquisition_date': date(2026, 1, 1),
'in_service_date': date(2026, 1, 1),
'method': 'straight_line',
'useful_life_years': 5,
})
def test_engine_model_exists(self):
self.assertIn('fusion.asset.engine', self.env.registry)
def test_compute_schedule_straight_line(self):
result = self.engine.compute_depreciation_schedule(self.asset)
self.assertEqual(result['lines_created'], 5)
lines = self.asset.depreciation_line_ids
self.assertEqual(len(lines), 5)
# Total depreciation should equal cost - salvage = 9000
total = sum(lines.mapped('amount'))
self.assertAlmostEqual(total, 9000, places=2)
def test_compute_schedule_declining_balance(self):
self.asset.write({'method': 'declining_balance', 'declining_rate_pct': 30.0})
self.engine.compute_depreciation_schedule(self.asset)
lines = self.asset.depreciation_line_ids
self.assertGreater(len(lines), 0)
# First-period amount should be cost * rate = 10000 * 0.3 = 3000
first = lines.sorted('period_index')[0]
self.assertAlmostEqual(first.amount, 3000, places=2)
def test_compute_schedule_recompute_wipes_unposted(self):
self.engine.compute_depreciation_schedule(self.asset)
self.asset.write({'useful_life_years': 8})
self.engine.compute_depreciation_schedule(self.asset, recompute=True)
self.assertEqual(len(self.asset.depreciation_line_ids), 8)
def test_compute_schedule_validates_zero_cost(self):
# Bypass DB constraint with sudo + the constraint allows cost >= 0,
# but engine validation requires cost > 0.
bad = self.env['fusion.asset'].create({
'name': 'Zero',
'cost': 0,
'acquisition_date': date(2026, 1, 1),
'method': 'straight_line',
'useful_life_years': 5,
})
with self.assertRaises(ValidationError):
self.engine.compute_depreciation_schedule(bad)
def test_post_depreciation_entry_marks_line_posted(self):
self.engine.compute_depreciation_schedule(self.asset)
self.asset.action_set_running()
result = self.engine.post_depreciation_entry(self.asset)
self.assertEqual(result['posted_count'], 1)
first_line = self.asset.depreciation_line_ids.sorted('period_index')[0]
self.assertTrue(first_line.is_posted)
def test_post_depreciation_only_after_running(self):
self.engine.compute_depreciation_schedule(self.asset)
# asset is still in 'draft' state
with self.assertRaises(ValidationError):
self.engine.post_depreciation_entry(self.asset)
def test_dispose_asset_creates_disposal_record(self):
self.engine.compute_depreciation_schedule(self.asset)
self.asset.action_set_running()
result = self.engine.dispose_asset(
self.asset, sale_amount=5000, sale_date=date(2027, 6, 1),
)
self.assertEqual(self.asset.state, 'disposed')
self.assertIn('disposal_id', result)
self.assertEqual(result['book_value_at_disposal'], self.asset.book_value)
def test_partial_sale_creates_child_and_disposes(self):
self.engine.compute_depreciation_schedule(self.asset)
self.asset.action_set_running()
original_cost = self.asset.cost
result = self.engine.partial_sale(
self.asset, sold_amount=3000, sold_qty=0.3,
sale_date=date(2027, 6, 1),
)
self.assertIn('parent_asset_id', result)
self.assertIn('child_asset_id', result)
self.asset.invalidate_recordset(['cost'])
expected_remaining = round(original_cost * 0.7, 2)
self.assertAlmostEqual(self.asset.cost, expected_remaining, places=2)
def test_pause_resume_round_trip(self):
self.asset.action_set_running()
self.engine.pause_asset(self.asset)
self.assertEqual(self.asset.state, 'paused')
self.engine.resume_asset(self.asset)
self.assertEqual(self.asset.state, 'running')
def test_reverse_disposal_restores_running_state(self):
self.engine.compute_depreciation_schedule(self.asset)
self.asset.action_set_running()
self.engine.dispose_asset(self.asset, sale_amount=5000)
self.assertEqual(self.asset.state, 'disposed')
self.engine.reverse_disposal(self.asset)
self.assertEqual(self.asset.state, 'running')