152 lines
6.1 KiB
Python
152 lines
6.1 KiB
Python
"""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)
|