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:
87
app/api/admin/sync-all-bookings/route.ts
Normal file
87
app/api/admin/sync-all-bookings/route.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { nextcloudCalendar } from '@/lib/nextcloud-calendar';
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
console.log('🔄 Synchronisiere bestehende Buchungen mit Nextcloud...\n');
|
||||
|
||||
// Hole alle bestätigten Buchungen
|
||||
const bookings = await prisma.booking.findMany({
|
||||
where: {
|
||||
status: {
|
||||
in: ['RESERVED', 'CONFIRMED'],
|
||||
},
|
||||
},
|
||||
include: {
|
||||
location: true,
|
||||
photobox: true,
|
||||
},
|
||||
orderBy: {
|
||||
eventDate: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`📊 Gefunden: ${bookings.length} Buchungen`);
|
||||
|
||||
if (bookings.length === 0) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Keine Buchungen zum Synchronisieren gefunden.',
|
||||
synced: 0,
|
||||
failed: 0,
|
||||
total: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let synced = 0;
|
||||
let failed = 0;
|
||||
const errors: any[] = [];
|
||||
|
||||
for (const booking of bookings) {
|
||||
try {
|
||||
console.log(`📅 Synchronisiere: ${booking.bookingNumber} - ${booking.customerName}`);
|
||||
|
||||
await nextcloudCalendar.syncBookingToCalendar(booking);
|
||||
synced++;
|
||||
console.log(` ✅ Erfolgreich!`);
|
||||
} catch (error: any) {
|
||||
failed++;
|
||||
console.error(` ❌ Fehler: ${error.message}`);
|
||||
errors.push({
|
||||
bookingNumber: booking.bookingNumber,
|
||||
customerName: booking.customerName,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log('─'.repeat(50));
|
||||
console.log(`✅ Erfolgreich synchronisiert: ${synced}`);
|
||||
console.log(`❌ Fehlgeschlagen: ${failed}`);
|
||||
console.log(`📊 Gesamt: ${bookings.length}`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
synced,
|
||||
failed,
|
||||
total: bookings.length,
|
||||
errors: errors.length > 0 ? errors : undefined,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Fehler beim Synchronisieren:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to sync bookings' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
108
app/api/admin/sync-emails/route.ts
Normal file
108
app/api/admin/sync-emails/route.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { emailSyncService } from '@/lib/email-sync';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
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 { locationId } = await request.json();
|
||||
|
||||
if (locationId) {
|
||||
// Sync specific location
|
||||
console.log(`🔄 Starte E-Mail-Sync für Location: ${locationId}`);
|
||||
const result = await emailSyncService.syncLocationEmails(locationId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: result.success,
|
||||
location: locationId,
|
||||
newEmails: result.newEmails,
|
||||
newBookings: result.newBookings,
|
||||
errors: result.errors,
|
||||
});
|
||||
} else {
|
||||
// Sync all locations
|
||||
console.log('🔄 Starte E-Mail-Sync für alle Locations...');
|
||||
|
||||
const locations = await prisma.location.findMany({
|
||||
where: { emailSyncEnabled: true },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const location of locations) {
|
||||
console.log(`📍 Sync: ${location.name}`);
|
||||
const result = await emailSyncService.syncLocationEmails(location.id);
|
||||
results.push({
|
||||
locationId: location.id,
|
||||
locationName: location.name,
|
||||
...result,
|
||||
});
|
||||
}
|
||||
|
||||
const totalNewEmails = results.reduce((sum, r) => sum + r.newEmails, 0);
|
||||
const totalNewBookings = results.reduce((sum, r) => sum + r.newBookings, 0);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
totalLocations: locations.length,
|
||||
totalNewEmails,
|
||||
totalNewBookings,
|
||||
results,
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ E-Mail-Sync Fehler:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'E-Mail-Sync fehlgeschlagen' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
// Get sync status for all locations
|
||||
const locations = await prisma.location.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
emailSyncEnabled: true,
|
||||
lastEmailSync: true,
|
||||
imapHost: true,
|
||||
imapUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
const status = locations.map(loc => ({
|
||||
id: loc.id,
|
||||
name: loc.name,
|
||||
slug: loc.slug,
|
||||
syncEnabled: loc.emailSyncEnabled,
|
||||
configured: !!(loc.imapHost && loc.imapUser),
|
||||
lastSync: loc.lastEmailSync,
|
||||
}));
|
||||
|
||||
return NextResponse.json({ locations: status });
|
||||
} catch (error: any) {
|
||||
console.error('❌ Fehler beim Abrufen des Sync-Status:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Fehler beim Abrufen des Status' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
96
app/api/admin/test-automation/route.ts
Normal file
96
app/api/admin/test-automation/route.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { bookingAutomationService } from '@/lib/booking-automation';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
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 { bookingId } = await request.json();
|
||||
|
||||
if (!bookingId) {
|
||||
return NextResponse.json({ error: 'bookingId required' }, { status: 400 });
|
||||
}
|
||||
|
||||
console.log(`🤖 Starte automatische Aktionen für Buchung: ${bookingId}`);
|
||||
|
||||
const result = await bookingAutomationService.runPostBookingActions(bookingId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
emailSent: result.emailSent,
|
||||
calendarSynced: result.calendarSynced,
|
||||
lexofficeCreated: result.lexofficeCreated,
|
||||
contractGenerated: result.contractGenerated,
|
||||
errors: result.errors,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Automation Fehler:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Automation fehlgeschlagen' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// GET: Hole neueste Buchung und teste Automation
|
||||
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 });
|
||||
}
|
||||
|
||||
// Hole neueste Buchung
|
||||
const latestBooking = await prisma.booking.findFirst({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
bookingNumber: true,
|
||||
customerName: true,
|
||||
customerEmail: true,
|
||||
eventDate: true,
|
||||
calendarSynced: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!latestBooking) {
|
||||
return NextResponse.json({ error: 'Keine Buchung gefunden' }, { status: 404 });
|
||||
}
|
||||
|
||||
console.log(`🤖 Teste Automation für: ${latestBooking.bookingNumber}`);
|
||||
|
||||
const result = await bookingAutomationService.runPostBookingActions(latestBooking.id);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
booking: {
|
||||
id: latestBooking.id,
|
||||
bookingNumber: latestBooking.bookingNumber,
|
||||
customerName: latestBooking.customerName,
|
||||
customerEmail: latestBooking.customerEmail,
|
||||
eventDate: latestBooking.eventDate,
|
||||
},
|
||||
emailSent: result.emailSent,
|
||||
calendarSynced: result.calendarSynced,
|
||||
lexofficeCreated: result.lexofficeCreated,
|
||||
contractGenerated: result.contractGenerated,
|
||||
errors: result.errors,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Test Automation Fehler:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Test fehlgeschlagen' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user