- 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
190 lines
6.1 KiB
TypeScript
190 lines
6.1 KiB
TypeScript
import { prisma } from './prisma';
|
|
import { sendInitialBookingEmail } from './email-service';
|
|
import { nextcloudCalendar } from './nextcloud-calendar';
|
|
import { lexofficeService } from './lexoffice';
|
|
import { generateContractFromTemplate } from './pdf-template-service';
|
|
|
|
export class BookingAutomationService {
|
|
async runPostBookingActions(bookingId: string): Promise<{
|
|
emailSent: boolean;
|
|
calendarSynced: boolean;
|
|
lexofficeCreated: boolean;
|
|
contractGenerated: boolean;
|
|
errors: string[];
|
|
}> {
|
|
const errors: string[] = [];
|
|
let emailSent = false;
|
|
let calendarSynced = false;
|
|
let lexofficeCreated = false;
|
|
let contractGenerated = false;
|
|
|
|
try {
|
|
const booking = await prisma.booking.findUnique({
|
|
where: { id: bookingId },
|
|
include: {
|
|
location: true,
|
|
photobox: true,
|
|
bookingEquipment: {
|
|
include: {
|
|
equipment: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!booking) {
|
|
errors.push('Buchung nicht gefunden');
|
|
return { emailSent, calendarSynced, lexofficeCreated, contractGenerated, errors };
|
|
}
|
|
|
|
let priceConfig = null;
|
|
if (booking.photobox?.model && booking.locationId) {
|
|
priceConfig = await prisma.priceConfig.findUnique({
|
|
where: {
|
|
locationId_model: {
|
|
locationId: booking.locationId,
|
|
model: booking.photobox.model,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
const bookingWithPriceConfig = {
|
|
...booking,
|
|
priceConfig,
|
|
};
|
|
|
|
console.log(`🤖 Automatische Aktionen für Buchung ${booking.bookingNumber}...`);
|
|
|
|
let quotationPdf: Buffer | null = null;
|
|
let contractPdf: Buffer | null = null;
|
|
|
|
// 1. LexOffice Contact + Quotation erstellen
|
|
try {
|
|
console.log(' 💼 Erstelle LexOffice-Kontakt und Angebot...');
|
|
|
|
const contactId = await lexofficeService.createContactFromBooking(bookingWithPriceConfig);
|
|
console.log(` ✅ LexOffice-Kontakt erstellt: ${contactId}`);
|
|
|
|
const quotationId = await lexofficeService.createQuotationFromBooking(bookingWithPriceConfig, contactId);
|
|
console.log(` ✅ LexOffice-Angebot erstellt: ${quotationId}`);
|
|
|
|
await prisma.booking.update({
|
|
where: { id: booking.id },
|
|
data: {
|
|
lexofficeContactId: contactId,
|
|
lexofficeOfferId: quotationId,
|
|
},
|
|
});
|
|
|
|
quotationPdf = await lexofficeService.getQuotationPDF(quotationId);
|
|
console.log(' ✅ Angebots-PDF heruntergeladen');
|
|
|
|
lexofficeCreated = true;
|
|
} catch (error: any) {
|
|
console.error(' ❌ LexOffice Fehler:', error.message);
|
|
errors.push(`LexOffice: ${error.message}`);
|
|
}
|
|
|
|
// 2. Mietvertrag-PDF generieren
|
|
try {
|
|
console.log(' 📄 Generiere Mietvertrag-PDF...');
|
|
|
|
contractPdf = await generateContractFromTemplate(
|
|
bookingWithPriceConfig,
|
|
booking.location,
|
|
booking.photobox
|
|
);
|
|
|
|
await prisma.booking.update({
|
|
where: { id: booking.id },
|
|
data: {
|
|
contractGenerated: true,
|
|
contractGeneratedAt: new Date(),
|
|
},
|
|
});
|
|
|
|
console.log(' ✅ Mietvertrag-PDF generiert');
|
|
contractGenerated = true;
|
|
} catch (error: any) {
|
|
console.error(' ❌ PDF-Generierung Fehler:', error.message);
|
|
errors.push(`PDF: ${error.message}`);
|
|
}
|
|
|
|
// 3. E-Mail mit Angebot + Vertrag versenden
|
|
if (quotationPdf && contractPdf) {
|
|
try {
|
|
console.log(' 📧 Sende E-Mail mit Angebot und Vertrag...');
|
|
|
|
await sendInitialBookingEmail(bookingWithPriceConfig, quotationPdf, contractPdf);
|
|
|
|
await prisma.booking.update({
|
|
where: { id: booking.id },
|
|
data: {
|
|
contractSentAt: new Date(),
|
|
},
|
|
});
|
|
|
|
emailSent = true;
|
|
console.log(' ✅ E-Mail gesendet');
|
|
} catch (error: any) {
|
|
console.error(' ❌ E-Mail Fehler:', error.message);
|
|
errors.push(`E-Mail: ${error.message}`);
|
|
}
|
|
} else {
|
|
console.warn(' ⚠️ E-Mail nicht gesendet - PDFs fehlen');
|
|
errors.push('E-Mail: PDFs nicht verfügbar');
|
|
}
|
|
|
|
// 4. Automatischer Nextcloud Kalender-Sync
|
|
try {
|
|
console.log(' 📅 Synchronisiere mit Nextcloud-Kalender...');
|
|
|
|
await nextcloudCalendar.syncBookingToCalendar(booking);
|
|
|
|
await prisma.booking.update({
|
|
where: { id: booking.id },
|
|
data: {
|
|
calendarSynced: true,
|
|
calendarSyncedAt: new Date(),
|
|
},
|
|
});
|
|
|
|
calendarSynced = true;
|
|
console.log(' ✅ Kalender synchronisiert');
|
|
} catch (error: any) {
|
|
console.error(' ❌ Kalender-Sync Fehler:', error.message);
|
|
errors.push(`Kalender: ${error.message}`);
|
|
}
|
|
|
|
// 5. Admin-Benachrichtigung erstellen
|
|
try {
|
|
await prisma.notification.create({
|
|
data: {
|
|
type: 'NEW_BOOKING',
|
|
title: 'Neue Buchungsanfrage',
|
|
message: `${booking.customerName} hat eine ${booking.photobox?.model || 'Fotobox'} für ${booking.eventCity} am ${new Date(booking.eventDate).toLocaleDateString('de-DE')} angefragt.`,
|
|
metadata: {
|
|
bookingId: booking.id,
|
|
bookingNumber: booking.bookingNumber,
|
|
},
|
|
},
|
|
});
|
|
console.log(' ✅ Admin-Benachrichtigung erstellt');
|
|
} catch (error: any) {
|
|
console.error(' ❌ Notification Fehler:', error.message);
|
|
}
|
|
|
|
console.log(`✅ Automatische Aktionen abgeschlossen (${errors.length} Fehler)`);
|
|
|
|
return { emailSent, calendarSynced, lexofficeCreated, contractGenerated, errors };
|
|
} catch (error: any) {
|
|
console.error('❌ Booking Automation Fehler:', error);
|
|
errors.push(error.message);
|
|
return { emailSent, calendarSynced, lexofficeCreated, contractGenerated, errors };
|
|
}
|
|
}
|
|
}
|
|
|
|
export const bookingAutomationService = new BookingAutomationService();
|