From 9f0d035a3b1b610932b4e9c9e7e059f336e3286b Mon Sep 17 00:00:00 2001 From: Rajiv Sahal Date: Mon, 19 Jan 2026 14:00:29 +0530 Subject: [PATCH] fix: pass seatUid when rescheduling seated booking from booking limit error (#26389) * fix: pass seatUid when rescheduling seated booking from booking limit error Co-Authored-By: rajiv@cal.com * test: add test case for seatUid in booking limit reschedule error for seated events Co-Authored-By: rajiv@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../Booker/components/hooks/useBookings.ts | 4 +- .../checkActiveBookingsLimitForBooker.ts | 11 ++ .../test/booking-validations.test.ts | 117 ++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/packages/features/bookings/Booker/components/hooks/useBookings.ts b/packages/features/bookings/Booker/components/hooks/useBookings.ts index 7ef1a668a6..1f88fbc407 100644 --- a/packages/features/bookings/Booker/components/hooks/useBookings.ts +++ b/packages/features/bookings/Booker/components/hooks/useBookings.ts @@ -399,12 +399,12 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, isBookin } const error = err as Error & { - data: { rescheduleUid: string; startTime: string; attendees: string[] }; + data: { rescheduleUid: string; startTime: string; attendees: string[]; seatUid?: string }; traceId?: string; }; if (error.message === ErrorCode.BookerLimitExceededReschedule && error.data?.rescheduleUid) { - setRescheduleUid(error.data?.rescheduleUid); + setRescheduleUid(error.data?.seatUid ?? error.data?.rescheduleUid); setBookingData({ uid: error.data?.rescheduleUid, startTime: error.data?.startTime, diff --git a/packages/features/bookings/lib/handleNewBooking/checkActiveBookingsLimitForBooker.ts b/packages/features/bookings/lib/handleNewBooking/checkActiveBookingsLimitForBooker.ts index 0dec8cf173..b0936ef729 100644 --- a/packages/features/bookings/lib/handleNewBooking/checkActiveBookingsLimitForBooker.ts +++ b/packages/features/bookings/lib/handleNewBooking/checkActiveBookingsLimitForBooker.ts @@ -102,12 +102,22 @@ const checkActiveBookingsLimitAndOfferReschedule = async ({ select: { name: true, email: true, + bookingSeat: { + select: { + referenceUid: true, + }, + }, + }, + where: { + email: bookerEmail, }, }, }, }); const lastBooking = bookingsCount[bookingsCount.length - 1]; + // Get the seatUid for the booker's seat in this booking (if it's a seated event) + const seatUid = lastBooking?.attendees[0]?.bookingSeat?.referenceUid; if (bookingsCount.length >= maxActiveBookingsPerBooker) { log.warn(`Maximum booking limit reached for ${bookerEmail} for event type ${eventTypeId}`); @@ -118,6 +128,7 @@ const checkActiveBookingsLimitAndOfferReschedule = async ({ rescheduleUid: lastBooking.uid, startTime: lastBooking.startTime, attendees: lastBooking.attendees, + seatUid, } ); } diff --git a/packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts b/packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts index df37f9c031..d623550cba 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts @@ -12,6 +12,7 @@ import { getScenarioData, getGoogleCalendarCredential, mockCalendarToHaveNoBusySlots, + getMockBookingAttendee, } from "@calcom/testing/lib/bookingScenario/bookingScenario"; import { getMockRequestDataForBooking } from "@calcom/testing/lib/bookingScenario/getMockRequestDataForBooking"; import { setupAndTeardown } from "@calcom/testing/lib/bookingScenario/setupAndTeardown"; @@ -402,6 +403,122 @@ describe("Booking Validation Specifications", () => { ); } }); + + test("includes seatUid in reschedule error for seated events", async () => { + vi.setSystemTime(new Date("2025-01-01")); + const plus1DateString = "2025-01-02"; + const plus2DateString = "2025-01-03"; + + const handleNewBooking = getNewBookingHandler(); + + const bookerA = getBooker({ + email: "bookerA@example.com", + name: "Booker A", + }); + + const bookerB = getBooker({ + email: "bookerB@example.com", + name: "Booker B", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + credentials: [getGoogleCalendarCredential()], + selectedCalendars: [TestData.selectedCalendars.google], + }); + + const seatReferenceUidA = "seat-reference-uid-a"; + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: 30, + length: 30, + maxActiveBookingsPerBooker: 1, + maxActiveBookingPerBookerOfferReschedule: true, + seatsPerTimeSlot: 3, + users: [ + { + id: 101, + }, + ], + }, + ], + organizer, + apps: [TestData.apps["google-calendar"]], + bookings: [ + { + uid: "existing-booking-1", + eventTypeId: 1, + userId: organizer.id, + startTime: `${plus1DateString}T10:00:00.000Z`, + endTime: `${plus1DateString}T10:30:00.000Z`, + title: "Existing Booking", + status: BookingStatus.ACCEPTED, + attendees: [ + getMockBookingAttendee({ + id: 1, + name: bookerA.name, + email: bookerA.email, + locale: "en", + timeZone: "America/Toronto", + bookingSeat: { + referenceUid: seatReferenceUidA, + data: {}, + }, + }), + getMockBookingAttendee({ + id: 2, + name: bookerB.name, + email: bookerB.email, + locale: "en", + timeZone: "America/Toronto", + bookingSeat: { + referenceUid: "seat-reference-uid-b", + data: {}, + }, + }), + ], + }, + ], + }) + ); + + await mockCalendarToHaveNoBusySlots("googlecalendar", {}); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + eventTypeId: 1, + start: `${plus2DateString}T10:00:00.000Z`, + end: `${plus2DateString}T10:30:00.000Z`, + responses: { + email: bookerA.email, + name: bookerA.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + try { + await handleNewBooking({ + bookingData: mockBookingData, + }); + expect.fail("Expected booking to throw booker_limit_exceeded_error_reschedule"); + } catch (error) { + expect(error.message).toEqual("booker_limit_exceeded_error_reschedule"); + expect(error.data).toEqual( + expect.objectContaining({ + rescheduleUid: "existing-booking-1", + seatUid: seatReferenceUidA, + }) + ); + } + }); }); describe("User Email Verification Setting", () => {