use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{money_from_value, Money}; fn ratio(numerator: i64, denominator: i64) -> Option { // A ratio over nothing is not 0% or 100%: None says there was nothing to realise. if denominator == 0 { None } else { Some(round_div(numerator * 10000, denominator, "half-up")) } } fn check_minutes(name: &str, value: i64) { if value < 0 { panic!("{} must be a non-negative integer, received {}", name, value); } } /// Utilisation and billing, collection and overall realisation, in basis points. /// /// # Panics /// Panics on negative minutes, zero available time, negative amounts or mixed currencies. pub fn utilisation_rates( billable_minutes: i64, available_minutes: i64, standard_value: &Money, billed_value: &Money, collected_value: &Money, ) -> UtilisationRates { check_minutes("billableMinutes", billable_minutes); check_minutes("availableMinutes", available_minutes); if available_minutes == 0 { panic!("availableMinutes must be greater than zero"); } for (name, amount) in [("standardValue", standard_value), ("billedValue", billed_value), ("collectedValue", collected_value)] { if amount.currency != standard_value.currency { panic!("currency mismatch: {} and {}", standard_value.currency, amount.currency); } if amount.minor < 0 { panic!("{} must not be negative, received {}", name, amount.minor); } } UtilisationRates { utilisation_basis_points: round_div(billable_minutes * 10000, available_minutes, "half-up"), billing_realisation_basis_points: ratio(billed_value.minor, standard_value.minor), collection_realisation_basis_points: ratio(collected_value.minor, billed_value.minor), overall_realisation_basis_points: ratio(collected_value.minor, standard_value.minor), } } fn optional_int(value: Option) -> Value { value.map_or(Value::Null, Value::Int) } pub fn utilisation_rates_to_value(r: &UtilisationRates) -> Value { Value::obj(vec![ ("utilisationBasisPoints", Value::Int(r.utilisation_basis_points)), ("billingRealisationBasisPoints", optional_int(r.billing_realisation_basis_points)), ("collectionRealisationBasisPoints", optional_int(r.collection_realisation_basis_points)), ("overallRealisationBasisPoints", optional_int(r.overall_realisation_basis_points)), ]) } pub fn fune_vector(args: &[Value]) -> Value { utilisation_rates_to_value(&utilisation_rates( args[0].as_i64(), args[1].as_i64(), &money_from_value(&args[2]), &money_from_value(&args[3]), &money_from_value(&args[4]), )) }