Functional Weave
Code in Python

payroll.overtime@1.0.1

impl/typescript.ts

1,805 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 { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts";  ← from money.amount ^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 OvertimeBand, type OvertimeLine, type OvertimePay } from "./payroll_overtime_types.ts";

/** Price the minutes worked band by band, rounding once per payslip line. */
export function overtimePay(minutesWorked: number, hourlyRate: Money, bands: readonly OvertimeBand[]): OvertimePay {
  if (!Number.isInteger(minutesWorked) || minutesWorked < 0) {
    throw new RangeError(`minutesWorked must be a whole number of minutes, not negative, received ${minutesWorked}`);
  }
  if (bands.length === 0) throw new RangeError("overtime needs at least one band");
  if (bands[0].fromMinutes !== 0) throw new RangeError("the first band must start at 0 minutes");
  for (let i = 1; i < bands.length; i++) {
    if (bands[i].fromMinutes <= bands[i - 1].fromMinutes) {
      throw new RangeError("bands must be in strictly ascending order of fromMinutes");
    }
  }
  const lines: OvertimeLine[] = [];
  for (let i = 0; i < bands.length; i++) {
    const from = bands[i].fromMinutes;
    const to = i + 1 < bands.length ? Math.min(bands[i + 1].fromMinutes, minutesWorked) : minutesWorked;
    const minutes = to - from;
    if (minutes <= 0) break;
    const multiplier = bands[i].multiplierBasisPoints;
    if (!Number.isInteger(multiplier) || multiplier < 0) {
      throw new RangeError(`multiplierBasisPoints must be a whole number, not negative, received ${multiplier}`);
    }
    const pay = money(roundDiv(hourlyRate.minor * minutes * multiplier, 60 * 10000, "half-up"), hourlyRate.currency);
    lines.push({ minutes, multiplierBasisPoints: multiplier, pay });
  }
  return { lines, total: sumMoney(lines.map((l) => l.pay), hourlyRate.currency) };
}