fix: prevent calendar event creation for unconfirmed round robin reschedules (#26719)

* fix rr booking

* fix test

* use actual prisma

* fix

* Refactor
This commit is contained in:
Anik Dhabal Babu
2026-01-11 16:38:46 +00:00
committed by GitHub
parent 1e98652161
commit 98ef45be18
2 changed files with 171 additions and 7 deletions
+18 -7
View File
@@ -677,13 +677,24 @@ export default class EventManager {
const shouldUpdateBookingReferences =
!!changedOrganizer || isLocationChanged || !!isBookingRequestedReschedule || isDailyVideoRoomExpired;
if (evt.requiresConfirmation && !skipDeleteEventsAndMeetings) {
log.debug("RescheduleRequiresConfirmation: Deleting Event and Meeting for previous booking");
// As the reschedule requires confirmation, we can't update the events and meetings to new time yet. So, just delete them and let it be handled when organizer confirms the booking.
await this.deleteEventsAndMeetings({
event: { ...event, destinationCalendar: previousHostDestinationCalendar },
bookingReferences: booking.references,
});
if (evt.requiresConfirmation) {
if (!skipDeleteEventsAndMeetings) {
log.debug(
"RescheduleRequiresConfirmation: Deleting Event and Meeting for previous booking"
);
// As the reschedule requires confirmation, we can't update the events and meetings to new time yet. So, just delete them and let it be handled when organizer confirms the booking.
await this.deleteEventsAndMeetings({
event: {
...event,
destinationCalendar: previousHostDestinationCalendar,
},
bookingReferences: booking.references,
});
} else {
log.debug(
"RescheduleRequiresConfirmation: Skipping deletion of Event and Meeting due to skipDeleteEventsAndMeetings flag"
);
}
} else {
if (changedOrganizer) {
if (!skipDeleteEventsAndMeetings) {
@@ -18,6 +18,7 @@ import { setupAndTeardown } from "@calcom/testing/lib/bookingScenario/setupAndTe
import { describe, test, vi, expect } from "vitest";
import prisma from "@calcom/prisma";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
@@ -1086,4 +1087,156 @@ describe("Round Robin handleNewBooking", () => {
});
});
});
describe("Round Robin with requiresConfirmation", () => {
test(
"should not create calendar events for unconfirmed round robin bookings on first booking and reschedule",
async () => {
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
});
const roundRobinHost1 = getOrganizer({
name: "RR Host 1",
email: "rrhost1@example.com",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "rrhost1@google-calendar.com",
},
});
const roundRobinHost2 = getOrganizer({
name: "RR Host 2",
email: "rrhost2@example.com",
id: 102,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "rrhost2@google-calendar.com",
},
});
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
await createBookingScenario(
getScenarioData({
eventTypes: [
{
id: 1,
slotInterval: 15,
length: 15,
requiresConfirmation: true,
schedulingType: SchedulingType.ROUND_ROBIN,
users: [
{
id: 101,
},
{
id: 102,
},
],
hosts: [
{ userId: 101, isFixed: false },
{ userId: 102, isFixed: false },
],
schedule: TestData.schedules.IstWorkHours,
},
],
organizer: roundRobinHost1,
usersApartFromOrganizer: [roundRobinHost2],
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
})
);
// Mock calendar - we should NOT see calendar events created for unconfirmed bookings
mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
uid: "MOCK_ID",
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
},
});
// First booking with first host - should be PENDING and NO calendar events
const firstBookingData = getMockRequestDataForBooking({
data: {
eventTypeId: 1,
user: roundRobinHost1.name,
start: `${plus1DateString}T05:00:00.000Z`,
end: `${plus1DateString}T05:15:00.000Z`,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const firstBooking = await handleNewBooking({
bookingData: firstBookingData,
});
// Verify first booking is PENDING
expect(firstBooking.status).toBe(BookingStatus.PENDING);
// Verify first booking has NO calendar references (no calendar events created)
const firstBookingInDb = await prisma.booking.findUnique({
where: {
id: firstBooking.id,
},
include: {
references: true,
},
});
expect(firstBookingInDb?.references).toHaveLength(0);
expect(firstBookingInDb?.status).toBe(BookingStatus.PENDING);
// Now reschedule with second host - should still be PENDING and NO calendar events
const rescheduleBookingData = getMockRequestDataForBooking({
data: {
eventTypeId: 1,
user: roundRobinHost2.name,
rescheduleUid: firstBooking.uid,
start: `${plus1DateString}T06:00:00.000Z`,
end: `${plus1DateString}T06:15:00.000Z`,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
rescheduledBy: booker.email,
},
});
const rescheduledBooking = await handleNewBooking({
bookingData: rescheduleBookingData,
});
// Verify rescheduled booking is still PENDING
expect(rescheduledBooking.status).toBe(BookingStatus.PENDING);
// Verify rescheduled booking has NO calendar references (no calendar events created)
// This is the key fix: rescheduling unconfirmed bookings should NOT create calendar events
const rescheduledBookingInDb = await prisma.booking.findUnique({
where: {
id: rescheduledBooking.id,
},
include: {
references: true,
},
});
expect(rescheduledBookingInDb?.references).toHaveLength(0);
expect(rescheduledBookingInDb?.status).toBe(BookingStatus.PENDING);
}
);
});
});