Check for working hours + rename isAvailable (#5342)

* Check for working hours + rename isAvailable

* Return did not make it all the way back to callee

* Removed redundant break clause

* Fixes forEach return
pull/5365/head^2
Alex van Andel 2022-11-04 07:36:11 -04:00 committed by GitHub
parent eb14c1b796
commit 328a354f4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 69 additions and 43 deletions

View File

@ -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<Credential>): Promise<Array
return await async.mapLimit(credentials, 5, refreshCredential);
}
function isAvailable(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType, length: number): boolean {
// Check for conflicts
let t = true;
// Early return
if (!Array.isArray(busyTimes) || busyTimes.length < 1) return t;
let i = 0;
while (t === true && i < busyTimes.length) {
const busyTime = busyTimes[i];
i++;
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, "[)")) {
t = false;
break;
}
// Check if slot end time is between start and end time
if (dayjs(time).add(length, "minutes").isBetween(startTime, endTime)) {
t = false;
break;
}
// Check if startTime is between slot
if (startTime.isBetween(dayjs(time), dayjs(time).add(length, "minutes"))) {
t = false;
break;
const isWithinAvailableHours = (
timeSlot: { start: ConfigType; end: ConfigType },
{
workingHours,
}: {
workingHours: WorkingHours[];
}
) => {
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);
}
}