import type { Logger } from "tslog"; import { getBusyTimesForLimitChecks } from "@calcom/core/getBusyTimes"; import { getUsersAvailability } from "@calcom/core/getUserAvailability"; import dayjs from "@calcom/dayjs"; import type { Dayjs } from "@calcom/dayjs"; import { parseBookingLimit, parseDurationLimit } from "@calcom/lib"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { safeStringify } from "@calcom/lib/safeStringify"; import { checkForConflicts } from "../conflictChecker/checkForConflicts"; import type { getEventTypeResponse } from "./getEventTypesFromDB"; import type { IsFixedAwareUser, BookingType } from "./types"; type DateRange = { start: Dayjs; end: Dayjs; }; const getDateTimeInUtc = (timeInput: string, timeZone?: string) => { return timeZone === "Etc/GMT" ? dayjs.utc(timeInput) : dayjs(timeInput).tz(timeZone).utc(); }; const getOriginalBookingDuration = (originalBooking?: BookingType) => { return originalBooking ? dayjs(originalBooking.endTime).diff(dayjs(originalBooking.startTime), "minutes") : undefined; }; const hasDateRangeForBooking = ( dateRanges: DateRange[], startDateTimeUtc: dayjs.Dayjs, endDateTimeUtc: dayjs.Dayjs ) => { let dateRangeForBooking = false; for (const dateRange of dateRanges) { if ( (startDateTimeUtc.isAfter(dateRange.start) || startDateTimeUtc.isSame(dateRange.start)) && (endDateTimeUtc.isBefore(dateRange.end) || endDateTimeUtc.isSame(dateRange.end)) ) { dateRangeForBooking = true; break; } } return dateRangeForBooking; }; export async function ensureAvailableUsers( eventType: getEventTypeResponse & { users: IsFixedAwareUser[]; }, input: { dateFrom: string; dateTo: string; timeZone: string; originalRescheduledBooking?: BookingType }, loggerWithEventDetails: Logger ) { const availableUsers: IsFixedAwareUser[] = []; const startDateTimeUtc = getDateTimeInUtc(input.dateFrom, input.timeZone); const endDateTimeUtc = getDateTimeInUtc(input.dateTo, input.timeZone); const duration = dayjs(input.dateTo).diff(input.dateFrom, "minute"); const originalBookingDuration = getOriginalBookingDuration(input.originalRescheduledBooking); const bookingLimits = parseBookingLimit(eventType?.bookingLimits); const durationLimits = parseDurationLimit(eventType?.durationLimits); const busyTimesFromLimitsBookingsAllUsers: Awaited> = eventType && (bookingLimits || durationLimits) ? await getBusyTimesForLimitChecks({ userIds: eventType.users.map((u) => u.id), eventTypeId: eventType.id, startDate: startDateTimeUtc.format(), endDate: endDateTimeUtc.format(), rescheduleUid: input.originalRescheduledBooking?.uid ?? null, bookingLimits, durationLimits, }) : []; const usersAvailability = await getUsersAvailability({ users: eventType.users, query: { ...input, eventTypeId: eventType.id, duration: originalBookingDuration, returnDateOverrides: false, dateFrom: startDateTimeUtc.format(), dateTo: endDateTimeUtc.format(), }, initialData: { eventType, rescheduleUid: input.originalRescheduledBooking?.uid ?? null, busyTimesFromLimitsBookings: busyTimesFromLimitsBookingsAllUsers, }, }); usersAvailability.forEach(({ oooExcludedDateRanges: dateRanges, busy: bufferedBusyTimes }, index) => { const user = eventType.users[index]; loggerWithEventDetails.debug( "calendarBusyTimes==>>>", JSON.stringify({ bufferedBusyTimes, dateRanges, isRecurringEvent: eventType.recurringEvent }) ); if (!dateRanges.length) { loggerWithEventDetails.error( `User does not have availability at this time.`, safeStringify({ startDateTimeUtc, endDateTimeUtc, input, }) ); return; } //check if event time is within the date range if (!hasDateRangeForBooking(dateRanges, startDateTimeUtc, endDateTimeUtc)) { loggerWithEventDetails.error( `No date range for booking.`, safeStringify({ startDateTimeUtc, endDateTimeUtc, input, }) ); return; } try { const foundConflict = checkForConflicts(bufferedBusyTimes, startDateTimeUtc, duration); // no conflicts found, add to available users. if (!foundConflict) { availableUsers.push(user); } } catch (error) { loggerWithEventDetails.error("Unable set isAvailableToBeBooked. Using true. ", error); } }); if (!availableUsers.length) { loggerWithEventDetails.error( `No available users found.`, safeStringify({ startDateTimeUtc, endDateTimeUtc, input, }) ); throw new Error(ErrorCode.NoAvailableUsersFound); } return availableUsers; }