Functional Weave
Code in TypeScript

professional.utilisation@1.0.0

impl/typescript.ts

1,931 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 } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { type UtilisationRates } from "./professional_utilisation_types.ts";

function ratio(numerator: number, denominator: number): number | null {
  // A ratio over nothing is not 0% or 100%: null says there was nothing to realise.
  return denominator === 0 ? null : roundDiv(numerator * 10000, denominator, "half-up");
}

function checkMinutes(name: string, value: number): void {
  if (!Number.isInteger(value) || value < 0) {
    throw new RangeError(`${name} must be a non-negative integer, received ${value}`);
  }
}

/** Utilisation and billing, collection and overall realisation, in basis points. */
export function utilisationRates(
  billableMinutes: number,
  availableMinutes: number,
  standardValue: Money,
  billedValue: Money,
  collectedValue: Money,
): UtilisationRates {
  checkMinutes("billableMinutes", billableMinutes);
  checkMinutes("availableMinutes", availableMinutes);
  if (availableMinutes === 0) {
    throw new RangeError("availableMinutes must be greater than zero");
  }
  const amounts: [string, Money][] = [["standardValue", standardValue], ["billedValue", billedValue], ["collectedValue", collectedValue]];
  for (const [name, amount] of amounts) {
    if (amount.currency !== standardValue.currency) {
      throw new RangeError(`currency mismatch: ${standardValue.currency} and ${amount.currency}`);
    }
    if (amount.minor < 0) {
      throw new RangeError(`${name} must not be negative, received ${amount.minor}`);
    }
  }
  return {
    utilisationBasisPoints: roundDiv(billableMinutes * 10000, availableMinutes, "half-up"),
    billingRealisationBasisPoints: ratio(billedValue.minor, standardValue.minor),
    collectionRealisationBasisPoints: ratio(collectedValue.minor, billedValue.minor),
    overallRealisationBasisPoints: ratio(collectedValue.minor, standardValue.minor),
  };
}