- 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
210 lines
5.9 KiB
JavaScript
210 lines
5.9 KiB
JavaScript
const { PrismaClient } = require('@prisma/client');
|
||
const { createDAVClient } = require('tsdav');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
function loadEnv() {
|
||
const envPath = path.join(__dirname, '.env');
|
||
const envContent = fs.readFileSync(envPath, 'utf-8');
|
||
const env = {};
|
||
|
||
envContent.split('\n').forEach(line => {
|
||
const match = line.match(/^([^=:#]+)=(.*)$/);
|
||
if (match) {
|
||
const key = match[1].trim();
|
||
let value = match[2].trim();
|
||
|
||
if (value.startsWith('"') && value.endsWith('"')) {
|
||
value = value.slice(1, -1);
|
||
value = value.replace(/\\"/g, '"');
|
||
value = value.replace(/\\\\/g, '\\');
|
||
} else if (value.startsWith("'") && value.endsWith("'")) {
|
||
value = value.slice(1, -1);
|
||
}
|
||
|
||
env[key] = value;
|
||
}
|
||
});
|
||
|
||
return env;
|
||
}
|
||
|
||
async function syncBookingToCalendar(client, booking, env) {
|
||
try {
|
||
const calendars = await client.fetchCalendars();
|
||
|
||
if (calendars.length === 0) {
|
||
throw new Error('No calendars found in Nextcloud');
|
||
}
|
||
|
||
// Suche nach "Buchungen" Kalender, sonst verwende ersten
|
||
let calendar = calendars.find((cal) =>
|
||
cal.displayName?.toLowerCase().includes('buchung')
|
||
);
|
||
|
||
if (!calendar) {
|
||
console.log(` ⚠️ Kein "Buchungen"-Kalender gefunden, verwende: ${calendars[0].displayName}`);
|
||
calendar = calendars[0];
|
||
}
|
||
|
||
const startDate = new Date(booking.eventDate);
|
||
const endDate = new Date(startDate.getTime() + 4 * 60 * 60 * 1000);
|
||
|
||
const event = {
|
||
summary: `${booking.customerName} - ${booking.location?.name || 'Unbekannt'}`,
|
||
description: `
|
||
Buchung #${booking.bookingNumber || booking.id}
|
||
Kunde: ${booking.customerName}
|
||
E-Mail: ${booking.customerEmail}
|
||
Telefon: ${booking.customerPhone || 'N/A'}
|
||
Event-Location: ${booking.eventLocation || booking.eventAddress}
|
||
Status: ${booking.status}
|
||
Fotobox: ${booking.photobox?.model || 'Keine Box'}
|
||
Standort: ${booking.location?.name || 'Unbekannt'}
|
||
Preis: ${booking.calculatedPrice || 0}€
|
||
`.trim(),
|
||
location: `${booking.eventAddress || ''}, ${booking.eventZip || ''} ${booking.eventCity || ''}`.trim(),
|
||
start: startDate.toISOString(),
|
||
end: endDate.toISOString(),
|
||
uid: `savethemoment-booking-${booking.id}`,
|
||
};
|
||
|
||
// Create iCalendar format
|
||
const icsContent = `BEGIN:VCALENDAR
|
||
VERSION:2.0
|
||
PRODID:-//SaveTheMoment Atlas//EN
|
||
BEGIN:VEVENT
|
||
UID:${event.uid}
|
||
DTSTAMP:${new Date().toISOString().replace(/[-:]/g, '').split('.')[0]}Z
|
||
DTSTART:${startDate.toISOString().replace(/[-:]/g, '').split('.')[0]}Z
|
||
DTEND:${endDate.toISOString().replace(/[-:]/g, '').split('.')[0]}Z
|
||
SUMMARY:${event.summary}
|
||
DESCRIPTION:${event.description.replace(/\n/g, '\\n')}
|
||
LOCATION:${event.location}
|
||
STATUS:CONFIRMED
|
||
END:VEVENT
|
||
END:VCALENDAR`;
|
||
|
||
// Check if event already exists
|
||
const calendarObjects = await client.fetchCalendarObjects({
|
||
calendar,
|
||
});
|
||
|
||
const existingEvent = calendarObjects.find(obj =>
|
||
obj.data && obj.data.includes(`UID:${event.uid}`)
|
||
);
|
||
|
||
if (existingEvent) {
|
||
// Update existing event
|
||
await client.updateCalendarObject({
|
||
calendarObject: {
|
||
url: existingEvent.url,
|
||
data: icsContent,
|
||
etag: existingEvent.etag,
|
||
},
|
||
});
|
||
} else {
|
||
// Create new event
|
||
await client.createCalendarObject({
|
||
calendar,
|
||
filename: `${event.uid}.ics`,
|
||
iCalString: icsContent,
|
||
});
|
||
}
|
||
|
||
return true;
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async function syncExistingBookings() {
|
||
console.log('🔄 Synchronisiere bestehende Buchungen mit Nextcloud...\n');
|
||
|
||
const env = loadEnv();
|
||
const serverUrl = env.NEXTCLOUD_URL;
|
||
const username = env.NEXTCLOUD_USERNAME;
|
||
const password = env.NEXTCLOUD_PASSWORD;
|
||
|
||
if (!serverUrl || !username || !password) {
|
||
console.error('❌ Missing Nextcloud credentials in .env file!');
|
||
process.exit(1);
|
||
}
|
||
|
||
try {
|
||
console.log('⏳ Verbinde mit Nextcloud...');
|
||
const client = await createDAVClient({
|
||
serverUrl: `${serverUrl}/remote.php/dav`,
|
||
credentials: {
|
||
username,
|
||
password,
|
||
},
|
||
authMethod: 'Basic',
|
||
defaultAccountType: 'caldav',
|
||
});
|
||
console.log('✅ Nextcloud-Verbindung hergestellt!\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\n`);
|
||
|
||
if (bookings.length === 0) {
|
||
console.log('ℹ️ Keine Buchungen zum Synchronisieren gefunden.');
|
||
return;
|
||
}
|
||
|
||
let synced = 0;
|
||
let failed = 0;
|
||
|
||
for (const booking of bookings) {
|
||
try {
|
||
console.log(`📅 Synchronisiere: ${booking.bookingNumber} - ${booking.customerName}`);
|
||
console.log(` Event: ${new Date(booking.eventDate).toLocaleDateString('de-DE')}`);
|
||
console.log(` Standort: ${booking.location?.name || 'Unbekannt'}`);
|
||
|
||
await syncBookingToCalendar(client, booking, env);
|
||
synced++;
|
||
console.log(` ✅ Erfolgreich!\n`);
|
||
} catch (error) {
|
||
failed++;
|
||
console.error(` ❌ Fehler: ${error.message}\n`);
|
||
}
|
||
}
|
||
|
||
console.log('─'.repeat(50));
|
||
console.log(`✅ Erfolgreich synchronisiert: ${synced}`);
|
||
console.log(`❌ Fehlgeschlagen: ${failed}`);
|
||
console.log(`📊 Gesamt: ${bookings.length}`);
|
||
console.log('\n🎉 Synchronisation abgeschlossen!');
|
||
console.log(' Prüfen Sie Nextcloud → Kalender "Buchungen (Dennis Forte)"');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Fehler beim Synchronisieren:', error);
|
||
throw error;
|
||
} finally {
|
||
await prisma.$disconnect();
|
||
}
|
||
}
|
||
|
||
syncExistingBookings()
|
||
.catch((error) => {
|
||
console.error('Fatal error:', error);
|
||
process.exit(1);
|
||
});
|