Functional Weave
Code in Rust

logistics.sscc@1.0.0

impl/typescript.ts

1,817 bytes · the TypeScript implementation · view raw

/**
 * Build an 18-digit SSCC: extension digit, GS1 company prefix, zero-padded
 * serial reference and GS1 mod-10 check digit.
 */
export function buildSscc(extensionDigit: number, companyPrefix: string, serialReference: number): string {
  if (!Number.isInteger(extensionDigit) || extensionDigit < 0 || extensionDigit > 9) {
    throw new RangeError(`extensionDigit must be 0 to 9, received ${extensionDigit}`);
  }
  if (typeof companyPrefix !== "string" || !/^[0-9]{4,12}$/.test(companyPrefix)) {
    throw new RangeError(`companyPrefix must be 4 to 12 digits, received "${companyPrefix}"`);
  }
  if (!Number.isInteger(serialReference)) {
    throw new TypeError(`serialReference must be an integer, received ${serialReference}`);
  }
  if (serialReference < 0) {
    throw new RangeError(`serialReference must not be negative, received ${serialReference}`);
  }
  // Prefix and serial share 16 digits, so a longer prefix leaves less room.
  // Too large is an error: truncating would reuse another unit's number.
  const room = 16 - companyPrefix.length;
  const serial = String(serialReference);
  if (serial.length > room) {
    throw new RangeError(
      `serialReference must fit in ${room} digits after a ${companyPrefix.length}-digit company prefix, received ${serialReference}`,
    );
  }
  const data = String(extensionDigit) + companyPrefix + serial.padStart(room, "0");

  // GS1 mod-10: weights 3, 1, 3, 1 ... from the rightmost data digit leftwards.
  // validation.gtin does the same sum but only exports validateGtin, which
  // takes 8 to 14 digits, not an 18-digit SSCC.
  let total = 0;
  let weight = 3;
  for (let i = data.length - 1; i >= 0; i--) {
    total += (data.charCodeAt(i) - 48) * weight;
    weight = weight === 3 ? 1 : 3;
  }
  return data + String((10 - (total % 10)) % 10);
}