Functional Weave
Code in Rust

hospitality.tronc-allocation@1.0.0

impl/rust.rs

3,239 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_allocate::allocate;  ← from money.allocate ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

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<TroncShare> {
    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<i64> = staff.iter().map(|m| weight_of(m, method)).collect();
    if weights.iter().sum::<i64>() == 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<TroncMember> = 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(),
    )
}