diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 3f37f65306..80d477957f 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -21,7 +21,7 @@ import { cancelScheduledJobs, scheduleTrigger } from "@calcom/app-store/zapier/l import EventManager from "@calcom/core/EventManager"; import { getEventName } from "@calcom/core/event"; import { getUserAvailability } from "@calcom/core/getUserAvailability"; -import dayjs from "@calcom/dayjs"; +import dayjs, { ConfigType } from "@calcom/dayjs"; import { sendAttendeeRequestEmail, sendOrganizerRequestEmail, @@ -46,6 +46,7 @@ import { EventTypeMetaDataSchema, extendedBookingCreateBody } from "@calcom/pris import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime"; import type { AdditionalInformation, AppsStatus, CalendarEvent } from "@calcom/types/Calendar"; import type { EventResult, PartialReference } from "@calcom/types/EventManager"; +import { WorkingHours } from "@calcom/types/schedule"; import sendPayload, { EventTypeInfo } from "../../webhooks/lib/sendPayload"; @@ -83,40 +84,56 @@ async function refreshCredentials(credentials: Array): Promise { + const timeSlotStart = dayjs(timeSlot.start).utc(); + const timeSlotEnd = dayjs(timeSlot.end).utc(); + for (const workingHour of workingHours) { + // TODO: Double check & possibly fix timezone conversions. + const startTime = timeSlotStart.startOf("day").add(workingHour.startTime, "minute"); + const endTime = timeSlotEnd.startOf("day").add(workingHour.endTime, "minute"); + if ( + workingHour.days.includes(timeSlotStart.day()) && + // UTC mode, should be performant. + timeSlotStart.isBetween(startTime, endTime, null, "[)") && + timeSlotEnd.isBetween(startTime, endTime, null, "(]") + ) { + return true; } } + return false; +}; - return t; +// if true, there are conflicts. +function checkForConflicts(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType, length: number) { + // Early return + if (!Array.isArray(busyTimes) || busyTimes.length < 1) { + return false; // guaranteed no conflicts when there is no busy times. + } + + for (const busyTime of busyTimes) { + const startTime = dayjs(busyTime.start); + const endTime = dayjs(busyTime.end); + // Check if time is between start and end times + if (dayjs(time).isBetween(startTime, endTime, null, "[)")) { + return true; + } + // Check if slot end time is between start and end time + if (dayjs(time).add(length, "minutes").isBetween(startTime, endTime)) { + return true; + } + // Check if startTime is between slot + if (startTime.isBetween(dayjs(time), dayjs(time).add(length, "minutes"))) { + return true; + } + } + return false; } const getEventTypesFromDB = async (eventTypeId: number) => { @@ -199,7 +216,7 @@ async function ensureAvailableUsers( const availableUsers: typeof eventType.users = []; /** Let's start checking for availability */ for (const user of eventType.users) { - const { busy: bufferedBusyTimes } = await getUserAvailability( + const { busy: bufferedBusyTimes, workingHours } = await getUserAvailability( { userId: user.id, eventTypeId: eventType.id, @@ -208,9 +225,22 @@ async function ensureAvailableUsers( { user, eventType } ); + // check if time slot is outside of schedule. + if ( + !isWithinAvailableHours( + { start: input.dateFrom, end: input.dateTo }, + { + workingHours, + } + ) + ) { + // user does not have availability at this time, skip user. + continue; + } + console.log("calendarBusyTimes==>>>", bufferedBusyTimes); - let isAvailableToBeBooked = true; + let foundConflict = true; try { if (eventType.recurringEvent) { const recurringEvent = parseRecurringEvent(eventType.recurringEvent); @@ -219,23 +249,19 @@ async function ensureAvailableUsers( // DONE: Decreased computational complexity from O(2^n) to O(n) by refactoring this loop to stop // running at the first unavailable time. let i = 0; - while (isAvailableToBeBooked === true && i < allBookingDates.length) { - const aDate = allBookingDates[i]; - i++; - isAvailableToBeBooked = isAvailable(bufferedBusyTimes, aDate, eventType.length); - // We bail at the first false, we don't need to keep checking - if (!isAvailableToBeBooked) break; + while (!foundConflict && i < allBookingDates.length) { + foundConflict = checkForConflicts(bufferedBusyTimes, allBookingDates[i++], eventType.length); } } else { - isAvailableToBeBooked = isAvailable(bufferedBusyTimes, input.dateFrom, eventType.length); + foundConflict = checkForConflicts(bufferedBusyTimes, input.dateFrom, eventType.length); } } catch { log.debug({ message: "Unable set isAvailableToBeBooked. Using true. ", }); } - - if (isAvailableToBeBooked) { + // no conflicts found, add to available users. + if (!foundConflict) { availableUsers.push(user); } }