import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { optimizeRoute, optimizeRouteBySchedule } from '@/lib/google-maps'; export async function GET(request: NextRequest) { try { const session = await getServerSession(authOptions); if (!session || session.user.role !== 'ADMIN') { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const { searchParams } = new URL(request.url); const driverId = searchParams.get('driverId'); const status = searchParams.get('status'); const date = searchParams.get('date'); const where: any = {}; if (driverId) where.driverId = driverId; if (status) where.status = status; if (date) { const startDate = new Date(date); const endDate = new Date(date); endDate.setDate(endDate.getDate() + 1); where.tourDate = { gte: startDate, lt: endDate, }; } const tours = await prisma.tour.findMany({ where, include: { driver: { select: { id: true, name: true, email: true, phoneNumber: true, vehiclePlate: true, }, }, bookings: { select: { id: true, bookingNumber: true, eventDate: true, eventAddress: true, eventCity: true, eventZip: true, eventLocation: true, customerName: true, setupTimeStart: true, setupTimeLatest: true, }, }, }, orderBy: { tourDate: 'desc', }, }); return NextResponse.json({ tours }); } catch (error: any) { console.error('Tour fetch error:', error); return NextResponse.json( { error: error.message || 'Failed to fetch tours' }, { status: 500 } ); } } export async function POST(request: NextRequest) { try { const session = await getServerSession(authOptions); if (!session || session.user.role !== 'ADMIN') { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const body = await request.json(); const { tourDate, driverId, bookingIds, optimizationType = 'fastest' } = body; if (!tourDate) { return NextResponse.json( { error: 'Tour date is required' }, { status: 400 } ); } const tourNumber = `TOUR-${new Date().getFullYear()}-${Date.now().toString().slice(-6)}`; const tour = await prisma.tour.create({ data: { tourDate: new Date(tourDate), tourNumber, driverId, status: 'PLANNED', }, }); if (bookingIds && bookingIds.length > 0) { await prisma.booking.updateMany({ where: { id: { in: bookingIds }, }, data: { tourId: tour.id, status: 'ASSIGNED', }, }); // Mark setup windows as selected for bookings with flexible setup times for (const bookingId of bookingIds) { const booking = await prisma.booking.findUnique({ where: { id: bookingId }, include: { setupWindows: true }, }); if (booking && booking.setupWindows.length > 0) { const tourDateStr = new Date(tourDate).toISOString().split('T')[0]; const matchingWindow = booking.setupWindows.find((w: any) => { const windowDateStr = new Date(w.setupDate).toISOString().split('T')[0]; return windowDateStr === tourDateStr && !w.selected; }); if (matchingWindow) { await prisma.setupWindow.update({ where: { id: matchingWindow.id }, data: { selected: true }, }); } } } const bookings = await prisma.booking.findMany({ where: { id: { in: bookingIds } }, include: { setupWindows: true }, select: { eventAddress: true, eventCity: true, eventZip: true, setupTimeStart: true, setupTimeLatest: true, setupWindows: true, }, }); // Create TourStops for each booking const fullBookings = await prisma.booking.findMany({ where: { id: { in: bookingIds } }, include: { setupWindows: true }, orderBy: { setupTimeStart: 'asc' }, }); try { // For route optimization, use the selected setup window time if available const stopsWithSetupTimes = bookings.map((booking: any) => { const tourDateStr = new Date(tourDate).toISOString().split('T')[0]; const selectedWindow = booking.setupWindows?.find((w: any) => w.selected); if (selectedWindow) { return { eventAddress: booking.eventAddress, eventCity: booking.eventCity, eventZip: booking.eventZip, setupTimeStart: selectedWindow.setupTimeStart, setupTimeLatest: selectedWindow.setupTimeEnd, }; } return { eventAddress: booking.eventAddress, eventCity: booking.eventCity, eventZip: booking.eventZip, setupTimeStart: booking.setupTimeStart, setupTimeLatest: booking.setupTimeLatest, }; }); const routeData = optimizationType === 'schedule' ? await optimizeRouteBySchedule(stopsWithSetupTimes) : await optimizeRoute(stopsWithSetupTimes); await prisma.tour.update({ where: { id: tour.id }, data: { routeOptimized: routeData as any, totalDistance: routeData.totalDistance, estimatedDuration: routeData.totalDuration, }, }); // Create TourStops based on optimized order const optimizedOrder = routeData.optimizedOrder || fullBookings.map((_, i) => i); for (let i = 0; i < optimizedOrder.length; i++) { const orderIndex = optimizedOrder[i]; const booking = fullBookings[orderIndex]; await prisma.tourStop.create({ data: { tourId: tour.id, bookingId: booking.id, stopOrder: i + 1, stopType: 'DELIVERY', status: 'PENDING', }, }); } } catch (routeError) { console.error('Route optimization error:', routeError); // If route optimization fails, create TourStops in simple order for (let i = 0; i < fullBookings.length; i++) { await prisma.tourStop.create({ data: { tourId: tour.id, bookingId: fullBookings[i].id, stopOrder: i + 1, stopType: 'DELIVERY', status: 'PENDING', }, }); } } } const updatedTour = await prisma.tour.findUnique({ where: { id: tour.id }, include: { driver: true, bookings: true, }, }); return NextResponse.json({ tour: updatedTour }, { status: 201 }); } catch (error: any) { console.error('Tour creation error:', error); return NextResponse.json( { error: error.message || 'Failed to create tour' }, { status: 500 } ); } }