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;