Functional Weave
Code in TypeScript

hospitality.service-charge@1.0.0

impl/rust.rs

2,499 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_add::add_money;  ← from money.add ^1.0.0 · built alongside by fune
use super::money_amount::{money_from_value, money_to_value, Money};  ← 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
use super::money_sum::sum_money;  ← from money.sum ^1.0.0 · built alongside by fune

/// A discretionary service charge on the eligible lines of a bill.
///
/// The rate is applied once, to the eligible total, and rounded once. Charging
/// each line and adding the pennies up drifts: three 3.33 lines at 12.5% are
/// 0.42 each, 1.26 in all, where 12.5% of 9.99 is 1.25.
///
/// # Panics
/// Panics on an empty bill, a rate outside 0 to 10000, mixed currencies or a
/// negative eligible total.
pub fn service_charge(lines: &[BillLine], basis_points: i64, mode: &str) -> ServiceCharge {
    if lines.is_empty() {
        panic!("a bill needs at least one line");
    }
    if !(0..=10000).contains(&basis_points) {
        panic!("basisPoints must be a whole number from 0 to 10000, received {}", basis_points);
    }
    let currency = lines[0].amount.currency.clone();
    let all: Vec<Money> = lines.iter().map(|l| l.amount.clone()).collect();
    let eligible: Vec<Money> = lines.iter().filter(|l| l.eligible).map(|l| l.amount.clone()).collect();
    let subtotal = sum_money(&all, &currency);
    let eligible_total = sum_money(&eligible, &currency);
    if eligible_total.minor < 0 {
        panic!(
            "the eligible lines total {}, and a service charge cannot be negative",
            eligible_total.minor
        );
    }
    let charge = apply_rate(&eligible_total, basis_points, mode);
    ServiceCharge {
        total: add_money(&subtotal, &charge),
        eligible_total,
        basis_points,
        charge,
        subtotal,
    }
}

pub fn bill_line_from_value(v: &Value) -> BillLine {
    BillLine {
        description: v.get("description").as_str().to_string(),
        amount: money_from_value(v.get("amount")),
        eligible: v.get("eligible").as_bool(),
    }
}

pub fn service_charge_to_value(s: &ServiceCharge) -> Value {
    Value::obj(vec![
        ("eligibleTotal", money_to_value(&s.eligible_total)),
        ("basisPoints", Value::Int(s.basis_points)),
        ("charge", money_to_value(&s.charge)),
        ("subtotal", money_to_value(&s.subtotal)),
        ("total", money_to_value(&s.total)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let lines: Vec<BillLine> = args[0].as_arr().iter().map(bill_line_from_value).collect();
    service_charge_to_value(&service_charge(&lines, args[1].as_i64(), args[2].as_str()))
}