test(fusion_accounting_assets): engine integration tests for full lifecycle
Made-with: Cursor
This commit is contained in:
@@ -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': """
|
||||
|
||||
@@ -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
|
||||
|
||||
151
fusion_accounting_assets/tests/test_engine_integration.py
Normal file
151
fusion_accounting_assets/tests/test_engine_integration.py
Normal 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)
|
||||
Reference in New Issue
Block a user