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_to_value}; ← from money.amount ^1.0.0 · built alongside by fune
use super::payroll_income_tax::income_tax; ← from payroll.income-tax ^1.0.0 · built alongside by fune
use super::payroll_national_insurance::national_insurance; ← from payroll.national-insurance ^1.0.0 · built alongside by fune
use super::payroll_pension_auto_enrolment::pension_contributions; ← from payroll.pension-auto-enrolment ^1.0.0 · built alongside by fune
use super::payroll_pension_auto_enrolment_types::PensionScheme;
use super::payroll_student_loan::student_loan; ← from payroll.student-loan ^1.0.0 · built alongside by fune
use super::payroll_tax_period::tax_period; ← from payroll.tax-period ^1.0.0 · built alongside by fune
/// One period's payslip, in the order a UK payroll works it out.
///
/// The order matters because each deduction is taken from a different figure:
/// a net pay arrangement pension comes off pay before tax but not before
/// National Insurance; tax is on taxable pay to date; NI and student loans are
/// on this period's gross pay alone; relief at source pension comes off net pay.
///
/// # Panics
/// Panics on anything the capabilities it calls refuse, a non-GBP gross, or a
/// student loan plan listed twice.
pub fn gross_to_net(input: &PayslipInput) -> Payslip {
let gross = &input.gross;
if gross.currency != "GBP" {
panic!(
"payroll amounts must be in GBP, received {} for gross",
gross.currency
);
}
let when = tax_period(&input.pay_date, &input.frequency);
let mut pension_employee = 0i64;
let mut pension_employer = 0i64;
let mut before_tax = 0i64;
if let Some(scheme) = &input.pension {
let pension = pension_contributions(gross, &input.frequency, scheme, &input.pay_date);
pension_employee = pension.employee_deduction.minor;
pension_employer = pension.employer.minor;
if pension.deduct_before_tax {
before_tax = pension.employee_deduction.minor;
}
}
let taxable_pay = money(gross.minor - before_tax, "GBP");
let tax = income_tax(
&input.tax_code,
&input.frequency,
when.period,
&taxable_pay,
&input.previous_taxable_pay_to_date,
&input.previous_tax_to_date,
&input.pay_date,
);
let ni = national_insurance(gross, &input.ni_category, &input.frequency, &input.pay_date, None);
// An employee with more than one undergraduate plan repays once, against
// the lowest threshold, which is the largest of the per-plan deductions; a
// postgraduate loan is repaid alongside it.
let mut undergraduate = 0i64;
let mut postgraduate = 0i64;
let mut seen: Vec<&str> = Vec::new();
for plan in &input.student_loan_plans {
if seen.contains(&plan.as_str()) {
panic!("student loan plan {} is listed twice", plan);
}
seen.push(plan);
let deduction = student_loan(gross, plan, &input.frequency, &input.pay_date).minor;
if plan == "postgraduate" {
postgraduate = deduction;
} else if deduction > undergraduate {
undergraduate = deduction;
}
}
let net = gross.minor - pension_employee - tax.tax.minor - ni.employee.minor - undergraduate - postgraduate;
Payslip {
gross: gross.clone(),
pension_employee: money(pension_employee, "GBP"),
pension_employer: money(pension_employer, "GBP"),
taxable_pay,
tax: tax.tax,
employee_ni: ni.employee,
employer_ni: ni.employer,
student_loan: money(undergraduate, "GBP"),
postgraduate_loan: money(postgraduate, "GBP"),
net: money(net, "GBP"),
taxable_pay_to_date: tax.pay_to_date,
tax_to_date: tax.tax_to_date,
period: when.period,
tax_year: when.tax_year,
}
}
fn optional_int(v: &Value) -> Option<i64> {
if v.is_null() {
None
} else {
Some(v.as_i64())
}
}
pub fn payslip_input_from_value(v: &Value) -> PayslipInput {
let pension = v.get("pension");
PayslipInput {
gross: money_from_value(v.get("gross")),
frequency: v.get("frequency").as_str().to_string(),
pay_date: v.get("payDate").as_str().to_string(),
tax_code: v.get("taxCode").as_str().to_string(),
previous_taxable_pay_to_date: money_from_value(v.get("previousTaxablePayToDate")),
previous_tax_to_date: money_from_value(v.get("previousTaxToDate")),
ni_category: v.get("niCategory").as_str().to_string(),
student_loan_plans: v
.get("studentLoanPlans")
.as_arr()
.iter()
.map(|p| p.as_str().to_string())
.collect(),
pension: if pension.is_null() {
None
} else {
Some(PensionScheme {
employee_basis_points: optional_int(pension.get("employeeBasisPoints")),
employer_basis_points: optional_int(pension.get("employerBasisPoints")),
earnings_basis: pension.get("earningsBasis").as_str().to_string(),
arrangement: pension.get("arrangement").as_str().to_string(),
})
},
}
}
pub fn payslip_to_value(p: &Payslip) -> Value {
Value::obj(vec![
("gross", money_to_value(&p.gross)),
("pensionEmployee", money_to_value(&p.pension_employee)),
("pensionEmployer", money_to_value(&p.pension_employer)),
("taxablePay", money_to_value(&p.taxable_pay)),
("tax", money_to_value(&p.tax)),
("employeeNi", money_to_value(&p.employee_ni)),
("employerNi", money_to_value(&p.employer_ni)),
("studentLoan", money_to_value(&p.student_loan)),
("postgraduateLoan", money_to_value(&p.postgraduate_loan)),
("net", money_to_value(&p.net)),
("taxablePayToDate", money_to_value(&p.taxable_pay_to_date)),
("taxToDate", money_to_value(&p.tax_to_date)),
("period", Value::Int(p.period)),
("taxYear", Value::str(&p.tax_year)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
payslip_to_value(&gross_to_net(&payslip_input_from_value(&args[0])))
}