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::dates_add_days::parse_iso_date; ← from dates.add-days ^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, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
/// Split a fee into dated instalments that add up to it exactly.
///
/// The split is money.allocate's: floors, then the leftover pennies to the
/// largest remainders, ties to the earlier instalment.
///
/// # Panics
/// Panics on a negative total, mismatched or empty lists, negative or all-zero
/// shares, or due dates that are malformed or not strictly ascending.
pub fn fee_instalments(total: &Money, shares: &[i64], due_dates: &[String]) -> Vec<FeeInstalment> {
if total.minor < 0 {
panic!("total must not be negative, received {}", total.minor);
}
if shares.is_empty() {
panic!("shares must name at least one instalment");
}
if shares.len() != due_dates.len() {
panic!(
"shares and dueDates must be the same length, received {} and {}",
shares.len(),
due_dates.len()
);
}
for &s in shares {
if s < 0 {
panic!("each share must be a whole number of 0 or more, received {}", s);
}
}
if shares.iter().sum::<i64>() == 0 {
panic!("shares must not all be zero");
}
for i in 0..due_dates.len() {
parse_iso_date(&due_dates[i]);
if i > 0 && due_dates[i] <= due_dates[i - 1] {
panic!(
"dueDates must be in strictly ascending order: {} follows {}",
due_dates[i],
due_dates[i - 1]
);
}
}
let amounts = allocate(total, shares);
let mut paid = 0;
amounts
.into_iter()
.enumerate()
.map(|(i, amount)| {
paid += amount.minor;
FeeInstalment {
number: i as i64 + 1,
due_date: due_dates[i].clone(),
amount,
cumulative: money(paid, &total.currency),
remaining: money(total.minor - paid, &total.currency),
}
})
.collect()
}
pub fn fee_instalment_to_value(f: &FeeInstalment) -> Value {
Value::obj(vec![
("number", Value::Int(f.number)),
("dueDate", Value::str(&f.due_date)),
("amount", money_to_value(&f.amount)),
("cumulative", money_to_value(&f.cumulative)),
("remaining", money_to_value(&f.remaining)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let shares: Vec<i64> = args[1]
.as_arr()
.iter()
.map(|v| match v {
Value::Float(f) if f.fract() != 0.0 => panic!("each share must be a whole number of 0 or more, received {}", f),
other => other.as_i64(),
})
.collect();
let dates: Vec<String> = args[2].as_arr().iter().map(|v| v.as_str().to_string()).collect();
Value::Arr(
fee_instalments(&money_from_value(&args[0]), &shares, &dates)
.iter()
.map(fee_instalment_to_value)
.collect(),
)
}