Functional Weave
Code in Rust

telecoms.data-allowance@1.0.0

impl/typescript.ts

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

/**
 * Usage against a bundle: remaining allowance, overage in whole blocks priced
 * and limited to an optional cap, and the share used, rounded down so a
 * bundle with anything left never reads 100%.
 */
export function dataAllowance(allowance: number, used: number, overageBlock: number, pricePerBlock: Money, overageCap: Money | null): DataAllowance {
  if (!Number.isInteger(allowance) || allowance < 0 || !Number.isInteger(used) || used < 0) {
    throw new RangeError(`allowance and usage must be whole numbers of 0 or more, received ${allowance} and ${used}`);
  }
  if (!Number.isInteger(overageBlock) || overageBlock < 1) {
    throw new RangeError(`overage block must be 1 or more, received ${overageBlock}`);
  }
  if (pricePerBlock.minor < 0) {
    throw new RangeError(`price per block must not be negative, received ${pricePerBlock.minor}`);
  }
  if (overageCap !== null) {
    assertSameCurrency(pricePerBlock, overageCap);
    if (overageCap.minor < 0) throw new RangeError(`overage cap must not be negative, received ${overageCap.minor}`);
  }
  const overage = Math.max(used - allowance, 0);
  const overageBlocks = roundDiv(overage, overageBlock, "up");
  const uncapped = overageBlocks * pricePerBlock.minor;
  const capped = overageCap !== null && uncapped > overageCap.minor;
  return {
    used,
    remaining: Math.max(allowance - used, 0),
    overage,
    overageBlocks,
    overageCharge: money(capped ? (overageCap as Money).minor : uncapped, pricePerBlock.currency),
    capped,
    percentUsedBasisPoints: allowance === 0 ? null : roundDiv(used * 10000, allowance, "down"),
  };
}