@@ -20,6 +20,72 @@ import type { CredentialForCalendarService } from "@calcom/types/Credential";
2020import { getDefinedBufferTimes } from "../features/eventtypes/lib/getDefinedBufferTimes" ;
2121import { BookingRepository as BookingRepo } from "./server/repository/booking" ;
2222
23+ const processBookingsToBusyTimes = (
24+ bookings : ( Pick < Booking , "id" | "uid" | "userId" | "startTime" | "endTime" | "title" > & {
25+ eventType : Pick < EventType , "id" | "beforeEventBuffer" | "afterEventBuffer" | "seatsPerTimeSlot" > | null ;
26+ _count ?: {
27+ seatsReferences : number ;
28+ } ;
29+ } ) [ ] ,
30+ rescheduleUid : string | null | undefined ,
31+ eventTypeId : number | undefined ,
32+ beforeEventBuffer : number | undefined ,
33+ afterEventBuffer : number | undefined
34+ ) => {
35+ const bookingSeatCountMap : { [ x : string ] : number } = { } ;
36+ return {
37+ busyTimes : bookings . reduce ( ( aggregate : EventBusyDetails [ ] , booking ) => {
38+ const { id, startTime, endTime, eventType, title, ...rest } = booking ;
39+
40+ const minutesToBlockBeforeEvent = ( eventType ?. beforeEventBuffer || 0 ) + ( afterEventBuffer || 0 ) ;
41+ const minutesToBlockAfterEvent = ( eventType ?. afterEventBuffer || 0 ) + ( beforeEventBuffer || 0 ) ;
42+
43+ if ( rest . _count ?. seatsReferences ) {
44+ const bookedAt = `${ dayjs ( startTime ) . utc ( ) . format ( ) } <>${ dayjs ( endTime ) . utc ( ) . format ( ) } ` ;
45+ bookingSeatCountMap [ bookedAt ] = bookingSeatCountMap [ bookedAt ] || 0 ;
46+ bookingSeatCountMap [ bookedAt ] ++ ;
47+ // Seat references on the current event are non-blocking until the event is fully booked.
48+ if (
49+ // there are still seats available.
50+ bookingSeatCountMap [ bookedAt ] < ( eventType ?. seatsPerTimeSlot || 1 ) &&
51+ // and this is the seated event, other event types should be blocked.
52+ eventTypeId === eventType ?. id
53+ ) {
54+ // then we ONLY add the before/after buffer times as busy times.
55+ if ( minutesToBlockBeforeEvent ) {
56+ aggregate . push ( {
57+ start : dayjs ( startTime ) . subtract ( minutesToBlockBeforeEvent , "minute" ) . toDate ( ) ,
58+ end : dayjs ( startTime ) . toDate ( ) , // The event starts after the buffer
59+ } ) ;
60+ }
61+ if ( minutesToBlockAfterEvent ) {
62+ aggregate . push ( {
63+ start : dayjs ( endTime ) . toDate ( ) , // The event ends before the buffer
64+ end : dayjs ( endTime ) . add ( minutesToBlockAfterEvent , "minute" ) . toDate ( ) ,
65+ } ) ;
66+ }
67+ return aggregate ;
68+ }
69+ // if it does get blocked at this point; we remove the bookingSeatCountMap entry
70+ // doing this allows using the map later to remove the ranges from calendar busy times.
71+ delete bookingSeatCountMap [ bookedAt ] ;
72+ }
73+ // rescheduling the same booking to the same time should be possible. Why?
74+ if ( rest . uid === rescheduleUid ) {
75+ return aggregate ;
76+ }
77+ aggregate . push ( {
78+ start : dayjs ( startTime ) . subtract ( minutesToBlockBeforeEvent , "minute" ) . toDate ( ) ,
79+ end : dayjs ( endTime ) . add ( minutesToBlockAfterEvent , "minute" ) . toDate ( ) ,
80+ title,
81+ source : `eventType-${ eventType ?. id } -booking-${ id } ` ,
82+ } ) ;
83+ return aggregate ;
84+ } , [ ] ) ,
85+ bookingSeatCountMap,
86+ } ;
87+ } ;
88+
2389const _getBusyTimes = async ( params : {
2490 credentials : CredentialForCalendarService [ ] ;
2591 userId : number ;
@@ -107,65 +173,45 @@ const _getBusyTimes = async (params: {
107173 // to avoid potential side effects.
108174 let bookings = params . currentBookings ;
109175
176+ const promises = [ ] ;
177+ let bookingsPromise ;
178+
110179 if ( ! bookings ) {
111- bookings = await BookingRepo . findAllExistingBookingsForEventTypeBetween ( {
180+ bookingsPromise = BookingRepo . findAllExistingBookingsForEventTypeBetween ( {
112181 userIdAndEmailMap : new Map ( [ [ userId , userEmail ] ] ) ,
113182 eventTypeId,
114183 startDate : startTimeAdjustedWithMaxBuffer ,
115184 endDate : endTimeAdjustedWithMaxBuffer ,
116185 seatedEvent,
117186 } ) ;
187+ promises . push ( bookingsPromise ) ;
118188 }
119189
120- const bookingSeatCountMap : { [ x : string ] : number } = { } ;
121- const busyTimes = bookings . reduce ( ( aggregate : EventBusyDetails [ ] , booking ) => {
122- const { id, startTime, endTime, eventType, title, ...rest } = booking ;
123-
124- const minutesToBlockBeforeEvent = ( eventType ?. beforeEventBuffer || 0 ) + ( afterEventBuffer || 0 ) ;
125- const minutesToBlockAfterEvent = ( eventType ?. afterEventBuffer || 0 ) + ( beforeEventBuffer || 0 ) ;
126-
127- if ( rest . _count ?. seatsReferences ) {
128- const bookedAt = `${ dayjs ( startTime ) . utc ( ) . format ( ) } <>${ dayjs ( endTime ) . utc ( ) . format ( ) } ` ;
129- bookingSeatCountMap [ bookedAt ] = bookingSeatCountMap [ bookedAt ] || 0 ;
130- bookingSeatCountMap [ bookedAt ] ++ ;
131- // Seat references on the current event are non-blocking until the event is fully booked.
132- if (
133- // there are still seats available.
134- bookingSeatCountMap [ bookedAt ] < ( eventType ?. seatsPerTimeSlot || 1 ) &&
135- // and this is the seated event, other event types should be blocked.
136- eventTypeId === eventType ?. id
137- ) {
138- // then we ONLY add the before/after buffer times as busy times.
139- if ( minutesToBlockBeforeEvent ) {
140- aggregate . push ( {
141- start : dayjs ( startTime ) . subtract ( minutesToBlockBeforeEvent , "minute" ) . toDate ( ) ,
142- end : dayjs ( startTime ) . toDate ( ) , // The event starts after the buffer
143- } ) ;
144- }
145- if ( minutesToBlockAfterEvent ) {
146- aggregate . push ( {
147- start : dayjs ( endTime ) . toDate ( ) , // The event ends before the buffer
148- end : dayjs ( endTime ) . add ( minutesToBlockAfterEvent , "minute" ) . toDate ( ) ,
149- } ) ;
150- }
151- return aggregate ;
152- }
153- // if it does get blocked at this point; we remove the bookingSeatCountMap entry
154- // doing this allows using the map later to remove the ranges from calendar busy times.
155- delete bookingSeatCountMap [ bookedAt ] ;
156- }
157- // rescheduling the same booking to the same time should be possible. Why?
158- if ( rest . uid === rescheduleUid ) {
159- return aggregate ;
160- }
161- aggregate . push ( {
162- start : dayjs ( startTime ) . subtract ( minutesToBlockBeforeEvent , "minute" ) . toDate ( ) ,
163- end : dayjs ( endTime ) . add ( minutesToBlockAfterEvent , "minute" ) . toDate ( ) ,
164- title,
165- source : `eventType-${ eventType ?. id } -booking-${ id } ` ,
166- } ) ;
167- return aggregate ;
168- } , [ ] ) ;
190+ let calendarBusyTimesPromise ;
191+ if ( credentials ?. length > 0 && ! bypassBusyCalendarTimes ) {
192+ calendarBusyTimesPromise = getBusyCalendarTimes (
193+ credentials ,
194+ startTime ,
195+ endTime ,
196+ selectedCalendars ,
197+ shouldServeCache
198+ ) ;
199+ promises . push ( calendarBusyTimesPromise ) ;
200+ }
201+
202+ await Promise . all ( promises ) ;
203+
204+ if ( bookingsPromise ) {
205+ bookings = await bookingsPromise ;
206+ }
207+
208+ const { busyTimes, bookingSeatCountMap } = processBookingsToBusyTimes (
209+ bookings || [ ] ,
210+ rescheduleUid ,
211+ eventTypeId ,
212+ beforeEventBuffer ,
213+ afterEventBuffer
214+ ) ;
169215
170216 logger . debug (
171217 `Busy Time from Cal Bookings ${ JSON . stringify ( {
@@ -176,15 +222,10 @@ const _getBusyTimes = async (params: {
176222 ) ;
177223 performance . mark ( "prismaBookingGetEnd" ) ;
178224 performance . measure ( `prisma booking get took $1'` , "prismaBookingGetStart" , "prismaBookingGetEnd" ) ;
179- if ( credentials ?. length > 0 && ! bypassBusyCalendarTimes ) {
225+
226+ if ( credentials ?. length > 0 && ! bypassBusyCalendarTimes && calendarBusyTimesPromise ) {
180227 const startConnectedCalendarsGet = performance . now ( ) ;
181- const calendarBusyTimes = await getBusyCalendarTimes (
182- credentials ,
183- startTime ,
184- endTime ,
185- selectedCalendars ,
186- shouldServeCache
187- ) ;
228+ const calendarBusyTimes = await calendarBusyTimesPromise ;
188229 const endConnectedCalendarsGet = performance . now ( ) ;
189230 logger . debug (
190231 `Connected Calendars get took ${
@@ -204,7 +245,7 @@ const _getBusyTimes = async (params: {
204245 } ) ;
205246
206247 if ( rescheduleUid ) {
207- const originalRescheduleBooking = bookings . find ( ( booking ) => booking . uid === rescheduleUid ) ;
248+ const originalRescheduleBooking = bookings ? .find ( ( booking ) => booking . uid === rescheduleUid ) ;
208249 // calendar busy time from original rescheduled booking should not be blocked
209250 if ( originalRescheduleBooking ) {
210251 openSeatsDateRanges . push ( {
0 commit comments