use super::funejson::Value; use super::money_allocate::allocate; use super::money_amount::{money, money_from_value, money_to_value, Money}; fn weight_of(member: &TroncMember, method: &str) -> i64 { match method { "hours" => member.minutes_worked, "points" => member.points, "hours-and-points" => member.minutes_worked * member.points, other => panic!("unknown tronc method \"{}\"", other), } } /// Share a tips pool between staff in proportion to a weight. /// /// Time is in minutes so a 7.5-hour shift is exact. The pool is split with /// money.allocate, so the shares add up to the pool exactly: the leftover /// pennies go to the largest remainders, ties to whoever is listed first. /// /// # Panics /// Panics on an empty pool of staff, a negative pool, duplicate ids, negative /// minutes or points, an unknown method, or a pool nobody has weight in. pub fn tronc_allocation(pool: &Money, staff: &[TroncMember], method: &str) -> Vec { if staff.is_empty() { panic!("staff must have at least one member"); } if pool.minor < 0 { panic!("pool must not be negative, received {}", pool.minor); } let mut ids: Vec<&str> = Vec::new(); for member in staff { if member.id.is_empty() { panic!("every member needs an id"); } if ids.contains(&member.id.as_str()) { panic!("staff id \"{}\" appears twice", member.id); } ids.push(&member.id); if member.minutes_worked < 0 { panic!( "minutesWorked must not be negative, received {} for \"{}\"", member.minutes_worked, member.id ); } if member.points < 0 { panic!("points must not be negative, received {} for \"{}\"", member.points, member.id); } } let weights: Vec = staff.iter().map(|m| weight_of(m, method)).collect(); if weights.iter().sum::() == 0 { if pool.minor != 0 { panic!("nobody in the pool has any weight to share it by"); } return staff .iter() .map(|m| TroncShare { id: m.id.clone(), weight: 0, share: money(0, &pool.currency) }) .collect(); } let shares = allocate(pool, &weights); staff .iter() .zip(weights.iter().zip(shares)) .map(|(m, (w, s))| TroncShare { id: m.id.clone(), weight: *w, share: s }) .collect() } pub fn tronc_member_from_value(v: &Value) -> TroncMember { TroncMember { id: v.get("id").as_str().to_string(), minutes_worked: v.get("minutesWorked").as_i64(), points: v.get("points").as_i64(), } } pub fn tronc_share_to_value(s: &TroncShare) -> Value { Value::obj(vec![ ("id", Value::str(&s.id)), ("weight", Value::Int(s.weight)), ("share", money_to_value(&s.share)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let staff: Vec = args[1].as_arr().iter().map(tronc_member_from_value).collect(); Value::Arr( tronc_allocation(&money_from_value(&args[0]), &staff, args[2].as_str()) .iter() .map(tronc_share_to_value) .collect(), ) }