Files
Atlas/app/api/bookings/route.ts
2025-11-12 20:21:32 +01:00

213 lines
6.0 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { z } from 'zod';
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,
},
},
});
const calculatedPrice = priceConfig ? priceConfig.basePrice : 0;
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,
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,
},
},
});
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) {
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 }
);
}
}