Refactor: Verbessere CalDAV und Bookings für Multi-Treatment-Support

- CalDAV SUMMARY zeigt jetzt alle Treatment-Namen als Liste statt Anzahl
- Treatments-Array im Booking-Type optional für Rückwärtskompatibilität
- Neue addMinutesToTime Helper-Funktion für saubere DTEND-Berechnung
- getTreatmentNames filtert leere Namen und liefert sicheren Fallback
This commit is contained in:
2025-10-08 19:50:16 +02:00
parent ccba9d443b
commit ebd9d8a72e
3 changed files with 223 additions and 33 deletions

View File

@@ -5,7 +5,7 @@ import { assertOwner } from "../lib/auth.js";
// Types für Buchungen (vereinfacht für CalDAV)
type Booking = {
id: string;
treatmentId: string;
treatments?: Array<{id: string, name: string, duration: number, price: number}>;
customerName: string;
customerEmail?: string;
customerPhone?: string;
@@ -13,6 +13,8 @@ type Booking = {
appointmentTime: string; // HH:MM
status: "pending" | "confirmed" | "cancelled" | "completed";
notes?: string;
// Deprecated fields for backward compatibility
treatmentId?: string;
bookedDurationMinutes?: number;
createdAt: string;
};
@@ -44,6 +46,14 @@ function formatDateTime(dateStr: string, timeStr: string): string {
return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
}
function addMinutesToTime(timeStr: string, minutesToAdd: number): string {
const [hours, minutes] = timeStr.split(':').map(Number);
const totalMinutes = hours * 60 + minutes + minutesToAdd;
const newHours = Math.floor(totalMinutes / 60);
const newMinutes = totalMinutes % 60;
return `${String(newHours).padStart(2, '0')}:${String(newMinutes).padStart(2, '0')}`;
}
function generateICSContent(bookings: Booking[], treatments: Treatment[]): string {
const now = new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
@@ -63,14 +73,41 @@ X-WR-TIMEZONE:Europe/Berlin
);
for (const booking of activeBookings) {
const treatment = treatments.find(t => t.id === booking.treatmentId);
const treatmentName = treatment?.name || 'Unbekannte Behandlung';
const duration = booking.bookedDurationMinutes || treatment?.duration || 60;
// Handle new treatments array structure
let treatmentNames: string;
let duration: number;
let treatmentDetails: string;
let totalPrice = 0;
if (booking.treatments && Array.isArray(booking.treatments) && booking.treatments.length > 0) {
// Use new treatments array
treatmentNames = booking.treatments.map(t => t.name).join(', ');
duration = booking.treatments.reduce((sum, t) => sum + (t.duration || 0), 0);
totalPrice = booking.treatments.reduce((sum, t) => sum + (t.price || 0), 0);
// Build detailed treatment list for description
treatmentDetails = booking.treatments
.map(t => `- ${t.name} (${t.duration} Min., ${t.price}€)`)
.join('\\n');
if (booking.treatments.length > 1) {
treatmentDetails += `\\n\\nGesamt: ${duration} Min., ${totalPrice.toFixed(2)}`;
}
} else {
// Fallback to deprecated treatmentId for backward compatibility
const treatment = booking.treatmentId ? treatments.find(t => t.id === booking.treatmentId) : null;
treatmentNames = treatment?.name || 'Unbekannte Behandlung';
duration = booking.bookedDurationMinutes || treatment?.duration || 60;
treatmentDetails = `Behandlung: ${treatmentNames}`;
if (treatment?.price) {
treatmentDetails += ` (${duration} Min., ${treatment.price}€)`;
}
}
const startTime = formatDateTime(booking.appointmentDate, booking.appointmentTime);
const endTime = formatDateTime(booking.appointmentDate,
`${String(Math.floor((parseInt(booking.appointmentTime.split(':')[0]) * 60 + parseInt(booking.appointmentTime.split(':')[1]) + duration) / 60)).padStart(2, '0')}:${String((parseInt(booking.appointmentTime.split(':')[0]) * 60 + parseInt(booking.appointmentTime.split(':')[1]) + duration) % 60).padStart(2, '0')}`
);
const endTimeStr = addMinutesToTime(booking.appointmentTime, duration);
const endTime = formatDateTime(booking.appointmentDate, endTimeStr);
// UID für jeden Termin (eindeutig)
const uid = `booking-${booking.id}@stargirlnails.de`;
@@ -83,8 +120,8 @@ UID:${uid}
DTSTAMP:${now}
DTSTART:${startTime}
DTEND:${endTime}
SUMMARY:${treatmentName} - ${booking.customerName}
DESCRIPTION:Behandlung: ${treatmentName}\\nKunde: ${booking.customerName}${booking.customerPhone ? `\\nTelefon: ${booking.customerPhone}` : ''}${booking.notes ? `\\nNotizen: ${booking.notes}` : ''}
SUMMARY:${treatmentNames} - ${booking.customerName}
DESCRIPTION:${treatmentDetails}\\n\\nKunde: ${booking.customerName}${booking.customerPhone ? `\\nTelefon: ${booking.customerPhone}` : ''}${booking.notes ? `\\nNotizen: ${booking.notes}` : ''}
STATUS:${status}
TRANSP:OPAQUE
END:VEVENT