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_days_between::days_between; ← from dates.days-between ^1.0.0 · built alongside by fune
use super::finance_proration::prorate; ← from finance.proration ^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
/// A buyer's completion statement. Each outgoing is split at the end of the
/// completion day (the seller owns the property until then) by
/// finance.proration, so the two shares always add back to the charge.
///
/// # Panics
/// Panics on mixed currencies, a negative price, charge or fee, a deposit
/// outside 0..=price, a malformed date, or a period that ends before it starts.
pub fn completion_statement(
price: &Money,
deposit_paid: &Money,
completion_date: &str,
apportionments: &[PeriodCharge],
fees: &[FeeLine],
) -> CompletionStatement {
let currency = price.currency.as_str();
let same = |m: &Money, what: &str| {
if m.currency != currency {
panic!("currency mismatch: {} is {}, the price is {}", what, m.currency, currency);
}
};
same(deposit_paid, "depositPaid");
if price.minor < 0 {
panic!("price must not be negative, received {}", price.minor);
}
if deposit_paid.minor < 0 || deposit_paid.minor > price.minor {
panic!("depositPaid must be between 0 and the price, received {}", deposit_paid.minor);
}
let mut balance = price.minor - deposit_paid.minor;
let mut lines: Vec<ApportionmentLine> = Vec::new();
for charge in apportionments {
same(&charge.amount, &format!("\"{}\"", charge.label));
if charge.amount.minor < 0 {
panic!("\"{}\" must not be negative, received {}", charge.label, charge.amount.minor);
}
let total_days = days_between(&charge.period_start, &charge.period_end) + 1;
if total_days < 1 {
panic!(
"\"{}\": periodEnd {} is before periodStart {}",
charge.label, charge.period_end, charge.period_start
);
}
// The seller owns the property to the end of the completion day.
let up_to_completion = days_between(&charge.period_start, completion_date) + 1;
let seller_days = total_days.min(up_to_completion.max(0));
let split = prorate(&charge.amount, total_days, seller_days);
let adjustment = if charge.paid_by_seller { split.unused.minor } else { -split.used.minor };
balance += adjustment;
lines.push(ApportionmentLine {
label: charge.label.clone(),
seller_days,
buyer_days: total_days - seller_days,
adjustment: money(adjustment, currency),
});
}
let mut fees_total = 0i64;
let mut fee_lines: Vec<FeeLine> = Vec::new();
for fee in fees {
same(&fee.amount, &format!("\"{}\"", fee.label));
if fee.amount.minor < 0 {
panic!("\"{}\" must not be negative, received {}", fee.label, fee.amount.minor);
}
fees_total += fee.amount.minor;
fee_lines.push(FeeLine { label: fee.label.clone(), amount: money(fee.amount.minor, currency) });
}
CompletionStatement {
price: money(price.minor, currency),
deposit_paid: money(deposit_paid.minor, currency),
apportionments: lines,
balance_to_seller: money(balance, currency),
fees: fee_lines,
fees_total: money(fees_total, currency),
total_due: money(balance + fees_total, currency),
}
}
pub fn period_charge_from_value(v: &Value) -> PeriodCharge {
PeriodCharge {
label: v.get("label").as_str().to_string(),
amount: money_from_value(v.get("amount")),
period_start: v.get("periodStart").as_str().to_string(),
period_end: v.get("periodEnd").as_str().to_string(),
paid_by_seller: v.get("paidBySeller").as_bool(),
}
}
pub fn fee_line_from_value(v: &Value) -> FeeLine {
FeeLine { label: v.get("label").as_str().to_string(), amount: money_from_value(v.get("amount")) }
}
fn fee_line_to_value(fee: &FeeLine) -> Value {
Value::obj(vec![("label", Value::str(&fee.label)), ("amount", money_to_value(&fee.amount))])
}
pub fn completion_statement_to_value(s: &CompletionStatement) -> Value {
Value::obj(vec![
("price", money_to_value(&s.price)),
("depositPaid", money_to_value(&s.deposit_paid)),
(
"apportionments",
Value::Arr(
s.apportionments
.iter()
.map(|l| {
Value::obj(vec![
("label", Value::str(&l.label)),
("sellerDays", Value::Int(l.seller_days)),
("buyerDays", Value::Int(l.buyer_days)),
("adjustment", money_to_value(&l.adjustment)),
])
})
.collect(),
),
),
("balanceToSeller", money_to_value(&s.balance_to_seller)),
("fees", Value::Arr(s.fees.iter().map(fee_line_to_value).collect())),
("feesTotal", money_to_value(&s.fees_total)),
("totalDue", money_to_value(&s.total_due)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let charges: Vec<PeriodCharge> = args[3].as_arr().iter().map(period_charge_from_value).collect();
let fees: Vec<FeeLine> = args[4].as_arr().iter().map(fee_line_from_value).collect();
completion_statement_to_value(&completion_statement(
&money_from_value(&args[0]),
&money_from_value(&args[1]),
args[2].as_str(),
&charges,
&fees,
))
}