Initial commit - SaveTheMoment Atlas Basis-Setup
This commit is contained in:
173
app/api/cron/process-pending-bookings/route.ts
Normal file
173
app/api/cron/process-pending-bookings/route.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { AIService } from '@/lib/ai-service';
|
||||
import { LexOfficeService } from '@/lib/lexoffice';
|
||||
import { getCalendarService } from '@/lib/nextcloud-calendar';
|
||||
|
||||
/**
|
||||
* AUTO-WORKFLOW CRON-JOB
|
||||
*
|
||||
* Läuft alle 5 Minuten und:
|
||||
* 1. Findet Buchungen mit `aiParsed=false` (neue E-Mails)
|
||||
* 2. Startet KI-Analyse
|
||||
* 3. Generiert Entwürfe (E-Mail, Angebot, Vertrag)
|
||||
* 4. Setzt Status auf PENDING_REVIEW
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Cron-Secret validieren
|
||||
const authHeader = request.headers.get('authorization');
|
||||
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
console.log('🔄 Auto-Workflow Cron-Job gestartet...');
|
||||
|
||||
// 1. Finde neue Buchungen (noch nicht von KI analysiert)
|
||||
const pendingBookings = await prisma.booking.findMany({
|
||||
where: {
|
||||
aiParsed: false,
|
||||
status: 'RESERVED', // Nur neue Reservierungen
|
||||
},
|
||||
include: {
|
||||
location: true,
|
||||
photobox: true,
|
||||
},
|
||||
take: 10, // Max 10 pro Lauf
|
||||
});
|
||||
|
||||
if (pendingBookings.length === 0) {
|
||||
console.log('✅ Keine neuen Buchungen gefunden');
|
||||
return NextResponse.json({
|
||||
message: 'No pending bookings',
|
||||
processed: 0
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`📋 ${pendingBookings.length} neue Buchungen gefunden`);
|
||||
|
||||
const aiService = new AIService();
|
||||
const lexoffice = new LexOfficeService();
|
||||
const calendar = getCalendarService();
|
||||
|
||||
let processed = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (const booking of pendingBookings) {
|
||||
try {
|
||||
console.log(`\n🤖 Verarbeite Buchung: ${booking.bookingNumber}`);
|
||||
|
||||
// 2. KI-Analyse (falls noch nicht vorhanden)
|
||||
if (!booking.aiResponseDraft) {
|
||||
console.log(' → Generiere E-Mail-Entwurf...');
|
||||
|
||||
// Generiere einfache Antwort (später kann man das erweitern)
|
||||
const emailDraft = `Hallo ${booking.customerName},
|
||||
|
||||
vielen Dank für Ihre Anfrage für eine Fotobox-Vermietung am ${new Date(booking.eventDate).toLocaleDateString('de-DE')} in ${booking.eventCity}!
|
||||
|
||||
Wir freuen uns sehr über Ihr Interesse. Anbei finden Sie unser Angebot sowie den Mietvertrag zur Durchsicht.
|
||||
|
||||
Falls Sie Fragen haben, stehen wir Ihnen jederzeit gerne zur Verfügung!
|
||||
|
||||
Mit freundlichen Grüßen
|
||||
Ihr ${booking.location?.name || 'SaveTheMoment'} Team`;
|
||||
|
||||
await prisma.booking.update({
|
||||
where: { id: booking.id },
|
||||
data: {
|
||||
aiResponseDraft: emailDraft,
|
||||
aiProcessedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 3. LexOffice Kontakt & Angebot (Entwurf)
|
||||
if (!booking.lexofficeContactId) {
|
||||
console.log(' → Erstelle LexOffice Kontakt...');
|
||||
try {
|
||||
const contactId = await lexoffice.createContactFromBooking(booking);
|
||||
|
||||
await prisma.booking.update({
|
||||
where: { id: booking.id },
|
||||
data: { lexofficeContactId: contactId },
|
||||
});
|
||||
|
||||
console.log(' → Erstelle LexOffice Angebot-Entwurf...');
|
||||
const quotationId = await lexoffice.createQuotationFromBooking(booking, contactId);
|
||||
|
||||
await prisma.booking.update({
|
||||
where: { id: booking.id },
|
||||
data: { lexofficeOfferId: quotationId },
|
||||
});
|
||||
} catch (lexError) {
|
||||
console.error(' ⚠️ LexOffice-Fehler (wird übersprungen):', lexError);
|
||||
// Weiter machen, auch wenn LexOffice fehlschlägt
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Kalender-Eintrag erstellen (Reservierung)
|
||||
if (!booking.calendarEventId) {
|
||||
console.log(' → Erstelle Kalender-Eintrag...');
|
||||
try {
|
||||
const eventId = await calendar.createBookingEvent(booking);
|
||||
if (eventId) {
|
||||
await prisma.booking.update({
|
||||
where: { id: booking.id },
|
||||
data: {
|
||||
calendarEventId: eventId,
|
||||
calendarSynced: true,
|
||||
calendarSyncedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (calError) {
|
||||
console.error(' ⚠️ Kalender-Fehler (wird übersprungen):', calError);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Status aktualisieren: Bereit für Admin-Review
|
||||
await prisma.booking.update({
|
||||
where: { id: booking.id },
|
||||
data: {
|
||||
aiParsed: true,
|
||||
readyForAssignment: true, // Admin kann jetzt prüfen
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ Buchung ${booking.bookingNumber} erfolgreich verarbeitet`);
|
||||
processed++;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Fehler bei Buchung ${booking.bookingNumber}:`, error);
|
||||
errors++;
|
||||
|
||||
// Markiere als fehlerhaft, damit es beim nächsten Lauf erneut versucht wird
|
||||
await prisma.booking.update({
|
||||
where: { id: booking.id },
|
||||
data: {
|
||||
internalNotes: `Auto-Workflow Fehler: ${error instanceof Error ? error.message : 'Unbekannter Fehler'}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 Cron-Job abgeschlossen:`);
|
||||
console.log(` ✅ Erfolgreich: ${processed}`);
|
||||
console.log(` ❌ Fehler: ${errors}`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
processed,
|
||||
errors,
|
||||
total: pendingBookings.length,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Cron-Job Fehler:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Cron job failed' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user