Functional Weave
Code in Rust

payroll.pro-rata-salary@1.0.0

impl/typescript.ts

1,963 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 { addDays, daysBetween } from "./dates_add_days.ts";  ← from dates.add-days ^1.0.0 · built alongside by fune
import { businessDaysBetween } from "./dates_business_days_between.ts";  ← from dates.business-days-between ^1.0.0 · built alongside by fune
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 { type ProRata, type ProRataMethod } from "./payroll_pro_rata_salary_types.ts";

function countDays(from: string, to: string, method: ProRataMethod, holidays: readonly string[]): number {
  if (method === "calendar-days") return daysBetween(from, to) + 1;
  if (method === "working-days") return businessDaysBetween(from, addDays(to, 1), holidays);
  throw new RangeError(`unknown pro-rata method "${method}"`);
}

/** Pro-rate a period's salary by time employed and FTE, rounding once. */
export function proRataSalary(
  periodSalary: Money,
  fteBasisPoints: number,
  periodStart: string,
  periodEnd: string,
  workedFrom: string,
  workedTo: string,
  method: ProRataMethod,
  holidays: readonly string[],
): ProRata {
  if (!Number.isInteger(fteBasisPoints) || fteBasisPoints < 1 || fteBasisPoints > 10000) {
    throw new RangeError(`fteBasisPoints must be a whole number from 1 to 10000, received ${fteBasisPoints}`);
  }
  if (daysBetween(periodStart, periodEnd) < 0) throw new RangeError("periodEnd is before periodStart");
  if (daysBetween(workedFrom, workedTo) < 0) throw new RangeError("workedTo is before workedFrom");
  if (workedFrom < periodStart || workedTo > periodEnd) {
    throw new RangeError("the worked dates must fall inside the pay period");
  }
  const daysInPeriod = countDays(periodStart, periodEnd, method, holidays);
  if (daysInPeriod === 0) throw new RangeError("the pay period has no working days to pro-rate over");
  const daysCounted = countDays(workedFrom, workedTo, method, holidays);
  const minor = roundDiv(periodSalary.minor * fteBasisPoints * daysCounted, 10000 * daysInPeriod, "half-up");
  return { pay: money(minor, periodSalary.currency), daysCounted, daysInPeriod };
}