feat: Equipment-System, Buchungsbearbeitung, Kundenadresse, LexOffice-Fix

- Vintage Modell hinzugefuegt
- Equipment Multi-Select (Neue Buchung + Bearbeitung)
- Kundenadresse in Formularen
- Bearbeiten-Seite fuer Buchungen
- Abbau-Zeiten in Formularen und Uebersicht
- Vertrag PDF nur bei Privatkunden
- LexOffice Kontakt-Erstellung Fix (BUSINESS)
- Zurueck-Pfeil auf Touren-Seite
This commit is contained in:
Julia Wehden
2026-03-19 16:21:55 +01:00
parent 0b6e429329
commit a2c95c70e7
79 changed files with 7396 additions and 538 deletions

View File

@@ -1,6 +1,9 @@
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(),
@@ -92,7 +95,62 @@ export async function POST(request: NextRequest) {
},
});
const calculatedPrice = priceConfig ? priceConfig.basePrice : 0;
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: {
@@ -117,6 +175,7 @@ export async function POST(request: NextRequest) {
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,
},
@@ -138,6 +197,12 @@ export async function POST(request: NextRequest) {
},
});
// 🤖 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: {
@@ -174,7 +239,13 @@ export async function GET(request: NextRequest) {
const where: any = {};
if (status) {
where.status = 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) {