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::add_days; ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_day_of_week::day_of_week; ← from dates.day-of-week ^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_statutory_maternity_pay_data::{
SMP_LOWER_EARNINGS_LIMITS, SMP_LOWER_EARNINGS_LIMITS_HISTORY, SMP_RATES, SMP_RATES_HISTORY,
};
const SMP_WEEKS: i64 = 39;
const EARNINGS_RELATED_WEEKS: i64 = 6;
/// 90% of average weekly earnings (SSCBA 1992 s.166(1)(a)).
const EARNINGS_BASIS_POINTS: i64 = 9000;
fn lookup(rows: &[(&'static str, Option<&'static str>, i64)], history: &str, date: &str, what: &str) -> i64 {
for (from, to, value) in rows {
if date >= *from && to.map_or(true, |t| date <= t) {
return *value;
}
}
if history != "full" {
if let Some(earliest) = rows.iter().map(|r| r.0).min() {
if date < earliest {
panic!(
"no {} for {}: this build was installed with history={}, so it only carries rules from {}. Reinstall with history=full for older cases.",
what, date, history, earliest
);
}
}
}
panic!("no {} for {}", what, date)
}
/// Statutory Maternity Pay: whether it is due, and the 39 weekly amounts.
///
/// Two dates decide the figures and are easy to confuse. Eligibility uses the
/// lower earnings limit in force at the end of the qualifying week, which can
/// be in an earlier tax year than the pay; each week's flat rate is the one in
/// force on the day that SMP week starts.
///
/// # Panics
/// Panics on negative or non-GBP earnings, a malformed date, or a date no rule covers.
pub fn statutory_maternity_pay(average_weekly_earnings: &Money, due_date: &str, pay_start: &str) -> MaternityPay {
if average_weekly_earnings.currency != "GBP" {
panic!("SMP is paid in GBP, received {}", average_weekly_earnings.currency);
}
if average_weekly_earnings.minor < 0 {
panic!("averageWeeklyEarnings must not be negative");
}
let rates: Vec<(&'static str, Option<&'static str>, i64)> =
SMP_RATES.iter().map(|r| (r.valid_from, r.valid_to, r.weekly_rate)).collect();
let limits: Vec<(&'static str, Option<&'static str>, i64)> =
SMP_LOWER_EARNINGS_LIMITS.iter().map(|r| (r.valid_from, r.valid_to, r.weekly_limit)).collect();
// The expected week of childbirth runs Sunday to Saturday (SSCBA 1992 s.171(1)).
let expected_week = add_days(due_date, -(day_of_week(due_date) % 7));
let qualifying_week_start = add_days(&expected_week, -15 * 7);
add_days(pay_start, 0);
let lel = lookup(&limits, SMP_LOWER_EARNINGS_LIMITS_HISTORY, &add_days(&qualifying_week_start, 6), "lower earnings limit");
let eligible = average_weekly_earnings.minor >= lel;
let mut weeks: Vec<MaternityPayWeek> = Vec::new();
let mut total = 0i64;
if eligible {
// Kept in hundredths of a penny: HMRC says not to round average weekly
// earnings, and reg 28 rounds only the payment, up.
let earnings_rate = average_weekly_earnings.minor * EARNINGS_BASIS_POINTS;
for week in 1..=SMP_WEEKS {
let start = add_days(pay_start, 7 * (week - 1));
let mut rate = earnings_rate;
if week > EARNINGS_RELATED_WEEKS {
rate = rate.min(lookup(&rates, SMP_RATES_HISTORY, &start, "SMP rate") * 10000);
}
let amount = round_div(rate, 10000, "up");
total += amount;
let end = add_days(&start, 6);
weeks.push(MaternityPayWeek { week, start, end, amount: money(amount, "GBP") });
}
}
MaternityPay {
eligible,
qualifying_week_start,
lower_earnings_limit: money(lel, "GBP"),
weeks,
total: money(total, "GBP"),
}
}
pub fn maternity_pay_to_value(pay: &MaternityPay) -> Value {
Value::obj(vec![
("eligible", Value::Bool(pay.eligible)),
("qualifyingWeekStart", Value::str(&pay.qualifying_week_start)),
("lowerEarningsLimit", money_to_value(&pay.lower_earnings_limit)),
(
"weeks",
Value::Arr(
pay.weeks
.iter()
.map(|w| {
Value::obj(vec![
("week", Value::Int(w.week)),
("start", Value::str(&w.start)),
("end", Value::str(&w.end)),
("amount", money_to_value(&w.amount)),
])
})
.collect(),
),
),
("total", money_to_value(&pay.total)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
maternity_pay_to_value(&statutory_maternity_pay(&money_from_value(&args[0]), args[1].as_str(), args[2].as_str()))
}