From ac3699dcb42d95aebf8ae81c9b3fefcc94ea689d Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:15:40 +0200 Subject: [PATCH] fix: set attendeeSeatId in evt for seated booking reschedule emails (#26498) * fix: set attendeeSeatId in evt for seated booking reschedule emails When an attendee reschedules a seated booking, the reschedule/cancel links in emails were using bookingUid instead of seatUid after the first reschedule. This was because evt.attendeeSeatId was not being set before sending the email. This fix ensures that evt.attendeeSeatId is set to bookingSeat.referenceUid so that the email links consistently use seatUid for seated bookings. Co-Authored-By: morgan@cal.com * test: add E2E test for seatUid preservation across multiple reschedules Co-Authored-By: morgan@cal.com * fix: use valid time slots within availability window for E2E test Co-Authored-By: morgan@cal.com * fix: make E2E test self-contained to avoid shared state issues Co-Authored-By: morgan@cal.com * fix: update E2E test to validate seatUid usability across reschedules Co-Authored-By: morgan@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../e2e/seated-bookings.e2e-spec.ts | 105 ++++++++++++++++++ .../attendeeRescheduleSeatedBooking.ts | 3 + 2 files changed, 108 insertions(+) diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts index 1addb6a80c..503b3bab6f 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts @@ -437,6 +437,111 @@ describe("Bookings Endpoints 2024-08-13", () => { }); }); + it("should return valid seatUid for subsequent reschedules", async () => { + const createBody: CreateBookingInput_2024_08_13 = { + start: new Date(Date.UTC(2030, 0, 10, 10, 0, 0)).toISOString(), + eventTypeId: seatedEventTypeId, + attendee: { + name: `Reschedule Test ${randomString()}`, + email: `reschedule-test-${randomString()}@api.com`, + timeZone: "Europe/Rome", + language: "it", + }, + bookingFieldsResponses: { + codingLanguage: "Go", + }, + metadata: { + userId: "300", + }, + }; + + const createResponse = await request(app.getHttpServer()) + .post("/v2/bookings") + .send(createBody) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(201); + + const createData: CreateBookingOutput_2024_08_13 = createResponse.body; + expect(createData.status).toEqual(SUCCESS_STATUS); + expect(responseDataIsCreateSeatedBooking(createData.data)).toBe(true); + + if (!responseDataIsCreateSeatedBooking(createData.data)) { + throw new Error("Invalid response data - expected seated booking"); + } + + let testBooking: CreateSeatedBookingOutput_2024_08_13 = createData.data; + expect(testBooking.seatUid).toBeDefined(); + expect(testBooking.attendees[0].seatUid).toBeDefined(); + + const firstRescheduleBody: RescheduleSeatedBookingInput_2024_08_13 = { + start: new Date(Date.UTC(2030, 0, 10, 11, 0, 0)).toISOString(), + seatUid: testBooking.attendees[0].seatUid, + }; + + const firstRescheduleResponse = await request(app.getHttpServer()) + .post(`/v2/bookings/${testBooking.uid}/reschedule`) + .send(firstRescheduleBody) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(201); + + const firstRescheduleData: RescheduleBookingOutput_2024_08_13 = firstRescheduleResponse.body; + expect(firstRescheduleData.status).toEqual(SUCCESS_STATUS); + expect(responseDataIsGetSeatedBooking(firstRescheduleData.data)).toBe(true); + + if (!responseDataIsGetSeatedBooking(firstRescheduleData.data)) { + throw new Error("Invalid response data - expected seated booking"); + } + + testBooking = firstRescheduleData.data; + expect(testBooking.seatUid).toBeDefined(); + expect(testBooking.attendees[0].seatUid).toBeDefined(); + + const secondRescheduleBody: RescheduleSeatedBookingInput_2024_08_13 = { + start: new Date(Date.UTC(2030, 0, 10, 12, 0, 0)).toISOString(), + seatUid: testBooking.attendees[0].seatUid, + }; + + const secondRescheduleResponse = await request(app.getHttpServer()) + .post(`/v2/bookings/${testBooking.uid}/reschedule`) + .send(secondRescheduleBody) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(201); + + const secondRescheduleData: RescheduleBookingOutput_2024_08_13 = secondRescheduleResponse.body; + expect(secondRescheduleData.status).toEqual(SUCCESS_STATUS); + expect(responseDataIsGetSeatedBooking(secondRescheduleData.data)).toBe(true); + + if (!responseDataIsGetSeatedBooking(secondRescheduleData.data)) { + throw new Error("Invalid response data - expected seated booking"); + } + + testBooking = secondRescheduleData.data; + expect(testBooking.seatUid).toBeDefined(); + expect(testBooking.attendees[0].seatUid).toBeDefined(); + + const thirdRescheduleBody: RescheduleSeatedBookingInput_2024_08_13 = { + start: new Date(Date.UTC(2030, 0, 10, 13, 0, 0)).toISOString(), + seatUid: testBooking.attendees[0].seatUid, + }; + + const thirdRescheduleResponse = await request(app.getHttpServer()) + .post(`/v2/bookings/${testBooking.uid}/reschedule`) + .send(thirdRescheduleBody) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(201); + + const thirdRescheduleData: RescheduleBookingOutput_2024_08_13 = thirdRescheduleResponse.body; + expect(thirdRescheduleData.status).toEqual(SUCCESS_STATUS); + expect(responseDataIsGetSeatedBooking(thirdRescheduleData.data)).toBe(true); + + if (!responseDataIsGetSeatedBooking(thirdRescheduleData.data)) { + throw new Error("Invalid response data - expected seated booking"); + } + + expect(thirdRescheduleData.data.seatUid).toBeDefined(); + expect(thirdRescheduleData.data.attendees[0].seatUid).toBeDefined(); + }); + describe("book an event type with attendees disabled but auth provided", () => { let booking: CreateSeatedBookingOutput_2024_08_13; it("should book an event type with attendees disabled and no auth provided", async () => { diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 6b3fa17cb1..8644e9e5d1 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -23,6 +23,9 @@ const attendeeRescheduleSeatedBooking = async ( seatAttendee["language"] = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" }; + // Set attendeeSeatId so that reschedule/cancel links in emails use seatUid instead of bookingUid + evt.attendeeSeatId = bookingSeat?.referenceUid; + // 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