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(),
)
}