Functional Weave
Code in Rust

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