Files
dticket.tootaio.com/server/api/bookings/[id]/transaction-document.post.ts
xiaomai b64a2b4c1c 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
2026-05-09 12:56:32 +08:00

87 lines
2.6 KiB
TypeScript

import type { UpdateBookingDetailsResponse } from '~~/shared/booking'
import { getHeader, readMultipartFormData } from 'h3'
import { requireAuth } from '../../../utils/auth'
import {
getBookingById,
replaceBookingTransactionDocument
} from '../../../utils/booking-repository'
import { getRequiredRouteParam, httpError } from '../../../utils/http'
import {
deleteTransactionDocument,
saveTransactionDocument,
TRANSACTION_DOCUMENT_MAX_SIZE,
validateTransactionDocumentUpload
} from '../../../utils/transaction-documents'
export default defineEventHandler(async (event): Promise<UpdateBookingDetailsResponse> => {
const auth = await requireAuth(event)
const bookingId = getRequiredRouteParam(event, 'id', 'Booking ID')
const accessScope = auth.user.role === 'super_admin'
? undefined
: { personInChargeId: auth.user.id }
const booking = await getBookingById(bookingId, accessScope)
if (!booking) {
httpError(404, 'Booking not found')
}
if (booking.paymentMethod !== 'bank') {
httpError(400, 'Transaction document can only be uploaded for Bank payments')
}
const contentType = String(getHeader(event, 'content-type') || '').toLowerCase()
if (!contentType.startsWith('multipart/form-data;')) {
httpError(400, 'Transaction document upload must use multipart form data')
}
const contentLength = Number(getHeader(event, 'content-length') || 0)
if (contentLength > TRANSACTION_DOCUMENT_MAX_SIZE + 1024 * 1024) {
httpError(413, 'Transaction document must be 10MB or smaller')
}
const formData = await readMultipartFormData(event)
const filePart = formData?.find((part) => part.name === 'document' && part.filename)
if (!filePart) {
httpError(400, 'Transaction document is required')
}
const upload = validateTransactionDocumentUpload({
data: filePart.data,
filename: filePart.filename,
contentType: filePart.type
})
const storageName = await saveTransactionDocument(filePart.data, upload.fileType)
try {
const result = await replaceBookingTransactionDocument({
bookingId,
personInChargeId: auth.user.role === 'super_admin' ? undefined : auth.user.id,
originalName: upload.originalName,
storageName,
mimeType: upload.fileType.mimeType,
size: filePart.data.length
})
if (!result) {
await deleteTransactionDocument(storageName)
httpError(404, 'Booking not found')
}
await deleteTransactionDocument(result.previousStorageName)
return {
booking: result.booking
}
} catch (error) {
await deleteTransactionDocument(storageName)
throw error
}
})