impl/typescript.ts
3,047 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 TaxCode, type TaxCodeKind, type TaxRegion } from "./payroll_tax_code_parse_types.ts";
// The whitespace every language trims alike: trim() and \s also take \ufeff,
// which Python's strip() and Rust's trim() keep, and those take \x85, which
// trim() keeps. So each implementation names the same set instead.
const EDGE_SPACE = /^[ \t\n\r\v\f\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[ \t\n\r\v\f\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g;
// The basis marker may be written with or without a space: "1257L W1", "1257LM1", "1257L X".
const BASIS = /^(.*?)[ \t\n\r\v\f\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]*(W1|M1|X)$/;
const ALLOWANCE = /^(0|[1-9][0-9]{0,5})([LMNT])$/;
const K_CODE = /^K([1-9][0-9]{0,5})$/;
const D_CODE = /^D([0-9])$/;
/**
* Take a UK PAYE tax code apart into its region, kind, allowance and basis.
*
* The annual allowance is number x £10 + £9, not number x £10: a code covers a
* £10 band of allowances and HMRC's routines give the employee the top of it,
* so 1257L is worth £12,579 a year to the payroll.
*/
export function parseTaxCode(code: string): TaxCode {
const original = code;
let text = code.replace(EDGE_SPACE, "").toUpperCase();
if (text === "") throw new RangeError(`unrecognised tax code "${original}"`);
let cumulative = true;
const basis = BASIS.exec(text);
if (basis !== null && basis[1] !== "") {
text = basis[1];
cumulative = false;
}
let region: TaxRegion = "rest-of-uk";
let body = text;
if (text.startsWith("S")) {
region = "scotland";
body = text.slice(1);
} else if (text.startsWith("C")) {
region = "wales";
body = text.slice(1);
}
let kind: TaxCodeKind;
let number = 0;
let suffix: string | null = null;
let allowancePounds = 0;
const allowance = ALLOWANCE.exec(body);
const k = K_CODE.exec(body);
const d = D_CODE.exec(body);
if (body === "NT") {
if (region !== "rest-of-uk") {
throw new RangeError(`there is no Scottish or Welsh NT code: "${original}"`);
}
kind = "no-tax";
} else if (body === "BR") {
kind = "basic-rate";
} else if (d !== null) {
kind = "d-rate";
number = Number(d[1]);
} else if (k !== null) {
kind = "negative-allowance";
number = Number(k[1]);
allowancePounds = -(number * 10 + 9);
} else if (allowance !== null) {
number = Number(allowance[1]);
suffix = allowance[2];
if (number === 0 && suffix !== "T") {
throw new RangeError(`unrecognised tax code "${original}": a zero allowance is only issued as 0T`);
}
kind = "allowance";
allowancePounds = number === 0 ? 0 : number * 10 + 9;
} else {
throw new RangeError(`unrecognised tax code "${original}"`);
}
const prefix = region === "scotland" ? "S" : region === "wales" ? "C" : "";
return {
code: prefix + body,
region,
kind,
number,
suffix,
annualAllowance: money(allowancePounds * 100, "GBP"),
cumulative,
};
}