Initial commit - SaveTheMoment Atlas Basis-Setup
This commit is contained in:
212
app/api/bookings/route.ts
Normal file
212
app/api/bookings/route.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user