feat(fusion_accounting_assets): salvage_value service
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Assets',
|
||||
'version': '19.0.1.0.2',
|
||||
'version': '19.0.1.0.3',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||
'description': """
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
from . import depreciation_methods
|
||||
from . import prorate
|
||||
from . import salvage_value
|
||||
|
||||
38
fusion_accounting_assets/services/salvage_value.py
Normal file
38
fusion_accounting_assets/services/salvage_value.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Salvage value (scrap value) calculation helpers.
|
||||
|
||||
Most clients use straight % of cost; some use fixed dollar amounts.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
|
||||
SalvageMethod = Literal['percentage', 'fixed', 'zero']
|
||||
|
||||
|
||||
@dataclass
|
||||
class SalvageConfig:
|
||||
method: SalvageMethod
|
||||
value: float = 0.0
|
||||
|
||||
|
||||
def compute_salvage_value(*, cost: float, config: SalvageConfig) -> float:
|
||||
"""Compute end-of-life salvage value."""
|
||||
if config.method == 'zero':
|
||||
return 0.0
|
||||
if config.method == 'percentage':
|
||||
return round(cost * config.value / 100, 2)
|
||||
if config.method == 'fixed':
|
||||
return round(config.value, 2)
|
||||
raise ValueError(f"Unknown salvage method: {config.method}")
|
||||
|
||||
|
||||
def remaining_useful_life_value(*, current_book: float, salvage: float,
|
||||
periods_used: int, total_periods: int) -> float:
|
||||
"""Estimate remaining value if asset is sold/scrapped now."""
|
||||
if total_periods <= 0:
|
||||
return current_book
|
||||
if periods_used >= total_periods:
|
||||
return salvage
|
||||
remaining_pct = (total_periods - periods_used) / total_periods
|
||||
return round(salvage + (current_book - salvage) * remaining_pct, 2)
|
||||
@@ -1,2 +1,3 @@
|
||||
from . import test_depreciation_methods
|
||||
from . import test_prorate
|
||||
from . import test_salvage_value
|
||||
|
||||
45
fusion_accounting_assets/tests/test_salvage_value.py
Normal file
45
fusion_accounting_assets/tests/test_salvage_value.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.fusion_accounting_assets.services.salvage_value import (
|
||||
SalvageConfig, compute_salvage_value, remaining_useful_life_value,
|
||||
)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSalvageValue(TransactionCase):
|
||||
|
||||
def test_zero_method_returns_zero(self):
|
||||
v = compute_salvage_value(cost=10000, config=SalvageConfig(method='zero'))
|
||||
self.assertEqual(v, 0.0)
|
||||
|
||||
def test_percentage_method(self):
|
||||
v = compute_salvage_value(
|
||||
cost=10000, config=SalvageConfig(method='percentage', value=10),
|
||||
)
|
||||
self.assertAlmostEqual(v, 1000.0, places=2)
|
||||
|
||||
def test_fixed_method(self):
|
||||
v = compute_salvage_value(
|
||||
cost=10000, config=SalvageConfig(method='fixed', value=750),
|
||||
)
|
||||
self.assertAlmostEqual(v, 750.0, places=2)
|
||||
|
||||
def test_unknown_method_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
compute_salvage_value(
|
||||
cost=10000,
|
||||
config=SalvageConfig(method='bogus', value=0), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def test_remaining_useful_life_value_midway(self):
|
||||
# Halfway through life; current book 6000, salvage 1000 -> 1000 + 5000*0.5 = 3500
|
||||
v = remaining_useful_life_value(
|
||||
current_book=6000, salvage=1000, periods_used=5, total_periods=10,
|
||||
)
|
||||
self.assertAlmostEqual(v, 3500.0, places=2)
|
||||
|
||||
def test_remaining_useful_life_value_at_end_returns_salvage(self):
|
||||
v = remaining_useful_life_value(
|
||||
current_book=1200, salvage=1000, periods_used=10, total_periods=10,
|
||||
)
|
||||
self.assertEqual(v, 1000.0)
|
||||
Reference in New Issue
Block a user