hospitality.service-charge@1.0.0
impl/typescript.ts
1,597 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 { type RoundingMode } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { addMoney } from "./money_add.ts"; ← from money.add ^1.0.0 · built alongside by fune
import { type Money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { applyRate } from "./money_apply_rate.ts"; ← from money.apply-rate ^1.0.0 · built alongside by fune
import { sumMoney } from "./money_sum.ts"; ← from money.sum ^1.0.0 · built alongside by fune
import { type BillLine, type ServiceCharge } from "./hospitality_service_charge_types.ts";
/**
* A discretionary service charge on the eligible lines of a bill.
*
* The rate is applied once, to the eligible total, and rounded once. Charging
* each line and adding the pennies up drifts: three 3.33 lines at 12.5% are
* 0.42 each, 1.26 in all, where 12.5% of 9.99 is 1.25.
*/
export function serviceCharge(lines: readonly BillLine[], basisPoints: number, mode: RoundingMode): ServiceCharge {
if (lines.length === 0) {
throw new RangeError("a bill needs at least one line");
}
if (!Number.isInteger(basisPoints) || basisPoints < 0 || basisPoints > 10000) {
throw new RangeError(`basisPoints must be a whole number from 0 to 10000, received ${basisPoints}`);
}
const currency = lines[0].amount.currency;
const subtotal = sumMoney(
lines.map((line) => line.amount),
currency,
);
const eligibleTotal: Money = sumMoney(
lines.filter((line) => line.eligible).map((line) => line.amount),
currency,
);
if (eligibleTotal.minor < 0) {
throw new RangeError(`the eligible lines total ${eligibleTotal.minor}, and a service charge cannot be negative`);
}
const charge = applyRate(eligibleTotal, basisPoints, mode);
return { eligibleTotal, basisPoints, charge, subtotal, total: addMoney(subtotal, charge) };
}