Functional Weave
Code in TypeScript

payroll.gross-to-net@1.0.1

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,
  };
}