Functional Weave
Code in TypeScript

finance.tax.vat-rate@1.0.0

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();
}