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::dates_add_days::parse_iso_date; ← from dates.add-days ^1.0.0 · built alongside by fune
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::payroll_holiday_pay_accrual_data::{HolidayPayRule, HOLIDAY_PAY_RULES, HOLIDAY_PAY_RULES_HISTORY}; ← this capability’s own data, compiled from data/holiday-pay-rules.json into the same file by fune build
fn rule_for(method: &str, on_date: &str) -> &'static HolidayPayRule {
let mut best: Option<&'static HolidayPayRule> = None;
for rule in HOLIDAY_PAY_RULES {
if rule.method != method || on_date < rule.valid_from {
continue;
}
if let Some(to) = rule.valid_to {
if on_date > to {
continue;
}
}
if best.map_or(true, |b| rule.valid_from > b.valid_from) {
best = Some(rule);
}
}
match best {
Some(rule) => rule,
None => {
if HOLIDAY_PAY_RULES_HISTORY != "full" {
panic!(
"no {} holiday pay rule for {}: this build was installed with history={}. Reinstall with history=full for older dates.",
method, on_date, HOLIDAY_PAY_RULES_HISTORY
);
}
panic!("no {} holiday pay rule for {}", method, on_date)
}
}
}
/// Holiday pay under the Working Time Regulations 1998, by one of two methods.
///
/// Rolled-up: a 12.07% uplift on pay for work done, paid with it, allowed only
/// for irregular-hours and part-year workers in leave years from 1 April 2024.
/// Average week: a week's pay as the average over the most recent 52 weeks in
/// which the worker was paid, skipping unpaid weeks but never looking back more
/// than 104 weeks.
///
/// # Panics
/// Panics on negative pay, mixed currencies, no paid week, a malformed date,
/// an unknown method, or a date no rule covers.
pub fn holiday_pay(method: &str, period_pay: &Money, weekly_pay: &[Money], on_date: &str) -> HolidayPay {
parse_iso_date(on_date);
if method == "rolled-up" {
let rule = rule_for(method, on_date);
if period_pay.minor < 0 {
panic!("periodPay must not be negative");
}
return HolidayPay {
amount: money(round_div(period_pay.minor * rule.accrual_basis_points, 10000, "half-up"), &period_pay.currency),
weeks_counted: 0,
};
}
if method != "average-week" {
panic!("unknown holiday pay method \"{}\"", method);
}
let rule = rule_for(method, on_date);
let mut total = 0i64;
let mut counted = 0i64;
let mut currency: Option<String> = None;
let window = weekly_pay.len().min(rule.lookback_weeks as usize);
for week in &weekly_pay[..window] {
if counted >= rule.reference_weeks {
break;
}
match ¤cy {
None => currency = Some(week.currency.clone()),
Some(c) => {
if *c != week.currency {
panic!("currency mismatch: {} and {}", c, week.currency);
}
}
}
if week.minor < 0 {
panic!("weekly pay must not be negative");
}
if week.minor == 0 {
continue;
}
total += week.minor;
counted += 1;
}
match currency {
Some(c) if counted > 0 => HolidayPay { amount: money(round_div(total, counted, "half-up"), &c), weeks_counted: counted },
_ => panic!("no week with pay in the last {} weeks", rule.lookback_weeks),
}
}
pub fn holiday_pay_to_value(pay: &HolidayPay) -> Value {
Value::obj(vec![
("amount", money_to_value(&pay.amount)),
("weeksCounted", Value::Int(pay.weeks_counted)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let weeks: Vec<Money> = args[2].as_arr().iter().map(money_from_value).collect();
holiday_pay_to_value(&holiday_pay(args[0].as_str(), &money_from_value(&args[1]), &weeks, args[3].as_str()))
}