impl/typescript.ts
1,864 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 { VAT_RULES } from "./finance_tax_vat_rate_data.ts"; ← this capability’s own data, compiled from data/vat-rules.json into the same file by fune build
import { type VatRate } from "./finance_tax_vat_rate_types.ts";
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
/**
* The VAT rate in force for a jurisdiction and category on a given date.
*
* The date matters. A credit note against a 2009 invoice has to use the rate
* that applied in 2009, not the one that applies today, and a system that
* hardcodes 0.20 cannot do that at all.
*
* ISO dates compare correctly as strings, so this needs no date library.
*/
export function vatRate(jurisdiction: string, category: string, onDate: string): VatRate {
if (!ISO_DATE.test(onDate)) {
throw new RangeError(`onDate must be an ISO date (YYYY-MM-DD), received "${onDate}"`);
}
let best: VatRate | null = null;
for (const rule of VAT_RULES) {
if (rule.jurisdiction !== jurisdiction || rule.category !== category) continue;
if (onDate < rule.validFrom) continue;
if (rule.validTo !== null && onDate > rule.validTo) continue;
if (best === null || rule.validFrom > best.validFrom) {
best = {
jurisdiction: rule.jurisdiction,
category: rule.category,
basisPoints: rule.basisPoints,
validFrom: rule.validFrom,
validTo: rule.validTo,
};
}
}
if (best === null) {
throw new RangeError(`no VAT rule for ${jurisdiction}/${category} on ${onDate}`);
}
return best;
}
/** Every category this registry knows a rate for, for a jurisdiction and date. */
export function vatCategories(jurisdiction: string, onDate: string): string[] {
const seen = new Set<string>();
for (const rule of VAT_RULES) {
if (rule.jurisdiction !== jurisdiction) continue;
if (onDate < rule.validFrom) continue;
if (rule.validTo !== null && onDate > rule.validTo) continue;
seen.add(rule.category);
}
return [...seen].sort();
}