Functional Weave
Code in TypeScript

professional.wip-valuation@1.0.0

impl/rust.rs

2,563 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::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value};  ← from money.amount ^1.0.0 · built alongside by fune
use super::money_apply_rate::apply_rate;  ← from money.apply-rate ^1.0.0 · built alongside by fune

/// 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<WipEntry> = 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()))
}