import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; import { z } from 'zod'; import { DistanceCalculator } from '@/lib/distance-calculator'; import { PriceCalculator } from '@/lib/price-calculator'; import { bookingAutomationService } from '@/lib/booking-automation'; const bookingSchema = z.object({ locationSlug: z.string(), model: z.enum(['VINTAGE_SMILE', 'VINTAGE_PHOTOS', 'NOSTALGIE', 'MAGIC_MIRROR']), customerName: z.string().min(2), customerEmail: z.string().email(), customerPhone: z.string().min(5), customerAddress: z.string().optional(), customerCity: z.string().optional(), customerZip: z.string().optional(), invoiceType: z.enum(['PRIVATE', 'BUSINESS']), companyName: z.string().optional(), eventDate: z.string(), eventAddress: z.string().min(5), eventCity: z.string().min(2), eventZip: z.string().min(4), eventLocation: z.string().optional(), setupTimeStart: z.string(), setupTimeLatest: z.string(), dismantleTimeEarliest: z.string().optional(), dismantleTimeLatest: z.string().optional(), notes: z.string().optional(), }); function generateBookingNumber(): string { const date = new Date(); const year = date.getFullYear().toString().slice(-2); const month = String(date.getMonth() + 1).padStart(2, '0'); const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); return `STM-${year}${month}-${random}`; } export async function POST(request: NextRequest) { try { const body = await request.json(); const data = bookingSchema.parse(body); const location = await prisma.location.findUnique({ where: { slug: data.locationSlug }, }); if (!location) { return NextResponse.json( { error: 'Standort nicht gefunden' }, { status: 404 } ); } const eventDate = new Date(data.eventDate); const startOfDay = new Date(eventDate); startOfDay.setHours(0, 0, 0, 0); const endOfDay = new Date(eventDate); endOfDay.setHours(23, 59, 59, 999); const availablePhotobox = await prisma.photobox.findFirst({ where: { locationId: location.id, model: data.model, active: true, NOT: { bookings: { some: { eventDate: { gte: startOfDay, lte: endOfDay, }, status: { in: ['RESERVED', 'CONFIRMED'], }, }, }, }, }, }); if (!availablePhotobox) { return NextResponse.json( { error: 'Keine Fotobox verfügbar für dieses Datum' }, { status: 409 } ); } const priceConfig = await prisma.priceConfig.findUnique({ where: { locationId_model: { locationId: location.id, model: data.model, }, }, }); if (!priceConfig) { return NextResponse.json( { error: 'Preiskonfiguration nicht gefunden' }, { status: 404 } ); } let distance: number | null = null; let calculatedPrice = priceConfig.basePrice; if (location.warehouseAddress && location.warehouseZip && location.warehouseCity) { const warehouseAddress = DistanceCalculator.formatAddress( location.warehouseAddress, location.warehouseZip, location.warehouseCity ); const eventAddress = DistanceCalculator.formatAddress( data.eventAddress, data.eventZip, data.eventCity ); const distanceResult = await DistanceCalculator.calculateDistance( warehouseAddress, eventAddress ); if (distanceResult) { distance = distanceResult.distance; const priceBreakdown = PriceCalculator.calculateTotalPrice( priceConfig.basePrice, distance, { basePrice: priceConfig.basePrice, kmFlatRate: priceConfig.kmFlatRate, kmFlatRateUpTo: priceConfig.kmFlatRateUpTo, pricePerKm: priceConfig.pricePerKm, kmMultiplier: priceConfig.kmMultiplier, } ); calculatedPrice = priceBreakdown.totalPrice; console.log('📍 Distanzberechnung:', { from: warehouseAddress, to: eventAddress, distance: `${distance}km`, breakdown: PriceCalculator.formatPriceBreakdown(priceBreakdown), }); } else { console.warn('⚠️ Distanzberechnung fehlgeschlagen, verwende nur Grundpreis'); } } else { console.warn('⚠️ Keine Lager-Adresse konfiguriert, verwende nur Grundpreis'); } const booking = await prisma.booking.create({ data: { bookingNumber: generateBookingNumber(), locationId: location.id, photoboxId: availablePhotobox.id, status: 'RESERVED', customerName: data.customerName, customerEmail: data.customerEmail, customerPhone: data.customerPhone, customerAddress: data.customerAddress, customerCity: data.customerCity, customerZip: data.customerZip, invoiceType: data.invoiceType, companyName: data.companyName, eventDate: new Date(data.eventDate), eventAddress: data.eventAddress, eventCity: data.eventCity, eventZip: data.eventZip, eventLocation: data.eventLocation, setupTimeStart: new Date(data.setupTimeStart), setupTimeLatest: new Date(data.setupTimeLatest), dismantleTimeEarliest: data.dismantleTimeEarliest ? new Date(data.dismantleTimeEarliest) : null, dismantleTimeLatest: data.dismantleTimeLatest ? new Date(data.dismantleTimeLatest) : null, distance, calculatedPrice, notes: data.notes, }, include: { location: true, photobox: true, }, }); await prisma.notification.create({ data: { type: 'NEW_BOOKING', title: 'Neue Buchungsanfrage', message: `${data.customerName} hat eine ${data.model} für ${data.eventCity} am ${eventDate.toLocaleDateString('de-DE')} angefragt.`, metadata: { bookingId: booking.id, bookingNumber: booking.bookingNumber, }, }, }); // 🤖 Automatische Post-Booking Aktionen (E-Mail + Kalender) console.log('📢 Starte automatische Aktionen...'); bookingAutomationService.runPostBookingActions(booking.id).catch(err => { console.error('⚠️ Automatische Aktionen fehlgeschlagen:', err); }); return NextResponse.json({ success: true, booking: { id: booking.id, bookingNumber: booking.bookingNumber, status: booking.status, eventDate: booking.eventDate, calculatedPrice: booking.calculatedPrice, }, message: 'Buchungsanfrage erfolgreich erstellt! Wir melden uns in Kürze bei Ihnen.', }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: 'Ungültige Daten', details: error.errors }, { status: 400 } ); } console.error('Booking creation error:', error); return NextResponse.json( { error: 'Interner Serverfehler' }, { status: 500 } ); } } export async function GET(request: NextRequest) { try { const searchParams = request.nextUrl.searchParams; const status = searchParams.get('status'); const locationSlug = searchParams.get('location'); const where: any = {}; if (status) { // Support multiple statuses separated by comma const statuses = status.split(',').map(s => s.trim()); if (statuses.length > 1) { where.status = { in: statuses }; } else { where.status = status; } } if (locationSlug) { const location = await prisma.location.findUnique({ where: { slug: locationSlug }, }); if (location) { where.locationId = location.id; } } const bookings = await prisma.booking.findMany({ where, include: { location: true, photobox: true, setupWindows: { orderBy: { setupDate: 'asc' }, }, }, orderBy: { createdAt: 'desc', }, take: 100, }); return NextResponse.json({ bookings }); } catch (error) { console.error('Bookings fetch error:', error); return NextResponse.json( { error: 'Interner Serverfehler' }, { status: 500 } ); } }