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
use super::money_split_even::split_even; ← from money.split-even ^1.0.0 · built alongside by fune
use super::money_sum::sum_money; ← from money.sum ^1.0.0 · built alongside by fune
/// Split a bill between diners so that the shares add up to the bill exactly.
///
/// even and by-share divide the whole bill (items plus service) by weight, so
/// even totals never differ by more than a penny. by-item gives each diner what
/// they ordered, with shared items split evenly between the people who shared
/// them. In every method the service charge is then shared in proportion to
/// what each diner pays for, so nobody pays service on someone else's steak.
///
/// # Panics
/// Panics on a bad diner count, method, share list or diner number, negative
/// amounts, mixed currencies, or a service charge on a bill with no items.
pub fn bill_split(
items: &[BillItem],
service_charge: &Money,
diners: i64,
method: &str,
shares: &[i64],
) -> Vec<DinerShare> {
if diners < 1 {
panic!("diners must be at least 1, received {}", diners);
}
if method != "even" && method != "by-item" && method != "by-share" {
panic!("unknown split method \"{}\"", method);
}
if service_charge.minor < 0 {
panic!("serviceCharge must not be negative, received {}", service_charge.minor);
}
for item in items {
if item.amount.minor < 0 {
panic!(
"item amounts must not be negative, received {} for \"{}\"",
item.amount.minor, item.description
);
}
}
let currency = service_charge.currency.clone();
let amounts: Vec<Money> = items.iter().map(|i| i.amount.clone()).collect();
let items_total = sum_money(&amounts, ¤cy);
let n = diners as usize;
if method == "by-share" {
if shares.len() != n {
panic!(
"shares must have one entry per diner: {} diners, {} shares",
diners,
shares.len()
);
}
for s in shares {
if *s < 0 {
panic!("shares must not be negative, received {}", s);
}
}
if shares.iter().all(|s| *s == 0) {
panic!("shares must not all be zero");
}
} else if !shares.is_empty() {
panic!("shares are only used by the by-share method; pass []");
}
let item_shares: Vec<i64>;
let sc_shares: Vec<i64>;
if method == "by-item" {
let mut acc = vec![0i64; n];
for item in items {
let who: Vec<i64> = if item.diners.is_empty() { (0..diners).collect() } else { item.diners.clone() };
for (i, d) in who.iter().enumerate() {
if *d < 0 || *d >= diners {
panic!("diner {} is not at the table: diners are numbered 0 to {}", d, diners - 1);
}
if who.iter().position(|x| x == d) != Some(i) {
panic!("item \"{}\" lists diner {} twice", item.description, d);
}
}
let parts = split_even(&item.amount, who.len() as i64);
for (i, d) in who.iter().enumerate() {
acc[*d as usize] += parts[i].minor;
}
}
if items_total.minor == 0 {
if service_charge.minor != 0 {
panic!("a service charge cannot be shared on a bill with no items");
}
sc_shares = vec![0; n];
} else {
sc_shares = allocate(service_charge, &acc).iter().map(|m| m.minor).collect();
}
item_shares = acc;
} else {
let weights: Vec<i64> = if method == "even" { vec![1; n] } else { shares.to_vec() };
let grand = money(items_total.minor + service_charge.minor, ¤cy);
let totals: Vec<i64> = allocate(&grand, &weights).iter().map(|m| m.minor).collect();
sc_shares = if grand.minor == 0 {
vec![0; n]
} else {
allocate(service_charge, &totals).iter().map(|m| m.minor).collect()
};
item_shares = totals.iter().zip(sc_shares.iter()).map(|(t, s)| t - s).collect();
}
(0..n)
.map(|d| DinerShare {
diner: d as i64,
items: money(item_shares[d], ¤cy),
service_charge: money(sc_shares[d], ¤cy),
total: money(item_shares[d] + sc_shares[d], ¤cy),
})
.collect()
}
pub fn bill_item_from_value(v: &Value) -> BillItem {
BillItem {
description: v.get("description").as_str().to_string(),
amount: money_from_value(v.get("amount")),
diners: v.get("diners").as_arr().iter().map(|d| d.as_i64()).collect(),
}
}
pub fn diner_share_to_value(s: &DinerShare) -> Value {
Value::obj(vec![
("diner", Value::Int(s.diner)),
("items", money_to_value(&s.items)),
("serviceCharge", money_to_value(&s.service_charge)),
("total", money_to_value(&s.total)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let items: Vec<BillItem> = args[0].as_arr().iter().map(bill_item_from_value).collect();
let shares: Vec<i64> = args[4].as_arr().iter().map(|s| s.as_i64()).collect();
Value::Arr(
bill_split(&items, &money_from_value(&args[1]), args[2].as_i64(), args[3].as_str(), &shares)
.iter()
.map(diner_share_to_value)
.collect(),
)
}