feat(fusion_accounting_assets): salvage_value service
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
'name': 'Fusion Accounting Assets',
|
'name': 'Fusion Accounting Assets',
|
||||||
'version': '19.0.1.0.2',
|
'version': '19.0.1.0.3',
|
||||||
'category': 'Accounting/Accounting',
|
'category': 'Accounting/Accounting',
|
||||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||||
'description': """
|
'description': """
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
from . import depreciation_methods
|
from . import depreciation_methods
|
||||||
from . import prorate
|
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_depreciation_methods
|
||||||
from . import test_prorate
|
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