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