feat(bookings): add transaction document uploads for bank payments

Add payment method selection (Cash/Bank) to booking details
Support uploading, downloading, and deleting transaction documents
Update database schema and API endpoints to handle file storage
This commit is contained in:
2026-05-09 12:56:32 +08:00
parent 3710216346
commit b64a2b4c1c
14 changed files with 888 additions and 31 deletions

View File

@@ -1,7 +1,8 @@
import type { BookingCapacitySettings, BookingMode, PublicBooking, TicketType } from '~~/shared/booking'
import type { BookingCapacitySettings, BookingMode, PaymentMethod, PublicBooking, TicketType } from '~~/shared/booking'
import {
formatBookingCurrency
formatBookingCurrency,
isPaymentMethod
} from '~~/shared/booking'
import { hasValidFullName, isValidPhoneNumber, normalizeFullName, normalizePhoneNumber } from '~~/shared/auth'
import { resolveLocale } from '~~/shared/i18n'
@@ -49,12 +50,14 @@ export function parseUpdateBookingDetailsInput(body: {
bookingMode?: BookingMode | string | null
quantity?: number
ticketType?: TicketType
paymentMethod?: PaymentMethod | string | null
remark?: string | null
}) {
const customerName = normalizeFullName(body.customerName || '')
const customerPhone = normalizePhoneNumber(body.customerPhone || '')
const bookingMode = typeof body.bookingMode === 'string' ? body.bookingMode.trim().toLowerCase() : body.bookingMode
const ticketType = typeof body.ticketType === 'string' ? body.ticketType.trim().toLowerCase() : body.ticketType
const paymentMethod = typeof body.paymentMethod === 'string' ? body.paymentMethod.trim().toLowerCase() : body.paymentMethod
const quantity = Number(body.quantity)
const remark = typeof body.remark === 'string' ? body.remark.trim() : ''
@@ -63,6 +66,7 @@ export function parseUpdateBookingDetailsInput(body: {
assertBadRequest(typeof bookingMode === 'string' && bookingMode.length > 0, 'Booking mode is required')
assertBadRequest(Number.isInteger(quantity) && quantity >= 1, 'Quantity must be a whole number of at least 1')
assertBadRequest(typeof ticketType === 'string' && ticketType.trim().length > 0, 'Ticket category is required')
assertBadRequest(isPaymentMethod(paymentMethod), 'Payment method must be Cash or Bank')
assertBadRequest(remark.length <= 1000, 'Remark must be 1,000 characters or fewer')
return {
@@ -71,6 +75,7 @@ export function parseUpdateBookingDetailsInput(body: {
bookingMode,
quantity,
ticketType,
paymentMethod,
remark: remark || null
}
}