feat(booking): move event and ticket configuration to database
Replace hardcoded event details and ticket types with dynamic DB records Add booking-config API endpoint to serve active event settings
This commit is contained in:
@@ -1,17 +1,21 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import type {
|
||||
BookingModeOption,
|
||||
BookingCapacitySettings,
|
||||
BookingInventorySummary,
|
||||
DinnerEvent,
|
||||
BookingMode,
|
||||
BookingStatus,
|
||||
PublicBookingConfig,
|
||||
PublicBooking,
|
||||
PublicBookingSeat,
|
||||
ReceiptBooking,
|
||||
TicketCatalogItem,
|
||||
TicketType
|
||||
} from '~~/shared/booking'
|
||||
|
||||
import { calculateBookingInventorySummary, isBookingMode, isBookingStatus } from '~~/shared/booking'
|
||||
import { calculateBookingInventorySummary, getBookingStatusLabel, isBookingStatus } from '~~/shared/booking'
|
||||
|
||||
import { randomToken, toIsoString } from './base64url'
|
||||
import { ensureDatabaseReady } from './db-init'
|
||||
@@ -21,18 +25,30 @@ type DbBookingRow = {
|
||||
id: string
|
||||
confirmation_token: string
|
||||
receipt_token: string
|
||||
event_id: string
|
||||
event_title: string
|
||||
event_date_label: string
|
||||
event_time_label: string
|
||||
event_venue: string
|
||||
customer_name: string
|
||||
customer_phone: string
|
||||
booking_mode_id: string | null
|
||||
booking_mode: string
|
||||
booking_mode_label: string | null
|
||||
booking_mode_seats_per_unit: number | string | null
|
||||
quantity: number | string
|
||||
seat_count: number | string
|
||||
ticket_type: TicketType
|
||||
ticket_type_id: string | null
|
||||
ticket_type: string
|
||||
ticket_label: string | null
|
||||
ticket_description: string | null
|
||||
unit_price: number | string
|
||||
total_price: number | string
|
||||
person_in_charge_id: string
|
||||
person_in_charge_name: string
|
||||
person_in_charge_phone_number: string
|
||||
person_in_charge_name: string | null
|
||||
person_in_charge_phone_number: string | null
|
||||
status: BookingStatus | string
|
||||
status_label: string | null
|
||||
created_at: Date | string
|
||||
confirmed_at: Date | string | null
|
||||
}
|
||||
@@ -48,56 +64,175 @@ type DbBookingSeatRow = {
|
||||
updated_at: Date | string
|
||||
}
|
||||
|
||||
type DbBookingSeatWithBookingRow = DbBookingSeatRow & {
|
||||
type DbBookingSeatWithBookingRow = DbBookingSeatRow & Omit<DbBookingRow, 'id' | 'created_at'> & {
|
||||
booking_id: string
|
||||
confirmation_token: string
|
||||
receipt_token: string
|
||||
customer_name: string
|
||||
customer_phone: string
|
||||
booking_mode: string
|
||||
quantity: number | string
|
||||
seat_count: number | string
|
||||
ticket_type: TicketType
|
||||
unit_price: number | string
|
||||
total_price: number | string
|
||||
status: BookingStatus | string
|
||||
booking_created_at: Date | string
|
||||
confirmed_at: Date | string | null
|
||||
}
|
||||
|
||||
type DbBookingSettingsRow = {
|
||||
event_id: string
|
||||
total_tables: number | string | null
|
||||
total_seats: number | string | null
|
||||
updated_at: Date | string
|
||||
}
|
||||
|
||||
type DbDinnerEventRow = {
|
||||
id: string
|
||||
title: string
|
||||
date_label: string
|
||||
time_label: string
|
||||
venue: string
|
||||
}
|
||||
|
||||
type DbBookingModeOptionRow = {
|
||||
id: string
|
||||
event_id: string
|
||||
code: string
|
||||
label: string
|
||||
quantity_label: string
|
||||
seats_per_unit: number | string
|
||||
sort_order: number | string
|
||||
}
|
||||
|
||||
type DbTicketCatalogItemRow = {
|
||||
id: string
|
||||
event_id: string
|
||||
code: string
|
||||
label: string
|
||||
description: string
|
||||
price: number | string
|
||||
sort_order: number | string
|
||||
}
|
||||
|
||||
export interface BookingModeOptionRecord extends BookingModeOption {
|
||||
eventId: string
|
||||
}
|
||||
|
||||
export interface TicketCatalogItemRecord extends TicketCatalogItem {
|
||||
eventId: string
|
||||
}
|
||||
|
||||
function bookingSelectColumns(sql: any) {
|
||||
return sql`
|
||||
bookings.id,
|
||||
bookings.confirmation_token,
|
||||
bookings.receipt_token,
|
||||
dinner_events.id as event_id,
|
||||
dinner_events.title as event_title,
|
||||
dinner_events.date_label as event_date_label,
|
||||
dinner_events.time_label as event_time_label,
|
||||
dinner_events.venue as event_venue,
|
||||
bookings.customer_name,
|
||||
bookings.customer_phone,
|
||||
bookings.booking_mode_id,
|
||||
coalesce(booking_modes.code, bookings.booking_mode) as booking_mode,
|
||||
booking_modes.label as booking_mode_label,
|
||||
booking_modes.seats_per_unit as booking_mode_seats_per_unit,
|
||||
bookings.quantity,
|
||||
bookings.seat_count,
|
||||
bookings.ticket_type_id,
|
||||
coalesce(ticket_types.code, bookings.ticket_type) as ticket_type,
|
||||
ticket_types.label as ticket_label,
|
||||
ticket_types.description as ticket_description,
|
||||
bookings.unit_price,
|
||||
bookings.total_price,
|
||||
bookings.person_in_charge_id,
|
||||
coalesce(users.full_name, bookings.person_in_charge_name) as person_in_charge_name,
|
||||
coalesce(users.phone_number, bookings.person_in_charge_phone_number) as person_in_charge_phone_number,
|
||||
bookings.status,
|
||||
booking_statuses.label as status_label,
|
||||
bookings.created_at,
|
||||
bookings.confirmed_at
|
||||
`
|
||||
}
|
||||
|
||||
function bookingJoins(sql: any) {
|
||||
return sql`
|
||||
inner join dinner_events on dinner_events.id = bookings.event_id
|
||||
left join booking_modes on booking_modes.id = bookings.booking_mode_id
|
||||
left join ticket_types on ticket_types.id = bookings.ticket_type_id
|
||||
left join users on users.id = bookings.person_in_charge_id
|
||||
left join booking_statuses on booking_statuses.code = bookings.status
|
||||
`
|
||||
}
|
||||
|
||||
function parseInteger(value: number | string) {
|
||||
return typeof value === 'number' ? value : Number.parseInt(value, 10)
|
||||
}
|
||||
|
||||
function normalizeBookingMode(value: string): BookingMode {
|
||||
return isBookingMode(value) ? value : 'seat'
|
||||
function mapDinnerEvent(row: DbDinnerEventRow): DinnerEvent {
|
||||
return {
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
dateLabel: row.date_label,
|
||||
timeLabel: row.time_label,
|
||||
venue: row.venue
|
||||
}
|
||||
}
|
||||
|
||||
function mapDinnerEventFromBooking(row: DbBookingRow | DbBookingSeatWithBookingRow): DinnerEvent {
|
||||
return {
|
||||
id: row.event_id,
|
||||
title: row.event_title,
|
||||
dateLabel: row.event_date_label,
|
||||
timeLabel: row.event_time_label,
|
||||
venue: row.event_venue
|
||||
}
|
||||
}
|
||||
|
||||
function mapBookingModeOption(row: DbBookingModeOptionRow): BookingModeOptionRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
eventId: row.event_id,
|
||||
value: row.code,
|
||||
label: row.label,
|
||||
quantityLabel: row.quantity_label,
|
||||
seatsPerUnit: parseInteger(row.seats_per_unit),
|
||||
sortOrder: parseInteger(row.sort_order)
|
||||
}
|
||||
}
|
||||
|
||||
function mapTicketCatalogItem(row: DbTicketCatalogItemRow): TicketCatalogItemRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
eventId: row.event_id,
|
||||
value: row.code,
|
||||
label: row.label,
|
||||
description: row.description,
|
||||
price: parseInteger(row.price),
|
||||
sortOrder: parseInteger(row.sort_order)
|
||||
}
|
||||
}
|
||||
|
||||
function mapBooking(row: DbBookingRow): PublicBooking {
|
||||
const seatCount = parseInteger(row.seat_count)
|
||||
const status = isBookingStatus(row.status) ? row.status : 'pending'
|
||||
const ticketType = row.ticket_type
|
||||
const bookingMode = row.booking_mode
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
confirmationToken: row.confirmation_token,
|
||||
receiptToken: row.receipt_token,
|
||||
event: mapDinnerEventFromBooking(row),
|
||||
customerName: row.customer_name,
|
||||
customerPhone: row.customer_phone,
|
||||
bookingMode: normalizeBookingMode(row.booking_mode),
|
||||
bookingModeId: row.booking_mode_id,
|
||||
bookingMode,
|
||||
bookingModeLabel: row.booking_mode_label || bookingMode,
|
||||
quantity: parseInteger(row.quantity),
|
||||
seatCount,
|
||||
ticketType: row.ticket_type,
|
||||
ticketTypeId: row.ticket_type_id,
|
||||
ticketType,
|
||||
ticketLabel: row.ticket_label || ticketType.toUpperCase(),
|
||||
ticketDescription: row.ticket_description,
|
||||
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',
|
||||
personInChargeName: row.person_in_charge_name || '',
|
||||
personInChargePhoneNumber: row.person_in_charge_phone_number || '',
|
||||
status,
|
||||
statusLabel: row.status_label || getBookingStatusLabel(status),
|
||||
createdAt: toIsoString(row.created_at) ?? new Date().toISOString(),
|
||||
confirmedAt: toIsoString(row.confirmed_at)
|
||||
}
|
||||
@@ -105,24 +240,59 @@ function mapBooking(row: DbBookingRow): PublicBooking {
|
||||
|
||||
function mapReceiptBooking(row: DbBookingRow | DbBookingSeatWithBookingRow): ReceiptBooking {
|
||||
const seatCount = parseInteger(row.seat_count)
|
||||
const status = isBookingStatus(row.status) ? row.status : 'pending'
|
||||
const ticketType = row.ticket_type
|
||||
const bookingMode = row.booking_mode
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
receiptToken: row.receipt_token,
|
||||
event: mapDinnerEventFromBooking(row),
|
||||
customerName: row.customer_name,
|
||||
customerPhone: row.customer_phone,
|
||||
bookingMode: normalizeBookingMode(row.booking_mode),
|
||||
bookingModeId: row.booking_mode_id,
|
||||
bookingMode,
|
||||
bookingModeLabel: row.booking_mode_label || bookingMode,
|
||||
quantity: parseInteger(row.quantity),
|
||||
seatCount,
|
||||
ticketType: row.ticket_type,
|
||||
ticketTypeId: row.ticket_type_id,
|
||||
ticketType,
|
||||
ticketLabel: row.ticket_label || ticketType.toUpperCase(),
|
||||
ticketDescription: row.ticket_description,
|
||||
unitPrice: parseInteger(row.unit_price),
|
||||
totalPrice: parseInteger(row.total_price),
|
||||
status: isBookingStatus(row.status) ? row.status : 'pending',
|
||||
status,
|
||||
statusLabel: row.status_label || getBookingStatusLabel(status),
|
||||
createdAt: toIsoString('booking_created_at' in row ? row.booking_created_at : row.created_at) ?? new Date().toISOString(),
|
||||
confirmedAt: toIsoString(row.confirmed_at)
|
||||
}
|
||||
}
|
||||
|
||||
function mapPublicBookingToReceiptBooking(booking: PublicBooking): ReceiptBooking {
|
||||
return {
|
||||
id: booking.id,
|
||||
receiptToken: booking.receiptToken,
|
||||
event: booking.event,
|
||||
customerName: booking.customerName,
|
||||
customerPhone: booking.customerPhone,
|
||||
bookingModeId: booking.bookingModeId,
|
||||
bookingMode: booking.bookingMode,
|
||||
bookingModeLabel: booking.bookingModeLabel,
|
||||
quantity: booking.quantity,
|
||||
seatCount: booking.seatCount,
|
||||
ticketTypeId: booking.ticketTypeId,
|
||||
ticketType: booking.ticketType,
|
||||
ticketLabel: booking.ticketLabel,
|
||||
ticketDescription: booking.ticketDescription,
|
||||
unitPrice: booking.unitPrice,
|
||||
totalPrice: booking.totalPrice,
|
||||
status: booking.status,
|
||||
statusLabel: booking.statusLabel,
|
||||
createdAt: booking.createdAt,
|
||||
confirmedAt: booking.confirmedAt
|
||||
}
|
||||
}
|
||||
|
||||
function mapBookingSeat(row: DbBookingSeatRow): PublicBookingSeat {
|
||||
return {
|
||||
id: row.id,
|
||||
@@ -144,9 +314,7 @@ function mapBookingCapacitySettings(row: DbBookingSettingsRow | undefined): Book
|
||||
}
|
||||
}
|
||||
|
||||
const totalSeats = row.total_seats === null
|
||||
? (row.total_tables === null ? null : parseInteger(row.total_tables) * 10)
|
||||
: parseInteger(row.total_seats)
|
||||
const totalSeats = row.total_seats === null ? null : parseInteger(row.total_seats)
|
||||
|
||||
return {
|
||||
totalSeats,
|
||||
@@ -154,6 +322,115 @@ function mapBookingCapacitySettings(row: DbBookingSettingsRow | undefined): Book
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPublicBookingConfig(): Promise<PublicBookingConfig> {
|
||||
await ensureDatabaseReady()
|
||||
const sql = getSqlClient()
|
||||
|
||||
const [event] = await sql<DbDinnerEventRow[]>`
|
||||
select
|
||||
id,
|
||||
title,
|
||||
date_label,
|
||||
time_label,
|
||||
venue
|
||||
from dinner_events
|
||||
where is_active = true
|
||||
order by sort_order asc, created_at asc
|
||||
limit 1
|
||||
`
|
||||
|
||||
if (!event) {
|
||||
throw new Error('No active dinner event is configured.')
|
||||
}
|
||||
|
||||
const [bookingModes, ticketCatalog] = await Promise.all([
|
||||
sql<DbBookingModeOptionRow[]>`
|
||||
select
|
||||
id,
|
||||
event_id,
|
||||
code,
|
||||
label,
|
||||
quantity_label,
|
||||
seats_per_unit,
|
||||
sort_order
|
||||
from booking_modes
|
||||
where event_id = ${event.id}
|
||||
and is_active = true
|
||||
order by sort_order asc, label asc
|
||||
`,
|
||||
sql<DbTicketCatalogItemRow[]>`
|
||||
select
|
||||
id,
|
||||
event_id,
|
||||
code,
|
||||
label,
|
||||
description,
|
||||
price,
|
||||
sort_order
|
||||
from ticket_types
|
||||
where event_id = ${event.id}
|
||||
and is_active = true
|
||||
order by sort_order asc, label asc
|
||||
`
|
||||
])
|
||||
|
||||
return {
|
||||
event: mapDinnerEvent(event),
|
||||
bookingModes: bookingModes.map(mapBookingModeOption),
|
||||
ticketCatalog: ticketCatalog.map(mapTicketCatalogItem)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getActiveBookingModeOptionByCode(code: string): Promise<BookingModeOptionRecord | null> {
|
||||
await ensureDatabaseReady()
|
||||
const sql = getSqlClient()
|
||||
|
||||
const [row] = await sql<DbBookingModeOptionRow[]>`
|
||||
select
|
||||
booking_modes.id,
|
||||
booking_modes.event_id,
|
||||
booking_modes.code,
|
||||
booking_modes.label,
|
||||
booking_modes.quantity_label,
|
||||
booking_modes.seats_per_unit,
|
||||
booking_modes.sort_order
|
||||
from booking_modes
|
||||
inner join dinner_events on dinner_events.id = booking_modes.event_id
|
||||
where dinner_events.is_active = true
|
||||
and booking_modes.is_active = true
|
||||
and booking_modes.code = ${code}
|
||||
order by booking_modes.sort_order asc
|
||||
limit 1
|
||||
`
|
||||
|
||||
return row ? mapBookingModeOption(row) : null
|
||||
}
|
||||
|
||||
export async function getActiveTicketCatalogItemByCode(code: string): Promise<TicketCatalogItemRecord | null> {
|
||||
await ensureDatabaseReady()
|
||||
const sql = getSqlClient()
|
||||
|
||||
const [row] = await sql<DbTicketCatalogItemRow[]>`
|
||||
select
|
||||
ticket_types.id,
|
||||
ticket_types.event_id,
|
||||
ticket_types.code,
|
||||
ticket_types.label,
|
||||
ticket_types.description,
|
||||
ticket_types.price,
|
||||
ticket_types.sort_order
|
||||
from ticket_types
|
||||
inner join dinner_events on dinner_events.id = ticket_types.event_id
|
||||
where dinner_events.is_active = true
|
||||
and ticket_types.is_active = true
|
||||
and ticket_types.code = ${code}
|
||||
order by ticket_types.sort_order asc
|
||||
limit 1
|
||||
`
|
||||
|
||||
return row ? mapTicketCatalogItem(row) : null
|
||||
}
|
||||
|
||||
async function insertBookingSeats(
|
||||
tx: ReturnType<typeof getSqlClient>,
|
||||
bookingId: string,
|
||||
@@ -178,17 +455,18 @@ async function insertBookingSeats(
|
||||
}
|
||||
|
||||
export async function createBooking(input: {
|
||||
eventId: string
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
bookingModeId: string
|
||||
bookingMode: BookingMode
|
||||
quantity: number
|
||||
seatCount: number
|
||||
ticketTypeId: string
|
||||
ticketType: TicketType
|
||||
unitPrice: number
|
||||
totalPrice: number
|
||||
personInChargeId: string
|
||||
personInChargeName: string
|
||||
personInChargePhoneNumber: string
|
||||
}) {
|
||||
await ensureDatabaseReady()
|
||||
const sql = getSqlClient()
|
||||
@@ -198,58 +476,48 @@ export async function createBooking(input: {
|
||||
|
||||
const row = await sql.begin(async (tx) => {
|
||||
const [createdBooking] = await tx<DbBookingRow[]>`
|
||||
with inserted_booking as (
|
||||
insert into bookings (
|
||||
id,
|
||||
confirmation_token,
|
||||
receipt_token,
|
||||
event_id,
|
||||
customer_name,
|
||||
customer_phone,
|
||||
booking_mode_id,
|
||||
booking_mode,
|
||||
quantity,
|
||||
seat_count,
|
||||
ticket_type_id,
|
||||
ticket_type,
|
||||
unit_price,
|
||||
total_price,
|
||||
person_in_charge_id,
|
||||
person_in_charge_name,
|
||||
person_in_charge_phone_number,
|
||||
status
|
||||
)
|
||||
values (
|
||||
${bookingId},
|
||||
${confirmationToken},
|
||||
${receiptToken},
|
||||
${input.eventId},
|
||||
${input.customerName},
|
||||
${input.customerPhone},
|
||||
${input.bookingModeId},
|
||||
${input.bookingMode},
|
||||
${input.quantity},
|
||||
${input.seatCount},
|
||||
${input.ticketTypeId},
|
||||
${input.ticketType},
|
||||
${input.unitPrice},
|
||||
${input.totalPrice},
|
||||
${input.personInChargeId},
|
||||
${input.personInChargeName},
|
||||
${input.personInChargePhoneNumber},
|
||||
'pending'
|
||||
)
|
||||
returning
|
||||
id,
|
||||
confirmation_token,
|
||||
receipt_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
|
||||
returning *
|
||||
)
|
||||
select ${bookingSelectColumns(tx)}
|
||||
from inserted_booking as bookings
|
||||
${bookingJoins(tx)}
|
||||
`
|
||||
|
||||
await insertBookingSeats(tx, bookingId, input.seatCount)
|
||||
@@ -269,26 +537,10 @@ export async function getBookingByConfirmationToken(confirmationToken: string):
|
||||
const sql = getSqlClient()
|
||||
|
||||
const [row] = await sql<DbBookingRow[]>`
|
||||
select
|
||||
id,
|
||||
confirmation_token,
|
||||
receipt_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
|
||||
select ${bookingSelectColumns(sql)}
|
||||
from bookings
|
||||
where confirmation_token = ${confirmationToken}
|
||||
${bookingJoins(sql)}
|
||||
where bookings.confirmation_token = ${confirmationToken}
|
||||
limit 1
|
||||
`
|
||||
|
||||
@@ -300,26 +552,10 @@ export async function getBookingByReceiptToken(receiptToken: string): Promise<Pu
|
||||
const sql = getSqlClient()
|
||||
|
||||
const [row] = await sql<DbBookingRow[]>`
|
||||
select
|
||||
id,
|
||||
confirmation_token,
|
||||
receipt_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
|
||||
select ${bookingSelectColumns(sql)}
|
||||
from bookings
|
||||
where receipt_token = ${receiptToken}
|
||||
${bookingJoins(sql)}
|
||||
where bookings.receipt_token = ${receiptToken}
|
||||
limit 1
|
||||
`
|
||||
|
||||
@@ -334,49 +570,19 @@ export async function listBookings(options?: {
|
||||
|
||||
const rows = options?.personInChargeId
|
||||
? await sql<DbBookingRow[]>`
|
||||
select
|
||||
id,
|
||||
confirmation_token,
|
||||
receipt_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
|
||||
select ${bookingSelectColumns(sql)}
|
||||
from bookings
|
||||
where person_in_charge_id = ${options.personInChargeId}
|
||||
order by created_at desc
|
||||
${bookingJoins(sql)}
|
||||
where dinner_events.is_active = true
|
||||
and bookings.person_in_charge_id = ${options.personInChargeId}
|
||||
order by bookings.created_at desc
|
||||
`
|
||||
: await sql<DbBookingRow[]>`
|
||||
select
|
||||
id,
|
||||
confirmation_token,
|
||||
receipt_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
|
||||
select ${bookingSelectColumns(sql)}
|
||||
from bookings
|
||||
order by created_at desc
|
||||
${bookingJoins(sql)}
|
||||
where dinner_events.is_active = true
|
||||
order by bookings.created_at desc
|
||||
`
|
||||
|
||||
return rows.map(mapBooking)
|
||||
@@ -417,25 +623,7 @@ export async function getBookingReceiptByReceiptToken(receiptToken: string): Pro
|
||||
const seats = await listBookingSeats(booking.id)
|
||||
|
||||
return {
|
||||
booking: mapReceiptBooking({
|
||||
id: booking.id,
|
||||
confirmation_token: booking.confirmationToken,
|
||||
receipt_token: booking.receiptToken,
|
||||
customer_name: booking.customerName,
|
||||
customer_phone: booking.customerPhone,
|
||||
booking_mode: booking.bookingMode,
|
||||
quantity: booking.quantity,
|
||||
seat_count: booking.seatCount,
|
||||
ticket_type: booking.ticketType,
|
||||
unit_price: booking.unitPrice,
|
||||
total_price: booking.totalPrice,
|
||||
person_in_charge_id: booking.personInChargeId,
|
||||
person_in_charge_name: booking.personInChargeName,
|
||||
person_in_charge_phone_number: booking.personInChargePhoneNumber,
|
||||
status: booking.status,
|
||||
created_at: booking.createdAt,
|
||||
confirmed_at: booking.confirmedAt
|
||||
}),
|
||||
booking: mapPublicBookingToReceiptBooking(booking),
|
||||
seats
|
||||
}
|
||||
}
|
||||
@@ -460,19 +648,35 @@ export async function getSeatReceiptBySeatToken(seatToken: string): Promise<{
|
||||
bookings.id as booking_id,
|
||||
bookings.confirmation_token,
|
||||
bookings.receipt_token,
|
||||
dinner_events.id as event_id,
|
||||
dinner_events.title as event_title,
|
||||
dinner_events.date_label as event_date_label,
|
||||
dinner_events.time_label as event_time_label,
|
||||
dinner_events.venue as event_venue,
|
||||
bookings.customer_name,
|
||||
bookings.customer_phone,
|
||||
bookings.booking_mode,
|
||||
bookings.booking_mode_id,
|
||||
coalesce(booking_modes.code, bookings.booking_mode) as booking_mode,
|
||||
booking_modes.label as booking_mode_label,
|
||||
booking_modes.seats_per_unit as booking_mode_seats_per_unit,
|
||||
bookings.quantity,
|
||||
bookings.seat_count,
|
||||
bookings.ticket_type,
|
||||
bookings.ticket_type_id,
|
||||
coalesce(ticket_types.code, bookings.ticket_type) as ticket_type,
|
||||
ticket_types.label as ticket_label,
|
||||
ticket_types.description as ticket_description,
|
||||
bookings.unit_price,
|
||||
bookings.total_price,
|
||||
bookings.person_in_charge_id,
|
||||
coalesce(users.full_name, bookings.person_in_charge_name) as person_in_charge_name,
|
||||
coalesce(users.phone_number, bookings.person_in_charge_phone_number) as person_in_charge_phone_number,
|
||||
bookings.status,
|
||||
booking_statuses.label as status_label,
|
||||
bookings.created_at as booking_created_at,
|
||||
bookings.confirmed_at
|
||||
from booking_seats
|
||||
inner join bookings on bookings.id = booking_seats.booking_id
|
||||
${bookingJoins(sql)}
|
||||
where booking_seats.seat_token = ${seatToken}
|
||||
limit 1
|
||||
`
|
||||
@@ -533,11 +737,14 @@ export async function getBookingCapacitySettings(): Promise<BookingCapacitySetti
|
||||
|
||||
const [row] = await sql<DbBookingSettingsRow[]>`
|
||||
select
|
||||
booking_settings.event_id,
|
||||
total_tables,
|
||||
total_seats,
|
||||
updated_at
|
||||
booking_settings.updated_at
|
||||
from booking_settings
|
||||
where id = 'default'
|
||||
inner join dinner_events on dinner_events.id = booking_settings.event_id
|
||||
where dinner_events.is_active = true
|
||||
order by dinner_events.sort_order asc
|
||||
limit 1
|
||||
`
|
||||
|
||||
@@ -555,11 +762,14 @@ export async function updateBookingCapacitySettings(input: {
|
||||
set
|
||||
total_seats = ${input.totalSeats},
|
||||
updated_at = now()
|
||||
where id = 'default'
|
||||
from dinner_events
|
||||
where booking_settings.event_id = dinner_events.id
|
||||
and dinner_events.is_active = true
|
||||
returning
|
||||
booking_settings.event_id,
|
||||
total_tables,
|
||||
total_seats,
|
||||
updated_at
|
||||
booking_settings.updated_at
|
||||
`
|
||||
|
||||
return mapBookingCapacitySettings(row)
|
||||
@@ -579,31 +789,19 @@ export async function confirmBookingByConfirmationToken(confirmationToken: strin
|
||||
const sql = getSqlClient()
|
||||
|
||||
const [row] = await sql<DbBookingRow[]>`
|
||||
update bookings
|
||||
set
|
||||
status = 'confirmed',
|
||||
confirmed_at = now(),
|
||||
updated_at = now()
|
||||
where confirmation_token = ${confirmationToken}
|
||||
and status = 'pending'
|
||||
returning
|
||||
id,
|
||||
confirmation_token,
|
||||
receipt_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
|
||||
with updated_booking as (
|
||||
update bookings
|
||||
set
|
||||
status = 'confirmed',
|
||||
confirmed_at = now(),
|
||||
updated_at = now()
|
||||
where confirmation_token = ${confirmationToken}
|
||||
and status = 'pending'
|
||||
returning *
|
||||
)
|
||||
select ${bookingSelectColumns(sql)}
|
||||
from updated_booking as bookings
|
||||
${bookingJoins(sql)}
|
||||
`
|
||||
|
||||
if (row) {
|
||||
|
||||
Reference in New Issue
Block a user