use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{money, money_from_value, money_to_value, Money}; use super::money_currency_digits::currency_digits; 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); } } /// Cost to raise £1 and the share of spending that went on the charity's /// purposes. Each ratio is divided once from the integer amounts and rounded /// half up. /// /// # Panics /// Panics on negative amounts, mixed currencies, zero funds raised or total /// expenditure, or charitable spend above total expenditure. pub fn fundraising_ratios( fundraising_costs: &Money, funds_raised: &Money, charitable_spend: &Money, total_expenditure: &Money, ) -> FundraisingRatios { let currency = fundraising_costs.currency.as_str(); check("fundraisingCosts", currency, fundraising_costs); check("fundsRaised", currency, funds_raised); check("charitableSpend", currency, charitable_spend); check("totalExpenditure", currency, total_expenditure); if funds_raised.minor == 0 { panic!("fundsRaised must be positive to work out a cost to raise"); } if total_expenditure.minor == 0 { panic!("totalExpenditure must be positive to work out a charitable spend ratio"); } if charitable_spend.minor > total_expenditure.minor { panic!( "charitableSpend of {} is more than totalExpenditure of {}", charitable_spend.minor, total_expenditure.minor ); } let unit = 10i64.pow(currency_digits(currency) as u32); FundraisingRatios { cost_to_raise_one: money(round_div(fundraising_costs.minor * unit, funds_raised.minor, "half-up"), currency), cost_ratio_basis_points: round_div(fundraising_costs.minor * 10000, funds_raised.minor, "half-up"), charitable_spend_basis_points: round_div(charitable_spend.minor * 10000, total_expenditure.minor, "half-up"), } } pub fn fundraising_ratios_to_value(r: &FundraisingRatios) -> Value { Value::obj(vec![ ("costToRaiseOne", money_to_value(&r.cost_to_raise_one)), ("costRatioBasisPoints", Value::Int(r.cost_ratio_basis_points)), ("charitableSpendBasisPoints", Value::Int(r.charitable_spend_basis_points)), ]) } pub fn fune_vector(args: &[Value]) -> Value { fundraising_ratios_to_value(&fundraising_ratios( &money_from_value(&args[0]), &money_from_value(&args[1]), &money_from_value(&args[2]), &money_from_value(&args[3]), )) }