// Walk-in (door) pricing. // // An event may set a separate price for people who buy at the door. It is an // internal number: staff charge it on the door screen, but it never appears on // the public event page, listings or JSON-LD, so every public serializer must // drop it (see omitWalkInPrice). // // walkInPrice null -> not set, walk-ins pay the regular ticket price // walkInPrice 0 -> free walk-in (a real value, distinct from null) // walkInPrice n -> walk-ins pay n, in the event's currency export type WalkInPriceSource = 'walk_in' | 'ticket'; /** Roles that may see an event's walk-in price: event managers and door staff. */ export const WALK_IN_PRICE_ROLES = ['admin', 'organizer', 'staff'] as const; export function canSeeWalkInPrice(role: string | null | undefined): boolean { return !!role && (WALK_IN_PRICE_ROLES as readonly string[]).includes(role); } /** Postgres decimals arrive as strings; null/undefined/garbage stay null. */ export function parseWalkInPrice(value: unknown): number | null { if (value === null || value === undefined || value === '') return null; const n = typeof value === 'string' ? parseFloat(value) : Number(value); return Number.isFinite(n) ? n : null; } /** The per-ticket price a walk-in is charged, and where it came from. */ export function resolveWalkInPrice(event: { price: unknown; walkInPrice?: unknown }): { unitPrice: number; source: WalkInPriceSource; } { const walkIn = parseWalkInPrice(event.walkInPrice); if (walkIn !== null) return { unitPrice: walkIn, source: 'walk_in' }; const price = typeof event.price === 'string' ? parseFloat(event.price) : Number(event.price); return { unitPrice: Number.isFinite(price) ? price : 0, source: 'ticket' }; } /** Copy of an event row without its walk-in price, for anything a non-staff caller can read. */ export function omitWalkInPrice | null | undefined>(event: T): T { if (!event) return event; const { walkInPrice: _omitted, ...rest } = event as Record; return rest as T; }