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.
31 lines
1.1 KiB
TypeScript
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
|
|
}
|