Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { type RoundingMode, roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { IPT_RATES, IPT_RATES_HISTORY } from "./insurance_ipt_data.ts"; ← this capability’s own data, compiled from data/ipt-rates.json into the same file by fune build
import { type IptBand, type IptBreakdown } from "./insurance_ipt_types.ts";
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
/**
* Add UK Insurance Premium Tax to a net premium, or take it out of a gross one,
* at the rate in force on the date the insurer receives the premium.
*
* Only one figure is ever rounded - the tax when adding, the net when taking
* out - and the other is the difference, so net + tax is always the gross.
* Taking 12% off a gross premium is the classic error: the tax inside 112.00
* is 12.00, not 13.44.
*/
export function insurancePremiumTax(
amount: Money,
includesTax: boolean,
band: IptBand,
onDate: string,
mode: RoundingMode
): IptBreakdown {
if (band !== "standard" && band !== "higher") {
throw new RangeError(`unknown IPT band "${band}": use standard or higher`);
}
if (typeof onDate !== "string" || !ISO_DATE.test(onDate)) {
throw new RangeError(`onDate must be an ISO date (YYYY-MM-DD), received "${onDate}"`);
}
let rule: (typeof IPT_RATES)[number] | null = null;
for (const r of IPT_RATES) {
if (r.band !== band || onDate < r.validFrom) continue;
if (r.validTo !== null && onDate > r.validTo) continue;
rule = r;
}
if (rule === null) {
// A history=current build keeps only the rates in force; answering an older
// date with today's rate would be exactly the wrong answer, so say so.
if (IPT_RATES_HISTORY !== "full") {
let earliest: string | null = null;
for (const r of IPT_RATES) {
if (r.band === band && (earliest === null || r.validFrom < earliest)) earliest = r.validFrom;
}
if (earliest !== null && onDate < earliest) {
throw new RangeError(
`no IPT rule for ${band} on ${onDate}: this build was installed with history=${IPT_RATES_HISTORY}, ` +
`so it only carries rates from ${earliest}. Reinstall with history=full for older premiums.`
);
}
}
throw new RangeError(`no IPT rule for ${band} on ${onDate}`);
}
const bp = rule.basisPoints;
let net: Money;
let tax: Money;
let gross: Money;
if (includesTax) {
gross = amount;
net = money(roundDiv(amount.minor * 10000, 10000 + bp, mode), amount.currency);
tax = money(amount.minor - net.minor, amount.currency);
} else {
net = amount;
tax = money(roundDiv(amount.minor * bp, 10000, mode), amount.currency);
gross = money(amount.minor + tax.minor, amount.currency);
}
return { net, tax, gross, basisPoints: bp, band, validFrom: rule.validFrom };
}