Functional Weave
Code in TypeScript

retail.shipping-rate@1.0.1

impl/typescript.ts

2,741 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, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { compareMoney } from "./money_compare.ts";  ← from money.compare ^1.0.0 · built alongside by fune
import { chargeableWeight } from "./retail_volumetric_weight.ts";  ← from retail.volumetric-weight ^1.0.0 · built alongside by fune
import { type ShippingBand, type Parcel, type ShippingQuote } from "./retail_shipping_rate_types.ts";

const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;

function chargeableIn(band: ShippingBand, parcel: Parcel): number {
  if (band.volumetricDivisor === null) {
    return roundDiv(parcel.actualGrams, band.roundUpToGrams, "up") * band.roundUpToGrams;
  }
  return chargeableWeight(
    parcel.lengthMm,
    parcel.widthMm,
    parcel.heightMm,
    parcel.actualGrams,
    band.volumetricDivisor,
    band.roundUpToGrams,
  ).chargeableGrams;
}

/**
 * The delivery charge for a parcel: the smallest band on the rate card in
 * force on the order date that takes it, free when the basket reaches the
 * band's threshold.
 */
export function shippingRate(
  bands: readonly ShippingBand[],
  service: string,
  zone: string,
  parcel: Parcel,
  basketTotal: Money,
  onDate: string,
): ShippingQuote {
  if (!ISO_DATE.test(onDate)) throw new RangeError(`onDate must be an ISO date (YYYY-MM-DD), received "${onDate}"`);
  for (const d of [parcel.lengthMm, parcel.widthMm, parcel.heightMm]) {
    if (!Number.isInteger(d) || d < 1) throw new RangeError(`dimensions must be 1 mm or more, received ${d}`);
  }
  if (!Number.isInteger(parcel.actualGrams) || parcel.actualGrams < 0) {
    throw new RangeError(`actualGrams must not be negative, received ${parcel.actualGrams}`);
  }
  const longest = Math.max(parcel.lengthMm, parcel.widthMm, parcel.heightMm);

  let best: ShippingBand | null = null;
  let bestGrams = 0;
  for (const band of bands) {
    if (band.service !== service || band.zone !== zone) continue;
    if (onDate < band.validFrom || (band.validTo !== null && onDate > band.validTo)) continue;
    if (band.maxLengthMm !== null && longest > band.maxLengthMm) continue;
    const grams = chargeableIn(band, parcel);
    if (grams > band.maxGrams) continue;
    if (
      best === null ||
      band.maxGrams < best.maxGrams ||
      (band.maxGrams === best.maxGrams && compareMoney(band.price, best.price) < 0)
    ) {
      best = band;
      bestGrams = grams;
    }
  }
  if (best === null) {
    throw new RangeError(`no shipping band for service "${service}" to zone "${zone}" on ${onDate} fits this parcel`);
  }

  const free = best.freeOver !== null && compareMoney(basketTotal, best.freeOver) >= 0;
  return {
    service,
    zone,
    chargeableGrams: bestGrams,
    bandMaxGrams: best.maxGrams,
    standardPrice: best.price,
    price: free ? money(0, best.price.currency) : best.price,
    free,
  };
}