Functional Weave
Code in Python

charity.donation-matching@1.0.0

impl/typescript.ts

2,214 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 { applyRate } from "./money_apply_rate.ts";  ← from money.apply-rate ^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 DonationMatch, type MatchLimit } from "./charity_donation_matching_types.ts";

function check(name: string, currency: string, amount: Money): void {
  if (amount.currency !== currency) throw new RangeError(`currency mismatch: ${amount.currency} and ${currency}`);
  if (amount.minor < 0) throw new RangeError(`${name} must not be negative, received ${amount.minor}`);
}

/**
 * An employer's matched-giving contribution. The ratio is applied first and
 * rounded down; then the match is held to whatever room is left under the
 * donor's cap and the programme's budget, never below zero, so a donor who
 * has already used their cap gets nothing rather than a negative match.
 */
export function donationMatch(
  donation: Money,
  ratioBasisPoints: number,
  donorCap: Money | null,
  donorMatchedSoFar: Money,
  programmeCap: Money | null,
  programmeMatchedSoFar: Money
): DonationMatch {
  const currency = donation.currency;
  check("donation", currency, donation);
  if (!Number.isInteger(ratioBasisPoints) || ratioBasisPoints < 0) {
    throw new RangeError(`ratioBasisPoints must be a non-negative integer, received ${ratioBasisPoints}`);
  }
  if (donorCap !== null) check("donorCap", currency, donorCap);
  check("donorMatchedSoFar", currency, donorMatchedSoFar);
  if (programmeCap !== null) check("programmeCap", currency, programmeCap);
  check("programmeMatchedSoFar", currency, programmeMatchedSoFar);

  const uncapped = applyRate(donation, ratioBasisPoints, "down").minor;
  let matched = uncapped;
  let cappedBy: MatchLimit = "none";
  const donorRoom = donorCap === null ? null : Math.max(0, donorCap.minor - donorMatchedSoFar.minor);
  const programmeRoom = programmeCap === null ? null : Math.max(0, programmeCap.minor - programmeMatchedSoFar.minor);
  if (donorRoom !== null && donorRoom < matched) {
    matched = donorRoom;
    cappedBy = "donor";
  }
  if (programmeRoom !== null && programmeRoom < matched) {
    matched = programmeRoom;
    cappedBy = "programme";
  }
  return { matched: money(matched, currency), uncapped: money(uncapped, currency), cappedBy };
}