use super::funejson::Value; use super::money_amount::{money, money_from_value, money_to_value, Money}; use super::money_apply_rate::apply_rate; fn check(name: &str, currency: &str, amount: &Money) { if amount.currency != currency { panic!("currency mismatch: {} and {}", amount.currency, currency); } if amount.minor < 0 { panic!("{} must not be negative, received {}", name, amount.minor); } } /// An employer's matched-giving contribution. The ratio is applied first and /// rounded down; then the match is held to whatever room is left under the /// donor's cap and the programme's budget, never below zero. /// /// # Panics /// Panics on a negative amount or ratio, or mixed currencies. pub fn donation_match( donation: &Money, ratio_basis_points: i64, donor_cap: Option<&Money>, donor_matched_so_far: &Money, programme_cap: Option<&Money>, programme_matched_so_far: &Money, ) -> DonationMatch { let currency = donation.currency.as_str(); check("donation", currency, donation); if ratio_basis_points < 0 { panic!("ratioBasisPoints must be a non-negative integer, received {}", ratio_basis_points); } if let Some(cap) = donor_cap { check("donorCap", currency, cap); } check("donorMatchedSoFar", currency, donor_matched_so_far); if let Some(cap) = programme_cap { check("programmeCap", currency, cap); } check("programmeMatchedSoFar", currency, programme_matched_so_far); let uncapped = apply_rate(donation, ratio_basis_points, "down").minor; let mut matched = uncapped; let mut capped_by = "none"; if let Some(cap) = donor_cap { let room = (cap.minor - donor_matched_so_far.minor).max(0); if room < matched { matched = room; capped_by = "donor"; } } if let Some(cap) = programme_cap { let room = (cap.minor - programme_matched_so_far.minor).max(0); if room < matched { matched = room; capped_by = "programme"; } } DonationMatch { matched: money(matched, currency), uncapped: money(uncapped, currency), capped_by: capped_by.to_string(), } } pub fn donation_match_to_value(m: &DonationMatch) -> Value { Value::obj(vec![ ("matched", money_to_value(&m.matched)), ("uncapped", money_to_value(&m.uncapped)), ("cappedBy", Value::str(&m.capped_by)), ]) } pub fn fune_vector(args: &[Value]) -> Value { if let Value::Float(f) = args[1] { if f.fract() != 0.0 { panic!("ratioBasisPoints must be a non-negative integer, received {}", f); } } let donor_cap = if args[2].is_null() { None } else { Some(money_from_value(&args[2])) }; let programme_cap = if args[4].is_null() { None } else { Some(money_from_value(&args[4])) }; donation_match_to_value(&donation_match( &money_from_value(&args[0]), args[1].as_i64(), donor_cap.as_ref(), &money_from_value(&args[3]), programme_cap.as_ref(), &money_from_value(&args[5]), )) }