use super::funejson::Value; use super::money_add::add_money; use super::money_amount::{money_from_value, money_to_value, Money}; use super::money_apply_rate::apply_rate; use super::money_sum::sum_money; /// A discretionary service charge on the eligible lines of a bill. /// /// The rate is applied once, to the eligible total, and rounded once. Charging /// each line and adding the pennies up drifts: three 3.33 lines at 12.5% are /// 0.42 each, 1.26 in all, where 12.5% of 9.99 is 1.25. /// /// # Panics /// Panics on an empty bill, a rate outside 0 to 10000, mixed currencies or a /// negative eligible total. pub fn service_charge(lines: &[BillLine], basis_points: i64, mode: &str) -> ServiceCharge { if lines.is_empty() { panic!("a bill needs at least one line"); } if !(0..=10000).contains(&basis_points) { panic!("basisPoints must be a whole number from 0 to 10000, received {}", basis_points); } let currency = lines[0].amount.currency.clone(); let all: Vec = lines.iter().map(|l| l.amount.clone()).collect(); let eligible: Vec = lines.iter().filter(|l| l.eligible).map(|l| l.amount.clone()).collect(); let subtotal = sum_money(&all, ¤cy); let eligible_total = sum_money(&eligible, ¤cy); if eligible_total.minor < 0 { panic!( "the eligible lines total {}, and a service charge cannot be negative", eligible_total.minor ); } let charge = apply_rate(&eligible_total, basis_points, mode); ServiceCharge { total: add_money(&subtotal, &charge), eligible_total, basis_points, charge, subtotal, } } pub fn bill_line_from_value(v: &Value) -> BillLine { BillLine { description: v.get("description").as_str().to_string(), amount: money_from_value(v.get("amount")), eligible: v.get("eligible").as_bool(), } } pub fn service_charge_to_value(s: &ServiceCharge) -> Value { Value::obj(vec![ ("eligibleTotal", money_to_value(&s.eligible_total)), ("basisPoints", Value::Int(s.basis_points)), ("charge", money_to_value(&s.charge)), ("subtotal", money_to_value(&s.subtotal)), ("total", money_to_value(&s.total)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let lines: Vec = args[0].as_arr().iter().map(bill_line_from_value).collect(); service_charge_to_value(&service_charge(&lines, args[1].as_i64(), args[2].as_str())) }