import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { db } from '@/lib/db'; import { z } from 'zod'; interface RouteContext { params: Promise<{ id: string }>; } // Validation schema for updating booking const updateBookingSchema = z.object({ status: z.enum(['PENDING', 'CONFIRMED', 'CANCELLED', 'COMPLETED', 'NO_SHOW']).optional(), notes: z.string().max(500).optional(), cancelReason: z.string().max(500).optional(), playerNames: z.array(z.string()).optional(), }); // GET /api/bookings/[id] - Get a single booking by ID export async function GET( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'No autorizado' }, { status: 401 } ); } const { id } = await context.params; const booking = await db.booking.findFirst({ where: { id, site: { organizationId: session.user.organizationId, }, }, include: { court: { select: { id: true, name: true, type: true, status: true, pricePerHour: true, features: true, }, }, client: { select: { id: true, firstName: true, lastName: true, email: true, phone: true, level: true, notes: true, }, }, site: { select: { id: true, name: true, slug: true, address: true, phone: true, email: true, timezone: true, }, }, createdBy: { select: { id: true, firstName: true, lastName: true, email: true, }, }, payments: { select: { id: true, amount: true, paymentType: true, reference: true, notes: true, createdAt: true, }, orderBy: { createdAt: 'desc', }, }, }, }); if (!booking) { return NextResponse.json( { error: 'Reserva no encontrada' }, { status: 404 } ); } return NextResponse.json(booking); } catch (error) { console.error('Error fetching booking:', error); return NextResponse.json( { error: 'Error al obtener la reserva' }, { status: 500 } ); } } // PUT /api/bookings/[id] - Update a booking export async function PUT( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'No autorizado' }, { status: 401 } ); } const { id } = await context.params; // Verify booking exists and belongs to user's organization const existingBooking = await db.booking.findFirst({ where: { id, site: { organizationId: session.user.organizationId, }, }, include: { site: true, }, }); if (!existingBooking) { return NextResponse.json( { error: 'Reserva no encontrada' }, { status: 404 } ); } // If user is SITE_ADMIN, verify they have access to this site if (session.user.role === 'SITE_ADMIN' && session.user.siteId !== existingBooking.siteId) { return NextResponse.json( { error: 'No tiene permiso para modificar esta reserva' }, { status: 403 } ); } const body = await request.json(); // Validate input const validationResult = updateBookingSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { error: 'Datos de actualización inválidos', details: validationResult.error.flatten().fieldErrors, }, { status: 400 } ); } const { status, notes, cancelReason, playerNames } = validationResult.data; // Build update data const updateData: { status?: 'PENDING' | 'CONFIRMED' | 'CANCELLED' | 'COMPLETED' | 'NO_SHOW'; notes?: string | null; cancelReason?: string | null; cancelledAt?: Date | null; playerNames?: string[]; } = {}; if (status !== undefined) { updateData.status = status; // If cancelling, set cancellation timestamp if (status === 'CANCELLED') { updateData.cancelledAt = new Date(); if (cancelReason) { updateData.cancelReason = cancelReason; } } } if (notes !== undefined) { updateData.notes = notes || null; } if (playerNames !== undefined) { updateData.playerNames = playerNames; } const booking = await db.booking.update({ where: { id }, data: updateData, include: { court: { select: { id: true, name: true, type: true, pricePerHour: true, }, }, client: { select: { id: true, firstName: true, lastName: true, email: true, phone: true, }, }, site: { select: { id: true, name: true, timezone: true, }, }, }, }); return NextResponse.json(booking); } catch (error) { console.error('Error updating booking:', error); return NextResponse.json( { error: 'Error al actualizar la reserva' }, { status: 500 } ); } } // DELETE /api/bookings/[id] - Cancel/delete a booking export async function DELETE( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'No autorizado' }, { status: 401 } ); } const { id } = await context.params; // Verify booking exists and belongs to user's organization const existingBooking = await db.booking.findFirst({ where: { id, site: { organizationId: session.user.organizationId, }, }, include: { payments: true, }, }); if (!existingBooking) { return NextResponse.json( { error: 'Reserva no encontrada' }, { status: 404 } ); } // If user is SITE_ADMIN, verify they have access to this site if (session.user.role === 'SITE_ADMIN' && session.user.siteId !== existingBooking.siteId) { return NextResponse.json( { error: 'No tiene permiso para cancelar esta reserva' }, { status: 403 } ); } // Check if booking has payments const hasPayments = existingBooking.payments.length > 0; // Parse optional cancel reason from query params or body let cancelReason = 'Cancelada por el administrador'; try { const body = await request.json(); if (body.cancelReason) { cancelReason = body.cancelReason; } } catch { // No body provided, use default reason } if (hasPayments) { // If there are payments, just cancel the booking (soft delete) const booking = await db.booking.update({ where: { id }, data: { status: 'CANCELLED', cancelledAt: new Date(), cancelReason, }, }); return NextResponse.json({ message: 'Reserva cancelada exitosamente', booking, note: 'La reserva tiene pagos asociados, por lo que fue cancelada en lugar de eliminada', }); } else { // If no payments, allow hard delete for pending bookings only if (existingBooking.status === 'PENDING') { await db.booking.delete({ where: { id }, }); return NextResponse.json({ message: 'Reserva eliminada exitosamente', }); } else { // For non-pending bookings, soft delete const booking = await db.booking.update({ where: { id }, data: { status: 'CANCELLED', cancelledAt: new Date(), cancelReason, }, }); return NextResponse.json({ message: 'Reserva cancelada exitosamente', booking, }); } } } catch (error) { console.error('Error deleting booking:', error); return NextResponse.json( { error: 'Error al cancelar la reserva' }, { status: 500 } ); } }