use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{money, money_from_value, money_to_value, Money}; const MAX_SAFE: i128 = 9_007_199_254_740_991; /// Retention on a valuation, the way JCT-style contracts release it. /// /// Retention is always worked out afresh on the cumulative value, never /// accumulated valuation by valuation, so a later valuation corrects an earlier /// one. At practical completion half the percentage applies (not half of a /// figure rounded earlier), and after the defects are made good none does. /// /// # Panics /// Panics on a percentage outside 0..=10000, a negative value, an unknown /// stage, or a product beyond 2^53 - 1. pub fn retention(cumulative_value: &Money, basis_points: i64, stage: &str) -> Retention { if !(0..=10000).contains(&basis_points) { panic!("basisPoints must be a whole number from 0 to 10000, received {}", basis_points); } if cumulative_value.minor < 0 { panic!("cumulativeValue must not be negative, received {}", cumulative_value.minor); } let product = cumulative_value.minor as i128 * basis_points as i128; // i64 could go further, but TypeScript cannot, and all three must agree. if product > MAX_SAFE { panic!("the retention calculation exceeds 2^53 - 1"); } let product = product as i64; let full = round_div(product, 10000, "half-up"); let held = match stage { "interim" => full, "practical-completion" => round_div(product, 20000, "half-up"), "final" => 0, other => panic!("unknown retention stage \"{}\"", other), }; let currency = &cumulative_value.currency; Retention { full: money(full, currency), held: money(held, currency), released: money(full - held, currency), } } pub fn retention_to_value(r: &Retention) -> Value { Value::obj(vec![ ("full", money_to_value(&r.full)), ("held", money_to_value(&r.held)), ("released", money_to_value(&r.released)), ]) } pub fn fune_vector(args: &[Value]) -> Value { if let Value::Float(f) = &args[1] { panic!("basisPoints must be a whole number from 0 to 10000, received {}", f); } retention_to_value(&retention( &money_from_value(&args[0]), args[1].as_i64(), args[2].as_str(), )) }