Functional Weave
Code in TypeScript

inventory.pallet-fit@1.0.0

impl/typescript.ts

3,187 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 { PALLETS } from "./inventory_pallet_fit_data.ts";  ← this capability’s own data, compiled from data/pallets.json into the same file by fune build
import { type Carton, type PalletFit, type PalletLimit, type PalletType } from "./inventory_pallet_fit_types.ts";

/**
 * Cartons per layer on a `length` x `width` deck, base `a` x `b`, as the best
 * two-block pattern: one split across the length or the width, each block a
 * grid with the carton one way round.
 */
function cartonsPerLayer(length: number, width: number, a: number, b: number): number {
  let best = 0;
  for (let i = 0; i * a <= length; i++) {
    best = Math.max(best, i * Math.floor(width / b) + Math.floor((length - i * a) / b) * Math.floor(width / a));
  }
  for (let j = 0; j * b <= width; j++) {
    best = Math.max(best, j * Math.floor(length / a) + Math.floor((width - j * b) / a) * Math.floor(length / b));
  }
  return best;
}

function positive(name: string, value: number): void {
  if (!Number.isInteger(value) || value <= 0) {
    throw new RangeError(`${name} must be a whole number greater than zero, received ${value}`);
  }
}

/** The best stack of identical cartons on a UK or EUR pallet within the limits. */
export function palletFit(carton: Carton, pallet: PalletType, maxLoadHeightMm: number, maxLoadWeightGrams: number | null, upright: boolean): PalletFit {
  positive("lengthMm", carton.lengthMm);
  positive("widthMm", carton.widthMm);
  positive("heightMm", carton.heightMm);
  if (!Number.isInteger(carton.weightGrams) || carton.weightGrams < 0) {
    throw new RangeError(`weightGrams must be a whole number, not negative, received ${carton.weightGrams}`);
  }
  positive("maxLoadHeightMm", maxLoadHeightMm);
  if (maxLoadWeightGrams !== null && (!Number.isInteger(maxLoadWeightGrams) || maxLoadWeightGrams < 0)) {
    throw new RangeError(`maxLoadWeightGrams must be a whole number, not negative, received ${maxLoadWeightGrams}`);
  }
  const deck = PALLETS.find((p) => p.code === pallet);
  if (!deck) throw new RangeError(`unknown pallet "${pallet}": expected uk or euro`);

  const { lengthMm: l, widthMm: w, heightMm: h } = carton;
  const choices: [number, number, number][] = upright ? [[h, l, w]] : [[h, l, w], [w, l, h], [l, w, h]];
  let best: PalletFit | null = null;
  for (const [vertical, a, b] of choices) {
    const perLayer = cartonsPerLayer(deck.lengthMm, deck.widthMm, a, b);
    const layers = Math.floor(maxLoadHeightMm / vertical);
    let perPallet = perLayer * layers;
    let limitedBy: PalletLimit = perLayer === 0 ? "footprint" : "height";
    if (perLayer > 0 && maxLoadWeightGrams !== null && carton.weightGrams > 0) {
      const byWeight = Math.floor(maxLoadWeightGrams / carton.weightGrams);
      if (byWeight < perPallet) {
        perPallet = byWeight;
        limitedBy = "weight";
      }
    }
    const fit: PalletFit = {
      palletLengthMm: deck.lengthMm,
      palletWidthMm: deck.widthMm,
      cartonHeightMm: vertical,
      perLayer,
      layers,
      perPallet,
      limitedBy,
      loadHeightMm: perLayer === 0 ? 0 : Math.ceil(perPallet / perLayer) * vertical,
      loadWeightGrams: perPallet * carton.weightGrams,
    };
    if (best === null || fit.perPallet > best.perPallet) best = fit;
  }
  return best as PalletFit;
}