use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{money, money_from_value, money_to_value}; use super::money_apply_rate::apply_rate; /// Work in progress at cost or charge-out rates, less a write-down. /// /// # Panics /// Panics on an unknown basis, a write-down outside 0..=10000, negative /// minutes or rates, or a rate in another currency. pub fn wip_valuation(entries: &[WipEntry], basis: &str, write_down_basis_points: i64, currency: &str) -> WipValuation { if basis != "cost" && basis != "charge-out" { panic!("unknown WIP basis \"{}\": expected cost or charge-out", basis); } if !(0..=10000).contains(&write_down_basis_points) { panic!("writeDownBasisPoints must be between 0 and 10000, received {}", write_down_basis_points); } let mut minutes = 0; let mut gross = 0; for entry in entries { if entry.minutes < 0 { panic!("minutes must be a non-negative integer, received {}", entry.minutes); } let rate = if basis == "cost" { &entry.cost_rate } else { &entry.charge_rate }; if rate.currency != currency { panic!("currency mismatch: {} and {}", currency, rate.currency); } if rate.minor < 0 { panic!("hourly rates must not be negative, received {}", rate.minor); } minutes += entry.minutes; // Per entry, as the time would be billed, so WIP reconciles with the bill. gross += round_div(entry.minutes * rate.minor, 60, "half-up"); } let gross_money = money(gross, currency); let write_down = apply_rate(&gross_money, write_down_basis_points, "half-up"); let net = money(gross - write_down.minor, currency); WipValuation { minutes, gross: gross_money, write_down, net } } pub fn wip_entry_from_value(v: &Value) -> WipEntry { WipEntry { minutes: v.get("minutes").as_i64(), cost_rate: money_from_value(v.get("costRate")), charge_rate: money_from_value(v.get("chargeRate")), } } pub fn wip_valuation_to_value(w: &WipValuation) -> Value { Value::obj(vec![ ("minutes", Value::Int(w.minutes)), ("gross", money_to_value(&w.gross)), ("writeDown", money_to_value(&w.write_down)), ("net", money_to_value(&w.net)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let entries: Vec = args[0].as_arr().iter().map(wip_entry_from_value).collect(); wip_valuation_to_value(&wip_valuation(&entries, args[1].as_str(), args[2].as_i64(), args[3].as_str())) }