// Far beyond any realistic business use (sequence numbers are a handful of // digits) but small enough that padStart can never be tricked into // allocating a pathologically large string - {SEQ:999999999} in a // carelessly typed format would otherwise try to build a ~1GB string on // every single batch/bottling creation from then on. const MAX_SEQUENCE_PAD_WIDTH = 20; // Placeholders: {YYYY} 4-digit year, {YY} 2-digit year, {MM} month (01-12), // {DD} day (01-31), {SEQ} running sequence unpadded, {SEQ:N} sequence // zero-padded to N digits. Example matching the lastenheft's own sample // ("WH-2026-0001"): format "WH-{YYYY}-{SEQ:4}". // `vars` fills rule-specific placeholders, e.g. { ART: 'WA' } for {ART} in // the raw material format; a placeholder without a value stays as typed. export function formatGeneratedNumber( format: string, sequence: number, date: Date, vars: Record = {}, ): string { const yyyy = String(date.getFullYear()); const yy = yyyy.slice(-2); const mm = String(date.getMonth() + 1).padStart(2, '0'); const dd = String(date.getDate()).padStart(2, '0'); const withVars = Object.entries(vars).reduce( (result, [key, value]) => result.split(`{${key}}`).join(value), format, ); return withVars .replace(/\{YYYY\}/g, yyyy) .replace(/\{YY\}/g, yy) .replace(/\{MM\}/g, mm) .replace(/\{DD\}/g, dd) .replace(/\{SEQ:(\d+)\}/g, (_match, width: string) => String(sequence).padStart( Math.min(Number(width), MAX_SEQUENCE_PAD_WIDTH), '0', ), ) .replace(/\{SEQ\}/g, String(sequence)); }