diff --git a/packages/emails/lib/getICalUID.ts b/packages/emails/lib/getICalUID.ts index 1dc7e7216d..9ecf9f4a2b 100644 --- a/packages/emails/lib/getICalUID.ts +++ b/packages/emails/lib/getICalUID.ts @@ -1,5 +1,5 @@ import short from "short-uuid"; -import { v5 as uuidv5 } from "uuid"; +import { v4 as uuidv4 } from "uuid"; import { APP_NAME } from "@calcom/lib/constants"; @@ -15,11 +15,13 @@ const getICalUID = ({ uid, event, defaultToEventUid, + attendeeId, }: { uid?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any event?: { iCalUID?: string | null; uid?: string | null; [key: string]: any }; defaultToEventUid?: boolean; + attendeeId?: number; }) => { if (event?.iCalUID) return event.iCalUID; @@ -29,8 +31,8 @@ const getICalUID = ({ const translator = short(); - uid = translator.fromUUID(uuidv5(APP_NAME, uuidv5.URL)); - return `${uid}@${APP_NAME}`; + uid = translator.fromUUID(uuidv4()); + return `${uid}${attendeeId ? `${attendeeId}` : ""}@${APP_NAME}`; }; export default getICalUID; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 7507b3fc3a..85bc9fbdee 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1731,6 +1731,7 @@ async function handler( eventTrigger, responses, }); + if (newBooking) { req.statusCode = 201; const bookingResponse = { @@ -1745,6 +1746,13 @@ async function handler( ...bookingResponse, ...luckyUserResponse, }; + } else { + // Rescheduling logic for the original seated event was handled in handleSeats + // We want to use new booking logic for the new time slot + originalRescheduledBooking = null; + evt.iCalUID = getICalUID({ + attendeeId: bookingSeat?.attendeeId, + }); } } if (isTeamEventType) { @@ -1754,7 +1762,6 @@ async function handler( id: eventType.team?.id ?? 0, }; } - if (reqBody.recurringEventId && eventType.recurringEvent) { // Overriding the recurring event configuration count to be the actual number of events booked for // the recurring event (equal or less than recurring event configuration count) @@ -1876,7 +1883,7 @@ async function handler( let videoCallUrl; //this is the actual rescheduling logic - if (originalRescheduledBooking?.uid) { + if (!eventType.seatsPerTimeSlot && originalRescheduledBooking?.uid) { log.silly("Rescheduling booking", originalRescheduledBooking.uid); try { // cancel workflow reminders from previous rescheduled booking diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 31d289e957..c758670597 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -3,6 +3,7 @@ import { cloneDeep } from "lodash"; import type EventManager from "@calcom/core/EventManager"; import { sendRescheduledSeatEmail } from "@calcom/emails"; +import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; import type { Person, CalendarEvent } from "@calcom/types/Calendar"; @@ -17,11 +18,28 @@ const attendeeRescheduleSeatedBooking = async ( originalBookingEvt: CalendarEvent, eventManager: EventManager ) => { - const { tAttendees, bookingSeat, bookerEmail, rescheduleUid, evt } = rescheduleSeatedBookingObject; + const { tAttendees, bookingSeat, bookerEmail, evt } = rescheduleSeatedBookingObject; let { originalRescheduledBooking } = rescheduleSeatedBookingObject; seatAttendee["language"] = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" }; + // Update the original calendar event by removing the attendee that is rescheduling + if (originalBookingEvt && originalRescheduledBooking) { + // Event would probably be deleted so we first check than instead of updating references + const filteredAttendees = originalRescheduledBooking?.attendees.filter((attendee) => { + return attendee.email !== bookerEmail; + }); + const deletedReference = await lastAttendeeDeleteBooking( + originalRescheduledBooking, + filteredAttendees, + originalBookingEvt + ); + + if (!deletedReference) { + await eventManager.updateCalendarAttendees(originalBookingEvt, originalRescheduledBooking); + } + } + // If there is no booking then remove the attendee from the old booking and create a new one if (!newTimeSlotBooking) { await prisma.attendee.delete({ @@ -30,23 +48,6 @@ const attendeeRescheduleSeatedBooking = async ( }, }); - // Update the original calendar event by removing the attendee that is rescheduling - if (originalBookingEvt && originalRescheduledBooking) { - // Event would probably be deleted so we first check than instead of updating references - const filteredAttendees = originalRescheduledBooking?.attendees.filter((attendee) => { - return attendee.email !== bookerEmail; - }); - const deletedReference = await lastAttendeeDeleteBooking( - originalRescheduledBooking, - filteredAttendees, - originalBookingEvt - ); - - if (!deletedReference) { - await eventManager.updateCalendarAttendees(originalBookingEvt, originalRescheduledBooking); - } - } - // We don't want to trigger rescheduling logic of the original booking originalRescheduledBooking = null; @@ -76,17 +77,19 @@ const attendeeRescheduleSeatedBooking = async ( ]); } - const copyEvent = cloneDeep(evt); + // Add the new attendees to the new time slot booking attendees + for (const attendee of newTimeSlotBooking.attendees) { + const language = await getTranslation(attendee.locale ?? "en", "common"); + evt.attendees.push({ + email: attendee.email, + name: attendee.name, + language, + }); + } - const updateManager = await eventManager.reschedule(copyEvent, rescheduleUid, newTimeSlotBooking.id); + const copyEvent = cloneDeep({ ...evt, iCalUID: newTimeSlotBooking.iCalUID }); - const results = updateManager.results; - - const calendarResult = results.find((result) => result.type.includes("_calendar")); - - evt.iCalUID = Array.isArray(calendarResult?.updatedEvent) - ? calendarResult?.updatedEvent[0]?.iCalUID - : calendarResult?.updatedEvent?.iCalUID || undefined; + await eventManager.updateCalendarAttendees(copyEvent, newTimeSlotBooking); await sendRescheduledSeatEmail(copyEvent, seatAttendee as Person); const filteredAttendees = originalRescheduledBooking?.attendees.filter((attendee) => { diff --git a/packages/features/bookings/lib/handleSeats/reschedule/rescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/rescheduleSeatedBooking.ts index 50913ff383..a08b26cd79 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/rescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/rescheduleSeatedBooking.ts @@ -39,11 +39,14 @@ const rescheduleSeatedBooking = async ( select: { id: true, uid: true, + iCalUID: true, + userId: true, attendees: { include: { bookingSeat: true, }, }, + references: true, }, }); @@ -82,6 +85,7 @@ const rescheduleSeatedBooking = async ( startTime: dayjs(originalRescheduledBooking.startTime).utc().format(), endTime: dayjs(originalRescheduledBooking.endTime).utc().format(), attendees: updatedBookingAttendees, + iCalUID: originalRescheduledBooking.iCalUID, // If the location is a video integration then include the videoCallData ...(videoReference && { videoCallData: { diff --git a/packages/features/bookings/lib/handleSeats/types.d.ts b/packages/features/bookings/lib/handleSeats/types.d.ts index 28b5aaca0e..9233a0fd16 100644 --- a/packages/features/bookings/lib/handleSeats/types.d.ts +++ b/packages/features/bookings/lib/handleSeats/types.d.ts @@ -70,6 +70,9 @@ export type NewTimeSlotBooking = Prisma.BookingGetPayload<{ select: { id: true; uid: true; + iCalUID: true; + userId: true; + references: true; attendees: { include: { bookingSeat: true;