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, Money}; ← from money.amount ^1.0.0 · built alongside by fune
use super::time_round_to_increment::round_to_increment; ← from time.round-to-increment ^1.0.0 · built alongside by fune
/// Price time entries in whole billing units at each fee earner's hourly rate.
///
/// # Panics
/// Panics on an empty, duplicated, negative or mixed-currency rate card, an
/// entry with no rate, negative minutes or a unit below one.
pub fn time_billing(entries: &[TimeEntry], rates: &[FeeEarnerRate], unit_minutes: i64, mode: &str) -> TimeBill {
if rates.is_empty() {
panic!("rates must not be empty");
}
let currency = rates[0].hourly_rate.currency.clone();
let mut card: Vec<(&str, &Money)> = Vec::new();
for rate in rates {
if card.iter().any(|(code, _)| *code == rate.fee_earner) {
panic!("duplicate rate for fee earner \"{}\"", rate.fee_earner);
}
if rate.hourly_rate.currency != currency {
panic!("currency mismatch: {} and {}", currency, rate.hourly_rate.currency);
}
if rate.hourly_rate.minor < 0 {
panic!("hourly rates must not be negative, received {}", rate.hourly_rate.minor);
}
card.push((&rate.fee_earner, &rate.hourly_rate));
}
let mut lines: Vec<TimeBillLine> = Vec::new();
let mut totals: Vec<(String, i64, i64)> = Vec::new();
let mut units = 0;
let mut billed_minutes = 0;
let mut total = 0;
for entry in entries {
let hourly_rate = match card.iter().find(|(code, _)| *code == entry.fee_earner) {
Some((_, rate)) => (*rate).clone(),
None => panic!("no rate for fee earner \"{}\"", entry.fee_earner),
};
// Rounded per entry: seven 1-minute emails are seven units, not two.
let billed = round_to_increment(entry.minutes, unit_minutes, mode);
let line_units = billed / unit_minutes;
let fee = round_div(billed * hourly_rate.minor, 60, "half-up");
match totals.iter_mut().find(|t| t.0 == entry.fee_earner) {
Some(t) => {
t.1 += line_units;
t.2 += fee;
}
None => totals.push((entry.fee_earner.clone(), line_units, fee)),
}
lines.push(TimeBillLine {
fee_earner: entry.fee_earner.clone(),
minutes: entry.minutes,
billed_minutes: billed,
units: line_units,
hourly_rate,
fee: money(fee, ¤cy),
});
units += line_units;
billed_minutes += billed;
total += fee;
}
let by_fee_earner = totals
.into_iter()
.map(|(code, u, fee)| FeeEarnerTotal { fee_earner: code, units: u, fee: money(fee, ¤cy) })
.collect();
TimeBill { lines, by_fee_earner, units, billed_minutes, total: money(total, ¤cy) }
}
pub fn time_entry_from_value(v: &Value) -> TimeEntry {
TimeEntry { fee_earner: v.get("feeEarner").as_str().to_string(), minutes: v.get("minutes").as_i64() }
}
pub fn fee_earner_rate_from_value(v: &Value) -> FeeEarnerRate {
FeeEarnerRate { fee_earner: v.get("feeEarner").as_str().to_string(), hourly_rate: money_from_value(v.get("hourlyRate")) }
}
pub fn time_bill_to_value(b: &TimeBill) -> Value {
Value::obj(vec![
(
"lines",
Value::Arr(
b.lines
.iter()
.map(|l| {
Value::obj(vec![
("feeEarner", Value::str(&l.fee_earner)),
("minutes", Value::Int(l.minutes)),
("billedMinutes", Value::Int(l.billed_minutes)),
("units", Value::Int(l.units)),
("hourlyRate", money_to_value(&l.hourly_rate)),
("fee", money_to_value(&l.fee)),
])
})
.collect(),
),
),
(
"byFeeEarner",
Value::Arr(
b.by_fee_earner
.iter()
.map(|t| {
Value::obj(vec![
("feeEarner", Value::str(&t.fee_earner)),
("units", Value::Int(t.units)),
("fee", money_to_value(&t.fee)),
])
})
.collect(),
),
),
("units", Value::Int(b.units)),
("billedMinutes", Value::Int(b.billed_minutes)),
("total", money_to_value(&b.total)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let entries: Vec<TimeEntry> = args[0].as_arr().iter().map(time_entry_from_value).collect();
let rates: Vec<FeeEarnerRate> = args[1].as_arr().iter().map(fee_earner_rate_from_value).collect();
time_bill_to_value(&time_billing(&entries, &rates, args[2].as_i64(), args[3].as_str()))
}