Functional Weave
Code in Python

finance.proration@1.0.0

impl/typescript.ts

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

/**
 * Split a period's charge into the part used and the part not used.
 *
 * The mid-cycle upgrade, downgrade and cancellation calculation. Written the
 * obvious way - one rounding for the charge and another for the credit - the
 * two halves do not reliably add back up: 9.99 over two days is 4.995 each
 * way, and two half-up roundings produce 5.00 and 5.00, a penny invented on a
 * line the customer can see.
 *
 * One call to money.allocate does the whole split instead, so used + unused is
 * the original amount for every input, credits included.
 */
export function prorate(amount: Money, totalDays: number, usedDays: number): ProrationSplit {
  if (!Number.isInteger(totalDays) || totalDays <= 0) {
    throw new RangeError(`total days must be greater than zero, received ${totalDays}`);
  }
  if (!Number.isInteger(usedDays) || usedDays < 0) {
    throw new RangeError(`used days must not be negative, received ${usedDays}`);
  }
  if (usedDays > totalDays) {
    // Clamping here would turn a bug in the caller's period arithmetic into a
    // plausible invoice, which is far more expensive to find later.
    throw new RangeError(`used days must not exceed total days, received ${usedDays} of ${totalDays}`);
  }

  // totalDays > 0 guarantees the ratios do not sum to zero, so allocate is safe
  // even when one side is zero: [0, n] and [n, 0] are both well defined.
  const [used, unused] = allocate(amount, [usedDays, totalDays - usedDays]);

  return { total: amount, used, unused };
}