From 9583148e02ff79dee073db9fc405762179944581 Mon Sep 17 00:00:00 2001 From: elpatron Date: Wed, 8 Oct 2025 18:26:09 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20Korrigiere=20Konflikt-Erkennung=20f?= =?UTF-8?q?=C3=BCr=20Multi-Treatment-Buchungen=20in=20recurring-availabili?= =?UTF-8?q?ty.ts=20-=20Berechne=20Dauer=20korrekt=20f=C3=BCr=20neue=20Beha?= =?UTF-8?q?ndlungen-Arrays=20-=20Filtere=20undefined=20Treatment-IDs=20aus?= =?UTF-8?q?=20Legacy-Cache=20-=20Erstelle=20Treatment-Cache=20nur=20bei=20?= =?UTF-8?q?Bedarf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/rpc/recurring-availability.ts | 68 +++++++++++++++--------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/src/server/rpc/recurring-availability.ts b/src/server/rpc/recurring-availability.ts index 73e7b9a..5ab10ab 100644 --- a/src/server/rpc/recurring-availability.ts +++ b/src/server/rpc/recurring-availability.ts @@ -272,7 +272,12 @@ const getAvailableTimes = os .input( z.object({ date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/), - treatmentId: z.string(), + treatmentIds: z.array(z.string()) + .min(1, "Mindestens eine Behandlung muss ausgewählt werden") + .max(3, "Maximal 3 Behandlungen können ausgewählt werden") + .refine(list => { + return list.length === new Set(list).size; + }, { message: "Doppelte Behandlungen sind nicht erlaubt" }), }) ) .handler(async ({ input }) => { @@ -287,13 +292,22 @@ const getAvailableTimes = os return []; } - // Get treatment duration - const treatment = await treatmentsKV.getItem(input.treatmentId); - if (!treatment) { - throw new Error("Behandlung nicht gefunden."); + // Get multiple treatments and calculate total duration + const treatments = await Promise.all( + input.treatmentIds.map(id => treatmentsKV.getItem(id)) + ); + + // Validate that all treatments exist + const missingTreatments = treatments + .map((t, i) => t ? null : input.treatmentIds[i]) + .filter(id => id !== null); + + if (missingTreatments.length > 0) { + throw new Error(`Behandlung(en) nicht gefunden: ${missingTreatments.join(', ')}`); } - const treatmentDuration = treatment.duration; + // Calculate total duration by summing all treatment durations + const treatmentDuration = treatments.reduce((sum, t) => sum + (t?.duration || 0), 0); // Parse the date to get day of week const [year, month, day] = input.date.split('-').map(Number); @@ -344,36 +358,38 @@ const getAvailableTimes = os ['pending', 'confirmed', 'completed'].includes(booking.status) ); - // Optimize treatment duration lookup with Map caching - const uniqueTreatmentIds = [...new Set(dateBookings.map(booking => booking.treatmentId))]; + // Build cache only for legacy treatmentId bookings + const legacyTreatmentIds = [...new Set(dateBookings.filter(b => b.treatmentId).map(b => b.treatmentId as string))]; const treatmentDurationMap = new Map(); - for (const treatmentId of uniqueTreatmentIds) { - const treatment = await treatmentsKV.getItem(treatmentId); - treatmentDurationMap.set(treatmentId, treatment?.duration || 60); - } - - // Get treatment durations for all bookings using the cached map - const bookingTreatments = new Map(); - for (const booking of dateBookings) { - // Use bookedDurationMinutes if available, otherwise fallback to treatment duration - const duration = booking.bookedDurationMinutes || treatmentDurationMap.get(booking.treatmentId) || 60; - bookingTreatments.set(booking.id, duration); + // Only build cache if there are legacy bookings + if (legacyTreatmentIds.length > 0) { + for (const id of legacyTreatmentIds) { + const t = await treatmentsKV.getItem(id); + treatmentDurationMap.set(id, t?.duration || 60); + } } // Filter out booking conflicts const availableTimesFiltered = availableTimes.filter(slotTime => { const slotStartMinutes = parseTime(slotTime); - const slotEndMinutes = slotStartMinutes + treatmentDuration; + const slotEndMinutes = slotStartMinutes + treatmentDuration; // total from selected treatments - // Check if this slot overlaps with any existing booking const hasConflict = dateBookings.some(booking => { - const bookingStartMinutes = parseTime(booking.appointmentTime); - const bookingDuration = bookingTreatments.get(booking.id) || 60; - const bookingEndMinutes = bookingStartMinutes + bookingDuration; + let bookingDuration: number; + if (booking.treatments && booking.treatments.length > 0) { + bookingDuration = booking.treatments.reduce((sum: number, t: { duration: number }) => sum + t.duration, 0); + } else if (booking.bookedDurationMinutes) { + bookingDuration = booking.bookedDurationMinutes; + } else if (booking.treatmentId) { + bookingDuration = treatmentDurationMap.get(booking.treatmentId) || 60; + } else { + bookingDuration = 60; + } - // Check overlap: slotStart < bookingEnd && slotEnd > bookingStart - return slotStartMinutes < bookingEndMinutes && slotEndMinutes > bookingStartMinutes; + const bookingStart = parseTime(booking.appointmentTime); + const bookingEnd = bookingStart + bookingDuration; + return slotStartMinutes < bookingEnd && slotEndMinutes > bookingStart; }); return !hasConflict;