diff --git a/apps/web/components/eventtype/EventLimitsTab.tsx b/apps/web/components/eventtype/EventLimitsTab.tsx index d6b9e2ee86..22d9272a59 100644 --- a/apps/web/components/eventtype/EventLimitsTab.tsx +++ b/apps/web/components/eventtype/EventLimitsTab.tsx @@ -8,6 +8,7 @@ import { Controller, useFormContext } from "react-hook-form"; import type { SingleValue } from "react-select"; import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager"; +import { getDefinedBufferTimes } from "@calcom/features/eventtypes/lib/getDefinedBufferTimes"; import type { FormValues } from "@calcom/features/eventtypes/lib/types"; import { classNames } from "@calcom/lib"; import type { DurationType } from "@calcom/lib/convertToNewDurationType"; @@ -172,7 +173,7 @@ export const EventLimitsTab = ({ eventType }: Pick ({ + ...getDefinedBufferTimes().map((minutes) => ({ label: `${minutes} ${t("minutes")}`, value: minutes, })), diff --git a/packages/core/getBusyTimes.ts b/packages/core/getBusyTimes.ts index c239a7c1b5..36089cafd0 100644 --- a/packages/core/getBusyTimes.ts +++ b/packages/core/getBusyTimes.ts @@ -14,6 +14,8 @@ import { stringToDayjs } from "@calcom/prisma/zod-utils"; import type { EventBusyDetails, IntervalLimit } from "@calcom/types/Calendar"; import type { CredentialPayload } from "@calcom/types/Credential"; +import { getDefinedBufferTimes } from "../features/eventtypes/lib/getDefinedBufferTimes"; + export async function getBusyTimes(params: { credentials: CredentialPayload[]; userId: number; @@ -85,10 +87,16 @@ export async function getBusyTimes(params: { const endTimeDate = rescheduleUid && duration ? dayjs(endTime).add(duration, "minute").toDate() : new Date(endTime); + // to also get bookings that are outside of start and end time, but the buffer falls within the start and end time + const definedBufferTimes = getDefinedBufferTimes(); + const maxBuffer = definedBufferTimes[definedBufferTimes.length - 1]; + const startTimeAdjustedWithMaxBuffer = dayjs(startTimeDate).subtract(maxBuffer, "minute").toDate(); + const endTimeAdjustedWithMaxBuffer = dayjs(endTimeDate).add(maxBuffer, "minute").toDate(); + // startTime is less than endTimeDate and endTime grater than startTimeDate const sharedQuery = { - startTime: { lte: endTimeDate }, - endTime: { gte: startTimeDate }, + startTime: { lte: endTimeAdjustedWithMaxBuffer }, + endTime: { gte: startTimeAdjustedWithMaxBuffer }, status: { in: [BookingStatus.ACCEPTED], }, diff --git a/packages/features/bookings/lib/handleNewBooking/test/booking-limits.test.ts b/packages/features/bookings/lib/handleNewBooking/test/booking-limits.test.ts index f79531e8cf..712e95272e 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/booking-limits.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/booking-limits.test.ts @@ -462,4 +462,141 @@ describe("handleNewBooking", () => { }, timeout ); + + describe("Buffers", () => { + test(`should throw error when booking is within a before event buffer of an existing booking + `, async ({}) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + }); + + const { dateString: nextDayDateString } = getDate({ dateIncrement: 1 }); + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: 15, + length: 15, + beforeEventBuffer: 60, + users: [ + { + id: 101, + }, + ], + }, + ], + bookings: [ + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${nextDayDateString}T05:00:00.000Z`, + endTime: `${nextDayDateString}T05:15:00.000Z`, + }, + ], + organizer, + }) + ); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + start: `${nextDayDateString}T04:30:00.000Z`, + end: `${nextDayDateString}T04:45:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + await expect(async () => await handleNewBooking(req)).rejects.toThrowError( + "no_available_users_found_error" + ); + }); + }); + test(`should throw error when booking is within a after event buffer of an existing booking + `, async ({}) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + }); + + const { dateString: nextDayDateString } = getDate({ dateIncrement: 1 }); + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: 15, + length: 15, + afterEventBuffer: 60, + users: [ + { + id: 101, + }, + ], + }, + ], + bookings: [ + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${nextDayDateString}T05:00:00.000Z`, + endTime: `${nextDayDateString}T05:15:00.000Z`, + }, + ], + organizer, + }) + ); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + start: `${nextDayDateString}T05:30:00.000Z`, + end: `${nextDayDateString}T05:45:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + await expect(async () => await handleNewBooking(req)).rejects.toThrowError( + "no_available_users_found_error" + ); + }); }); diff --git a/packages/features/eventtypes/lib/getDefinedBufferTimes.ts b/packages/features/eventtypes/lib/getDefinedBufferTimes.ts new file mode 100644 index 0000000000..2531cee04c --- /dev/null +++ b/packages/features/eventtypes/lib/getDefinedBufferTimes.ts @@ -0,0 +1,3 @@ +export const getDefinedBufferTimes = () => { + return [5, 10, 15, 20, 30, 45, 60, 90, 120]; +};