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::epoch_day_from_iso; ← from dates.add-days ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value}; ← from money.amount ^1.0.0 · built alongside by fune
/// A claim's position after each transaction: paid, recovered, the
/// outstanding case reserve, and incurred = paid - recovered + outstanding.
///
/// A payment erodes the outstanding reserve by what it pays, never below
/// zero: a payment larger than the reserve raises incurred by the excess,
/// which is how an under-reserved claim shows up. A reserve transaction sets
/// the outstanding estimate outright. The movement is what each transaction
/// did to incurred, the figure that goes through the accounts.
///
/// # Panics
/// Panics on transactions out of date order, another currency, a negative
/// reserve, a payment or recovery that is not positive, or an unknown kind.
pub fn claim_reserve_history(transactions: &[ClaimTransaction], currency: &str) -> Vec<ReservePosition> {
let zero = money(0, currency);
let (mut paid, mut recovered, mut outstanding, mut incurred) = (0i64, 0i64, 0i64, 0i64);
let mut previous: Option<(i64, &str)> = None;
let mut out = Vec::new();
for t in transactions {
let day = epoch_day_from_iso(&t.date);
if let Some((previous_day, previous_date)) = previous {
if day < previous_day {
panic!("transactions must be in date order: {} comes after {}", t.date, previous_date);
}
}
previous = Some((day, &t.date));
assert_same_currency(&zero, &t.amount);
match t.kind.as_str() {
"reserve" => {
if t.amount.minor < 0 {
panic!("a reserve must not be negative, received {} on {}", t.amount.minor, t.date);
}
outstanding = t.amount.minor;
}
"payment" | "recovery" => {
if t.amount.minor <= 0 {
panic!("a {} must be more than zero, received {} on {}", t.kind, t.amount.minor, t.date);
}
if t.kind == "payment" {
paid += t.amount.minor;
outstanding = (outstanding - t.amount.minor).max(0);
} else {
recovered += t.amount.minor;
}
}
other => panic!("unknown transaction kind \"{}\": use reserve, payment or recovery", other),
}
let now = paid - recovered + outstanding;
out.push(ReservePosition {
date: t.date.clone(),
kind: t.kind.clone(),
paid: money(paid, currency),
recovered: money(recovered, currency),
outstanding: money(outstanding, currency),
incurred: money(now, currency),
movement: money(now - incurred, currency),
});
incurred = now;
}
out
}
pub fn reserve_position_to_value(p: &ReservePosition) -> Value {
Value::obj(vec![
("date", Value::str(&p.date)),
("kind", Value::str(&p.kind)),
("paid", money_to_value(&p.paid)),
("recovered", money_to_value(&p.recovered)),
("outstanding", money_to_value(&p.outstanding)),
("incurred", money_to_value(&p.incurred)),
("movement", money_to_value(&p.movement)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let transactions: Vec<ClaimTransaction> = args[0]
.as_arr()
.iter()
.map(|v| ClaimTransaction {
date: v.get("date").as_str().to_string(),
kind: v.get("kind").as_str().to_string(),
amount: money_from_value(v.get("amount")),
})
.collect();
Value::Arr(
claim_reserve_history(&transactions, args[1].as_str())
.iter()
.map(reserve_position_to_value)
.collect(),
)
}