@eristack/tax
Version:
Tax code registry and effective-dated rates — math via @eristack/money Tax ops
1 lines • 5.41 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/core/errors.ts","../src/core/registry.ts"],"sourcesContent":["export {\n TaxCodeNotFoundError,\n TaxRateNotFoundError,\n} from \"./core/errors.js\";\nexport { createTaxRegistry } from \"./core/registry.js\";\nexport type {\n ResolvedTaxRate,\n TaxCode,\n TaxRateScheduleEntry,\n} from \"./core/types.js\";\n","export class TaxCodeNotFoundError extends Error {\n constructor(public readonly code: string) {\n super(`Tax code not registered: ${code}`);\n this.name = \"TaxCodeNotFoundError\";\n }\n}\n\nexport class TaxRateNotFoundError extends Error {\n constructor(\n public readonly code: string,\n public readonly asOf: string,\n ) {\n super(`No tax rate for code ${code} effective on or before ${asOf}`);\n this.name = \"TaxRateNotFoundError\";\n }\n}\n","import { Money, Tax } from \"@eristack/money\";\nimport {\n TaxCodeNotFoundError,\n TaxRateNotFoundError,\n} from \"./errors.js\";\nimport type {\n ResolvedTaxRate,\n TaxCode,\n TaxRateScheduleEntry,\n} from \"./types.js\";\n\nfunction normalizeWallDate(date: string): string {\n const trimmed = date.trim();\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n throw new Error(`Tax asOf must be wall date YYYY-MM-DD, got \"${date}\"`);\n }\n return trimmed;\n}\n\nexport function createTaxRegistry(initial?: {\n codes?: TaxCode[];\n rates?: TaxRateScheduleEntry[];\n}) {\n const codes = new Map<string, TaxCode>();\n const schedules: TaxRateScheduleEntry[] = [];\n\n function registerTaxCode(entry: TaxCode): void {\n const code = entry.code.trim();\n if (!code) throw new Error(\"Tax code cannot be empty\");\n codes.set(code, { ...entry, code });\n }\n\n function registerRateSchedule(entry: TaxRateScheduleEntry): void {\n const code = entry.code.trim();\n if (!codes.has(code)) {\n throw new TaxCodeNotFoundError(code);\n }\n schedules.push({\n ...entry,\n code,\n effectiveFrom: normalizeWallDate(entry.effectiveFrom),\n ratePercent: entry.ratePercent.trim(),\n });\n schedules.sort((a, b) => {\n const byCode = a.code.localeCompare(b.code);\n if (byCode !== 0) return byCode;\n return a.effectiveFrom.localeCompare(b.effectiveFrom);\n });\n }\n\n for (const c of initial?.codes ?? []) registerTaxCode(c);\n for (const r of initial?.rates ?? []) registerRateSchedule(r);\n\n function resolveTaxRate(args: {\n code: string;\n asOf: string;\n }): ResolvedTaxRate {\n const code = args.code.trim();\n if (!codes.has(code)) {\n throw new TaxCodeNotFoundError(code);\n }\n const asOf = normalizeWallDate(args.asOf);\n let match: TaxRateScheduleEntry | undefined;\n for (const row of schedules) {\n if (row.code !== code) continue;\n if (row.effectiveFrom <= asOf) {\n match = row;\n } else if (match) {\n break;\n }\n }\n if (!match) {\n throw new TaxRateNotFoundError(code, asOf);\n }\n return {\n code: match.code,\n effectiveFrom: match.effectiveFrom,\n ratePercent: match.ratePercent,\n };\n }\n\n function applyTaxToAmount(base: Money, ratePercent: string): Money {\n return base.with(Tax.onExclusive(ratePercent));\n }\n\n function listTaxCodes(): TaxCode[] {\n return [...codes.values()];\n }\n\n return {\n registerTaxCode,\n registerRateSchedule,\n resolveTaxRate,\n applyTaxToAmount,\n listTaxCodes,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9C,YAA4B,MAAc;AACxC,UAAM,4BAA4B,IAAI,EAAE;AADd;AAE1B,SAAK,OAAO;AAAA,EACd;AAAA,EAH4B;AAI9B;AAEO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9C,YACkB,MACA,MAChB;AACA,UAAM,wBAAwB,IAAI,2BAA2B,IAAI,EAAE;AAHnD;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;;;ACfA,mBAA2B;AAW3B,SAAS,kBAAkB,MAAsB;AAC/C,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,sBAAsB,KAAK,OAAO,GAAG;AACxC,UAAM,IAAI,MAAM,+CAA+C,IAAI,GAAG;AAAA,EACxE;AACA,SAAO;AACT;AAEO,SAAS,kBAAkB,SAG/B;AACD,QAAM,QAAQ,oBAAI,IAAqB;AACvC,QAAM,YAAoC,CAAC;AAE3C,WAAS,gBAAgB,OAAsB;AAC7C,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,0BAA0B;AACrD,UAAM,IAAI,MAAM,EAAE,GAAG,OAAO,KAAK,CAAC;AAAA,EACpC;AAEA,WAAS,qBAAqB,OAAmC;AAC/D,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI,CAAC,MAAM,IAAI,IAAI,GAAG;AACpB,YAAM,IAAI,qBAAqB,IAAI;AAAA,IACrC;AACA,cAAU,KAAK;AAAA,MACb,GAAG;AAAA,MACH;AAAA,MACA,eAAe,kBAAkB,MAAM,aAAa;AAAA,MACpD,aAAa,MAAM,YAAY,KAAK;AAAA,IACtC,CAAC;AACD,cAAU,KAAK,CAAC,GAAG,MAAM;AACvB,YAAM,SAAS,EAAE,KAAK,cAAc,EAAE,IAAI;AAC1C,UAAI,WAAW,EAAG,QAAO;AACzB,aAAO,EAAE,cAAc,cAAc,EAAE,aAAa;AAAA,IACtD,CAAC;AAAA,EACH;AAEA,aAAW,KAAK,SAAS,SAAS,CAAC,EAAG,iBAAgB,CAAC;AACvD,aAAW,KAAK,SAAS,SAAS,CAAC,EAAG,sBAAqB,CAAC;AAE5D,WAAS,eAAe,MAGJ;AAClB,UAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,QAAI,CAAC,MAAM,IAAI,IAAI,GAAG;AACpB,YAAM,IAAI,qBAAqB,IAAI;AAAA,IACrC;AACA,UAAM,OAAO,kBAAkB,KAAK,IAAI;AACxC,QAAI;AACJ,eAAW,OAAO,WAAW;AAC3B,UAAI,IAAI,SAAS,KAAM;AACvB,UAAI,IAAI,iBAAiB,MAAM;AAC7B,gBAAQ;AAAA,MACV,WAAW,OAAO;AAChB;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,qBAAqB,MAAM,IAAI;AAAA,IAC3C;AACA,WAAO;AAAA,MACL,MAAM,MAAM;AAAA,MACZ,eAAe,MAAM;AAAA,MACrB,aAAa,MAAM;AAAA,IACrB;AAAA,EACF;AAEA,WAAS,iBAAiB,MAAa,aAA4B;AACjE,WAAO,KAAK,KAAK,iBAAI,YAAY,WAAW,CAAC;AAAA,EAC/C;AAEA,WAAS,eAA0B;AACjC,WAAO,CAAC,GAAG,MAAM,OAAO,CAAC;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}