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_amount::{money, money_from_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
use super::payroll_gross_to_net::{gross_to_net, payslip_input_from_value, payslip_to_value}; ← from payroll.gross-to-net ^1.0.0 · built alongside by fune
use super::payroll_gross_to_net_types::{Payslip, PayslipInput};
/// The search refuses to look above £10,000,000 of gross pay for one period.
const CEILING: i64 = 1_000_000_000;
/// Gross up a net payment: the gross pay whose payslip nets at least the target.
///
/// Net pay is not a smooth function of gross: tax is on whole pounds, student
/// loans are rounded down to whole pounds, and pension and NI thresholds
/// switch on. So there is no formula to invert, and a search is the honest
/// method. It is a bisection on whole pence, with fixed bounds and midpoints,
/// so it takes the same steps and finds the same answer in every language: a
/// gross G whose net is at least the target while the net at G - 1p is below it.
///
/// # Panics
/// Panics on a non-GBP or non-positive target, when no gross up to the ceiling
/// is enough, or on anything payroll.gross-to-net refuses.
pub fn net_to_gross(target_net: &Money, input: &PayslipInput) -> Payslip {
if target_net.currency != "GBP" {
panic!(
"payroll amounts must be in GBP, received {} for targetNet",
target_net.currency
);
}
if target_net.minor <= 0 {
panic!("targetNet must be more than zero, received {}", target_net.minor);
}
let slip = |gross: i64| -> Payslip {
let mut with_gross = input.clone();
with_gross.gross = money(gross, "GBP");
gross_to_net(&with_gross)
};
// A refund can make net exceed gross, so zero gross may already be enough.
let at_zero = slip(0);
if at_zero.net.minor >= target_net.minor {
return at_zero;
}
let mut low = 0i64;
let mut high = target_net.minor;
while slip(high).net.minor < target_net.minor {
low = high;
high *= 2;
if high > CEILING {
panic!(
"no gross pay up to {} pence gives a net of {} pence",
CEILING, target_net.minor
);
}
}
while high - low > 1 {
let middle = (low + high) / 2;
if slip(middle).net.minor >= target_net.minor {
high = middle;
} else {
low = middle;
}
}
slip(high)
}
pub fn fune_vector(args: &[Value]) -> Value {
payslip_to_value(&net_to_gross(
&money_from_value(&args[0]),
&payslip_input_from_value(&args[1]),
))
}