fix: Seat Attendee Rescheduling Logic (#14784)

* When rescheduling to new time slot don't trigger reschedule logic

* Add unique iCalUID when rescheduling to new slot

* Generating iCalUID use v4 uuid

* When moving an attendee to a new booking. Only move the attendee

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
Joe Au-Yeung
2024-06-15 08:57:52 +00:00
committed by GitHub
co-authored by Udit Takkar
parent d666f2926c
commit e1bdac077a
5 changed files with 51 additions and 32 deletions
+5 -3
View File
@@ -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;
@@ -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
@@ -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) => {
@@ -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: {
+3
View File
@@ -70,6 +70,9 @@ export type NewTimeSlotBooking = Prisma.BookingGetPayload<{
select: {
id: true;
uid: true;
iCalUID: true;
userId: true;
references: true;
attendees: {
include: {
bookingSeat: true;