use super::funejson::Value; use super::money_amount::{money, money_from_value, Money}; use super::payroll_gross_to_net::{gross_to_net, payslip_input_from_value, payslip_to_value}; 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]), )) }