174 lines
5.6 KiB
TypeScript
174 lines
5.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { prisma } from '@/lib/prisma';
|
|
import { AIService } from '@/lib/ai-service';
|
|
import { LexOfficeService } from '@/lib/lexoffice';
|
|
import { nextcloudCalendar } 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 = nextcloudCalendar;
|
|
|
|
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 }
|
|
);
|
|
}
|
|
}
|