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::money_amount::{money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
use super::money_apply_rate::apply_rate; ← from money.apply-rate ^1.0.0 · built alongside by fune
use super::money_sum::sum_money; ← from money.sum ^1.0.0 · built alongside by fune
/// The cancellation fee a policy sets for the notice given.
///
/// Notice is counted in calendar days from the day of cancelling to the day of
/// arrival, so cancelling on 4 December for an 18 December arrival is 14 days.
/// The band with the most notice that the cancellation still meets applies. A
/// cancellation on or after the arrival day is 0 days' notice, the no-show band.
///
/// # Panics
/// Panics on an empty or malformed policy, no nights, a bad date, mixed
/// currencies, or when no band covers the notice given.
pub fn cancellation_charge(
policy: &[CancellationBand],
nightly_rates: &[Money],
arrival: &str,
cancelled_on: &str,
) -> CancellationCharge {
if policy.is_empty() {
panic!("policy must have at least one band");
}
if nightly_rates.is_empty() {
panic!("nightlyRates must have at least one night");
}
let mut seen: Vec<i64> = Vec::new();
for band in policy {
if band.min_days_notice < 0 {
panic!("minDaysNotice must not be negative, received {}", band.min_days_notice);
}
if seen.contains(&band.min_days_notice) {
panic!("policy has two bands for {} days' notice", band.min_days_notice);
}
seen.push(band.min_days_notice);
if let Some(n) = band.nights {
if n < 1 {
panic!("nights must be at least 1 or null for the whole stay, received {}", n);
}
}
if !(0..=10000).contains(&band.basis_points) {
panic!(
"basisPoints must be a whole number from 0 to 10000, received {}",
band.basis_points
);
}
}
let days_notice = days_between(cancelled_on, arrival);
let effective = days_notice.max(0);
let mut chosen: Option<&CancellationBand> = None;
for band in policy {
if band.min_days_notice <= effective
&& chosen.map_or(true, |c| band.min_days_notice > c.min_days_notice)
{
chosen = Some(band);
}
}
let chosen = match chosen {
Some(c) => c,
None => panic!("no band in the policy covers {} days' notice", effective),
};
let stay = nightly_rates.len() as i64;
let charged_nights = match chosen.nights {
None => stay,
Some(n) => n.min(stay),
};
let currency = nightly_rates[0].currency.clone();
let charge_base = sum_money(&nightly_rates[..charged_nights as usize], ¤cy);
// The whole stay is checked for one currency, not only the nights charged.
sum_money(nightly_rates, ¤cy);
CancellationCharge {
days_notice,
min_days_notice: chosen.min_days_notice,
charged_nights,
charge: apply_rate(&charge_base, chosen.basis_points, "half-up"),
charge_base,
basis_points: chosen.basis_points,
}
}
pub fn cancellation_band_from_value(v: &Value) -> CancellationBand {
CancellationBand {
min_days_notice: v.get("minDaysNotice").as_i64(),
nights: if v.get("nights").is_null() { None } else { Some(v.get("nights").as_i64()) },
basis_points: v.get("basisPoints").as_i64(),
}
}
pub fn cancellation_charge_to_value(c: &CancellationCharge) -> Value {
Value::obj(vec![
("daysNotice", Value::Int(c.days_notice)),
("minDaysNotice", Value::Int(c.min_days_notice)),
("chargedNights", Value::Int(c.charged_nights)),
("chargeBase", money_to_value(&c.charge_base)),
("basisPoints", Value::Int(c.basis_points)),
("charge", money_to_value(&c.charge)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let policy: Vec<CancellationBand> = args[0].as_arr().iter().map(cancellation_band_from_value).collect();
let rates: Vec<Money> = args[1].as_arr().iter().map(money_from_value).collect();
cancellation_charge_to_value(&cancellation_charge(&policy, &rates, args[2].as_str(), args[3].as_str()))
}