Functional Weave
Code in Rust

finance.depreciation-straight-line@1.0.0

impl/rust.rs

3,909 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_add::subtract_money;  ← from money.add ^1.0.0 · built alongside by fune
use super::money_allocate::allocate;  ← from money.allocate ^1.0.0 · built alongside by fune
use super::money_amount::{money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

/// 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.
///
/// # Panics
/// Panics if `years` is below 1, either amount is negative, the residual value
/// exceeds the cost, or the two amounts are in different currencies.
pub fn straight_line_depreciation(
    cost: &Money,
    residual_value: &Money,
    years: i64,
) -> Vec<DepreciationPeriod> {
    if years < 1 {
        panic!("years must be at least 1, received {}", years);
    }
    if cost.minor < 0 {
        panic!("cost must not be negative");
    }
    if residual_value.minor < 0 {
        panic!("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.
    let depreciable = subtract_money(cost, residual_value);
    if depreciable.minor < 0 {
        panic!("residual value must not exceed cost");
    }

    let ratios = vec![1i64; years as usize];
    let charges = allocate(&depreciable, &ratios);

    let mut schedule: Vec<DepreciationPeriod> = Vec::with_capacity(years as usize);
    let mut opening_value = cost.clone();
    for (index, charge) in charges.iter().enumerate() {
        let closing_value = subtract_money(&opening_value, charge);
        schedule.push(DepreciationPeriod {
            year: index as i64 + 1,
            opening_value: opening_value.clone(),
            charge: charge.clone(),
            closing_value: closing_value.clone(),
        });
        opening_value = closing_value;
    }
    schedule
}

/// The written-down value after `elapsed_years` complete years.
///
/// # Panics
/// Panics if `elapsed_years` is outside `0..=years`.
pub fn written_down_value(
    cost: &Money,
    residual_value: &Money,
    years: i64,
    elapsed_years: i64,
) -> Money {
    let schedule = straight_line_depreciation(cost, residual_value, years);
    if !(0..=years).contains(&elapsed_years) {
        panic!(
            "elapsed_years must be between 0 and {}, received {}",
            years, elapsed_years
        );
    }
    if elapsed_years == 0 {
        cost.clone()
    } else {
        schedule[elapsed_years as usize - 1].closing_value.clone()
    }
}

pub fn depreciation_period_to_value(period: &DepreciationPeriod) -> Value {
    Value::obj(vec![
        ("year", Value::Int(period.year)),
        ("openingValue", money_to_value(&period.opening_value)),
        ("charge", money_to_value(&period.charge)),
        ("closingValue", money_to_value(&period.closing_value)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    // Refuse what the typed signature cannot hold, with the wording TypeScript
    // and Python use, rather than let the conversion below quietly change it.
    if let Value::Float(f) = args[2] {
        if f.fract() != 0.0 {
            panic!("years must be at least 1, received {}", f);
        }
    }
    Value::Arr(
        straight_line_depreciation(
            &money_from_value(&args[0]),
            &money_from_value(&args[1]),
            args[2].as_i64(),
        )
        .iter()
        .map(depreciation_period_to_value)
        .collect(),
    )
}