You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/packages/billing-engine/src/tax.ts

31 lines
1.1 KiB
TypeScript

import type { IsoDate } from '@sims/domain'
/**
* Tax rates are dated configuration rows, never constants (07-DB-AND-CONFIG §1;
* the Sept-2025 GST 2.0 slab change is the precedent). Rates are basis points:
* 18% → 1800. Resolution is by the bill's business date, so returns and credit
* notes against old bills resolve the rate that was law on that day.
*/
export interface TaxClassRow {
classCode: string
ratePctBp: number
cessPctBp: number
effectiveFrom: IsoDate
effectiveTo?: IsoDate
}
export function resolveTaxClass(rows: TaxClassRow[], classCode: string, onDate: IsoDate): TaxClassRow {
let best: TaxClassRow | undefined
for (const row of rows) {
if (row.classCode !== classCode) continue
if (row.effectiveFrom > onDate) continue
if (row.effectiveTo !== undefined && row.effectiveTo < onDate) continue
if (best === undefined || row.effectiveFrom > best.effectiveFrom) best = row
}
if (best === undefined) {
// Silent defaults on tax are how wrong GST reaches the portal — fail loudly.
throw new Error(`No tax rate for class "${classCode}" on ${onDate}`)
}
return best
}