/** * Goods Received Note number: `GRN----`. * * The GRN is mapped to the goods OWNER (the booking's customer / consignee) for * both import and export, so a note is identifiable by who owns the cargo * without opening it. The trailing reference slice stays as the uniqueness * anchor — one owner can have several bookings received on the same day. * Owner-less receipts (manual walk-ins with no booking) fall back to the * original `GRN---` form. * * Shared so a GRN raised at a load/unload facility is indistinguishable from one * raised in a warehouse — the two live in different tables * (facility_handling_events vs warehouse_inventory), and a second generator would * eventually let their formats drift apart. */ export function generateGrnNumber( direction: string, referenceId: string, date: Date, ownerName?: string | null, ): string { const stamp = date.toISOString().slice(0, 10).replace(/-/g, ''); const suffix = referenceId.replace(/-/g, '').slice(0, 8).toUpperCase(); const owner = grnOwnerSlug(ownerName); const base = `GRN-${direction.toUpperCase()}-${stamp}`; return owner ? `${base}-${owner}-${suffix}` : `${base}-${suffix}`; } /** * Owner name → GRN-safe token: letters/digits only, upper-cased, capped so a * long company name can't run away with the number. Null when there is nothing * usable, which drops the segment rather than emitting an empty `--`. */ export function grnOwnerSlug(ownerName?: string | null): string | null { const slug = (ownerName ?? '') .normalize('NFKD') .replace(/[^a-zA-Z0-9]+/g, '') .toUpperCase() .slice(0, 12); return slug || null; }