export type Branch = 'askim' | 'backaplan'; export interface ValidTableId { valid: true; branch: Branch; tableNumber: string; tableIdentifier: string; } export interface InvalidTableId { valid: false; reason: 'missing' | 'format' | 'range'; } export type ParsedTableId = ValidTableId | InvalidTableId; const TABLE_ID_PATTERN = /^(backaplan|askim)-([0-9]{3})$/; const MIN_TABLE = 1; const MAX_TABLE = 40; /** Domain rule: parse and validate QR table identifiers. */ export function parseTableId(raw: string | null | undefined): ParsedTableId { if (!raw) { return { valid: false, reason: 'missing' }; } const match = raw.match(TABLE_ID_PATTERN); if (!match) { return { valid: false, reason: 'format' }; } const branch = match[1] as Branch; const tableNum = parseInt(match[2], 10); if (tableNum < MIN_TABLE || tableNum > MAX_TABLE) { return { valid: false, reason: 'range' }; } return { valid: true, branch, tableNumber: match[2], tableIdentifier: raw, }; }