2
0
Files
cal/calcom/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts
2024-08-09 00:39:27 +02:00

156 lines
4.8 KiB
TypeScript

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<unknown>
) {
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<ReturnType<typeof getBusyTimesForLimitChecks>> =
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;
}