Functional Weave
Code in TypeScript

hospitality.tronc-allocation@1.0.0

impl/typescript.ts

2,260 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 { allocate } from "./money_allocate.ts";  ← from money.allocate ^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 TroncMember, type TroncMethod, type TroncShare } from "./hospitality_tronc_allocation_types.ts";

function weightOf(member: TroncMember, method: TroncMethod): number {
  if (method === "hours") return member.minutesWorked;
  if (method === "points") return member.points;
  if (method === "hours-and-points") return member.minutesWorked * member.points;
  throw new RangeError(`unknown tronc method "${method}"`);
}

/**
 * Share a tips pool between staff in proportion to a weight.
 *
 * Time is in minutes so a 7.5-hour shift is exact. The pool is split with
 * money.allocate, so the shares add up to the pool exactly: the leftover
 * pennies go to the largest remainders, ties to whoever is listed first.
 */
export function troncAllocation(pool: Money, staff: readonly TroncMember[], method: TroncMethod): readonly TroncShare[] {
  if (staff.length === 0) throw new RangeError("staff must have at least one member");
  if (pool.minor < 0) throw new RangeError(`pool must not be negative, received ${pool.minor}`);
  const ids = new Set<string>();
  for (const member of staff) {
    if (member.id === "") throw new RangeError("every member needs an id");
    if (ids.has(member.id)) throw new RangeError(`staff id "${member.id}" appears twice`);
    ids.add(member.id);
    if (!Number.isInteger(member.minutesWorked) || member.minutesWorked < 0) {
      throw new RangeError(`minutesWorked must not be negative, received ${member.minutesWorked} for "${member.id}"`);
    }
    if (!Number.isInteger(member.points) || member.points < 0) {
      throw new RangeError(`points must not be negative, received ${member.points} for "${member.id}"`);
    }
  }
  const weights = staff.map((member) => weightOf(member, method));
  const total = weights.reduce((sum, w) => sum + w, 0);
  if (total === 0) {
    if (pool.minor !== 0) throw new RangeError("nobody in the pool has any weight to share it by");
    return staff.map((member) => ({ id: member.id, weight: 0, share: money(0, pool.currency) }));
  }
  const shares = allocate(pool, weights);
  return staff.map((member, i) => ({ id: member.id, weight: weights[i], share: shares[i] }));
}