use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value, Money}; /// Usage against a bundle: remaining allowance, overage in whole blocks priced /// and limited to an optional cap, and the share used, rounded down so a /// bundle with anything left never reads 100%. /// /// # Panics /// Panics on negative quantities or prices, a block below 1, or a cap in /// another currency. pub fn data_allowance(allowance: i64, used: i64, overage_block: i64, price_per_block: &Money, overage_cap: Option<&Money>) -> DataAllowance { if allowance < 0 || used < 0 { panic!( "allowance and usage must be whole numbers of 0 or more, received {} and {}", allowance, used ); } if overage_block < 1 { panic!("overage block must be 1 or more, received {}", overage_block); } if price_per_block.minor < 0 { panic!("price per block must not be negative, received {}", price_per_block.minor); } if let Some(cap) = overage_cap { assert_same_currency(price_per_block, cap); if cap.minor < 0 { panic!("overage cap must not be negative, received {}", cap.minor); } } let overage = (used - allowance).max(0); let overage_blocks = round_div(overage, overage_block, "up"); let uncapped = overage_blocks * price_per_block.minor; let cap_minor = overage_cap.map(|c| c.minor); let capped = matches!(cap_minor, Some(c) if uncapped > c); DataAllowance { used, remaining: (allowance - used).max(0), overage, overage_blocks, overage_charge: money( if capped { cap_minor.unwrap() } else { uncapped }, &price_per_block.currency, ), capped, percent_used_basis_points: if allowance == 0 { None } else { Some(round_div(used * 10000, allowance, "down")) }, } } pub fn data_allowance_to_value(d: &DataAllowance) -> Value { Value::obj(vec![ ("used", Value::Int(d.used)), ("remaining", Value::Int(d.remaining)), ("overage", Value::Int(d.overage)), ("overageBlocks", Value::Int(d.overage_blocks)), ("overageCharge", money_to_value(&d.overage_charge)), ("capped", Value::Bool(d.capped)), ( "percentUsedBasisPoints", match d.percent_used_basis_points { Some(n) => Value::Int(n), None => Value::Null, }, ), ]) } pub fn fune_vector(args: &[Value]) -> Value { let cap = if args[4].is_null() { None } else { Some(money_from_value(&args[4])) }; data_allowance_to_value(&data_allowance( args[0].as_i64(), args[1].as_i64(), args[2].as_i64(), &money_from_value(&args[3]), cap.as_ref(), )) }