from typing import List from .finance_depreciation_straight_line_types import DepreciationPeriod from .money_add import subtract_money from .money_allocate import allocate from .money_amount import Money def straight_line_depreciation(cost: Money, residual_value: Money, years: int) -> List[DepreciationPeriod]: """A straight-line depreciation schedule, year by year. The depreciable amount is split with ``money.allocate`` instead of being divided by the number of years. Dividing leaves 1000.00 over three years as 333.33 three times - a penny short - and an asset that never quite finishes writing down to its residual value. ``allocate`` hands out every minor unit, so the last closing value equals the residual value exactly, by construction rather than by a fudge at the end. The odd units go to the earliest years, following ``allocate``'s largest- remainder rule rather than a second rule invented here. """ if isinstance(years, bool) or not isinstance(years, int) or years < 1: raise ValueError("years must be at least 1, received %r" % (years,)) if cost.minor < 0: raise ValueError("cost must not be negative") if residual_value.minor < 0: raise ValueError("residual value must not be negative") # subtract_money also rejects a cost and a residual value in different # currencies, which would otherwise produce a plausible-looking schedule. depreciable = subtract_money(cost, residual_value) if depreciable.minor < 0: raise ValueError("residual value must not exceed cost") charges = allocate(depreciable, [1] * years) schedule: List[DepreciationPeriod] = [] opening_value = cost for index in range(years): charge = charges[index] closing_value = subtract_money(opening_value, charge) schedule.append( DepreciationPeriod( year=index + 1, opening_value=opening_value, charge=charge, closing_value=closing_value, ) ) opening_value = closing_value return schedule def written_down_value(cost: Money, residual_value: Money, years: int, elapsed_years: int) -> Money: """The written-down value after ``elapsed_years`` complete years.""" schedule = straight_line_depreciation(cost, residual_value, years) if isinstance(elapsed_years, bool) or not isinstance(elapsed_years, int) or not 0 <= elapsed_years <= years: raise ValueError("elapsed_years must be between 0 and %d, received %r" % (years, elapsed_years)) return cost if elapsed_years == 0 else schedule[elapsed_years - 1].closing_value