import { randomUUID } from 'node:crypto' import type { BookingCapacitySettings, BookingInventorySummary, BookingMode, BookingStatus, PublicBooking, TicketType } from '~~/shared/booking' import { calculateBookingInventorySummary, isBookingStatus } from '~~/shared/booking' import { randomToken, toIsoString } from './base64url' import { ensureDatabaseReady } from './db-init' import { getSqlClient } from './postgres' type DbBookingRow = { id: string confirmation_token: string customer_name: string customer_phone: string booking_mode: BookingMode quantity: number | string seat_count: number | string ticket_type: TicketType unit_price: number | string total_price: number | string person_in_charge_id: string person_in_charge_name: string person_in_charge_phone_number: string status: BookingStatus | string created_at: Date | string confirmed_at: Date | string | null } type DbBookingSettingsRow = { total_tables: number | string | null updated_at: Date | string } function parseInteger(value: number | string) { return typeof value === 'number' ? value : Number.parseInt(value, 10) } function mapBooking(row: DbBookingRow): PublicBooking { return { id: row.id, confirmationToken: row.confirmation_token, customerName: row.customer_name, customerPhone: row.customer_phone, bookingMode: row.booking_mode, quantity: parseInteger(row.quantity), seatCount: parseInteger(row.seat_count), ticketType: row.ticket_type, unitPrice: parseInteger(row.unit_price), totalPrice: parseInteger(row.total_price), personInChargeId: row.person_in_charge_id, personInChargeName: row.person_in_charge_name, personInChargePhoneNumber: row.person_in_charge_phone_number, status: isBookingStatus(row.status) ? row.status : 'pending', createdAt: toIsoString(row.created_at) ?? new Date().toISOString(), confirmedAt: toIsoString(row.confirmed_at) } } function mapBookingCapacitySettings(row: DbBookingSettingsRow | undefined): BookingCapacitySettings { if (!row) { return { totalTables: null, totalSeats: null, updatedAt: null } } return { totalTables: row.total_tables === null ? null : parseInteger(row.total_tables), updatedAt: toIsoString(row.updated_at) } } export async function createBooking(input: { customerName: string customerPhone: string bookingMode: BookingMode quantity: number seatCount: number ticketType: TicketType unitPrice: number totalPrice: number personInChargeId: string personInChargeName: string personInChargePhoneNumber: string }) { await ensureDatabaseReady() const sql = getSqlClient() const confirmationToken = randomToken(24) const [row] = await sql` insert into bookings ( id, confirmation_token, customer_name, customer_phone, booking_mode, quantity, seat_count, ticket_type, unit_price, total_price, person_in_charge_id, person_in_charge_name, person_in_charge_phone_number, status ) values ( ${randomUUID()}, ${confirmationToken}, ${input.customerName}, ${input.customerPhone}, ${input.bookingMode}, ${input.quantity}, ${input.seatCount}, ${input.ticketType}, ${input.unitPrice}, ${input.totalPrice}, ${input.personInChargeId}, ${input.personInChargeName}, ${input.personInChargePhoneNumber}, 'pending' ) returning id, confirmation_token, customer_name, customer_phone, booking_mode, quantity, seat_count, ticket_type, unit_price, total_price, person_in_charge_id, person_in_charge_name, person_in_charge_phone_number, status, created_at, confirmed_at ` return { booking: mapBooking(row), confirmationToken } } export async function getBookingByConfirmationToken(confirmationToken: string): Promise { await ensureDatabaseReady() const sql = getSqlClient() const [row] = await sql` select id, confirmation_token, customer_name, customer_phone, booking_mode, quantity, seat_count, ticket_type, unit_price, total_price, person_in_charge_id, person_in_charge_name, person_in_charge_phone_number, status, created_at, confirmed_at from bookings where confirmation_token = ${confirmationToken} limit 1 ` return row ? mapBooking(row) : null } export async function listBookings(options?: { personInChargeId?: string }): Promise { await ensureDatabaseReady() const sql = getSqlClient() const rows = options?.personInChargeId ? await sql` select id, confirmation_token, customer_name, customer_phone, booking_mode, quantity, seat_count, ticket_type, unit_price, total_price, person_in_charge_id, person_in_charge_name, person_in_charge_phone_number, status, created_at, confirmed_at from bookings where person_in_charge_id = ${options.personInChargeId} order by created_at desc ` : await sql` select id, confirmation_token, customer_name, customer_phone, booking_mode, quantity, seat_count, ticket_type, unit_price, total_price, person_in_charge_id, person_in_charge_name, person_in_charge_phone_number, status, created_at, confirmed_at from bookings order by created_at desc ` return rows.map(mapBooking) } export async function getBookingCapacitySettings(): Promise { await ensureDatabaseReady() const sql = getSqlClient() const [row] = await sql` select total_tables, updated_at from booking_settings where id = 'default' limit 1 ` return mapBookingCapacitySettings(row) } export async function updateBookingCapacitySettings(input: { totalTables: number | null }): Promise { await ensureDatabaseReady() const sql = getSqlClient() const [row] = await sql` update booking_settings set total_tables = ${input.totalTables}, updated_at = now() where id = 'default' returning total_tables, updated_at ` return mapBookingCapacitySettings(row) } export async function getBookingInventorySummary(): Promise { const [bookings, settings] = await Promise.all([ listBookings(), getBookingCapacitySettings() ]) return calculateBookingInventorySummary(bookings, settings) } export async function confirmBookingByConfirmationToken(confirmationToken: string): Promise { await ensureDatabaseReady() const sql = getSqlClient() const [row] = await sql` update bookings set status = 'confirmed', confirmed_at = now(), updated_at = now() where confirmation_token = ${confirmationToken} and status = 'pending' returning id, confirmation_token, customer_name, customer_phone, booking_mode, quantity, seat_count, ticket_type, unit_price, total_price, person_in_charge_id, person_in_charge_name, person_in_charge_phone_number, status, created_at, confirmed_at ` if (row) { return mapBooking(row) } return await getBookingByConfirmationToken(confirmationToken) }