impl/typescript.ts
3,042 bytes · the TypeScript implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type Payslip, type PayslipInput } from "./payroll_gross_to_net_types.ts";
import { incomeTax } from "./payroll_income_tax.ts"; ← from payroll.income-tax ^1.0.0 · built alongside by fune
import { nationalInsurance } from "./payroll_national_insurance.ts"; ← from payroll.national-insurance ^1.0.0 · built alongside by fune
import { pensionContributions } from "./payroll_pension_auto_enrolment.ts"; ← from payroll.pension-auto-enrolment ^1.0.0 · built alongside by fune
import { studentLoan } from "./payroll_student_loan.ts"; ← from payroll.student-loan ^1.0.0 · built alongside by fune
import { taxPeriod } from "./payroll_tax_period.ts"; ← 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.
*/
export function grossToNet(input: PayslipInput): Payslip {
const gross = input.gross;
if (gross.currency !== "GBP") {
throw new RangeError(`payroll amounts must be in GBP, received ${gross.currency} for gross`);
}
const when = taxPeriod(input.payDate, input.frequency);
let pensionEmployee = 0;
let pensionEmployer = 0;
let beforeTax = 0;
if (input.pension !== null) {
const pension = pensionContributions(gross, input.frequency, input.pension, input.payDate);
pensionEmployee = pension.employeeDeduction.minor;
pensionEmployer = pension.employer.minor;
if (pension.deductBeforeTax) beforeTax = pension.employeeDeduction.minor;
}
const taxablePay = money(gross.minor - beforeTax, "GBP");
const tax = incomeTax(input.taxCode, input.frequency, when.period, taxablePay, input.previousTaxablePayToDate, input.previousTaxToDate, input.payDate);
const ni = nationalInsurance(gross, input.niCategory, input.frequency, input.payDate, null);
// 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 undergraduate = 0;
let postgraduate = 0;
const seen: string[] = [];
for (const plan of input.studentLoanPlans) {
if (seen.includes(plan)) throw new RangeError(`student loan plan ${plan} is listed twice`);
seen.push(plan);
const deduction = studentLoan(gross, plan, input.frequency, input.payDate).minor;
if (plan === "postgraduate") postgraduate = deduction;
else if (deduction > undergraduate) undergraduate = deduction;
}
const net = gross.minor - pensionEmployee - tax.tax.minor - ni.employee.minor - undergraduate - postgraduate;
return {
gross,
pensionEmployee: money(pensionEmployee, "GBP"),
pensionEmployer: money(pensionEmployer, "GBP"),
taxablePay,
tax: tax.tax,
employeeNi: ni.employee,
employerNi: ni.employer,
studentLoan: money(undergraduate, "GBP"),
postgraduateLoan: money(postgraduate, "GBP"),
net: money(net, "GBP"),
taxablePayToDate: tax.payToDate,
taxToDate: tax.taxToDate,
period: when.period,
taxYear: when.taxYear,
};
}