fix: double bookings on seated event (#13406)

* fix: double bookings on seated event

* test: correcting handleSeats.test.ts

fix: seat rescheduling

* Add test for booking w/o bookingUid in req

* Add back `bookingUid`

* add 'reqBookingUid' parameter

* Add `reqBookingUid`

* Fix

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
This commit is contained in:
Mehul
2024-02-16 12:48:57 -05:00
committed by GitHub
co-authored by Keith Williams Udit Takkar Joe Au-Yeung Joe Au-Yeung
parent c08365f09c
commit 5b50a469b2
4 changed files with 140 additions and 8 deletions
@@ -1511,7 +1511,7 @@ async function handler(
};
// For seats, if the booking already exists then we want to add the new attendee to the existing booking
if (eventType.seatsPerTimeSlot && (reqBody.bookingUid || rescheduleUid)) {
if (eventType.seatsPerTimeSlot) {
const newBooking = await handleSeats({
rescheduleUid,
reqBookingUid: reqBody.bookingUid,
@@ -28,7 +28,6 @@ const createNewSeat = async (
attendeeLanguage,
invitee,
eventType,
reqBookingUid,
additionalNotes,
noEmail,
paymentAppData,
@@ -68,7 +67,7 @@ const createNewSeat = async (
await prisma.booking.update({
where: {
uid: reqBookingUid,
uid: seatedBooking.uid,
},
include: {
attendees: true,
@@ -41,9 +41,10 @@ const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => {
{
uid: rescheduleUid || reqBookingUid,
},
{
eventTypeId: eventType.id,
startTime: evt.startTime,
startTime: new Date(evt.startTime),
},
],
status: BookingStatus.ACCEPTED,
@@ -63,10 +64,15 @@ const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => {
},
});
if (!seatedBooking) {
if (!seatedBooking && rescheduleUid) {
throw new HttpError({ statusCode: 404, message: ErrorCode.BookingNotFound });
}
// We might be trying to create a new booking
if (!seatedBooking) {
return;
}
// See if attendee is already signed up for timeslot
if (
seatedBooking.attendees.find((attendee) => attendee.email === invitee[0].email) &&
@@ -90,7 +90,7 @@ describe("handleSeats", () => {
await handleNewBooking(req);
expect(spy).toHaveBeenCalledTimes(0);
expect(spy).toHaveBeenCalledTimes(1);
});
test("handleSeats is called when a new attendee is added", async () => {
@@ -194,7 +194,6 @@ describe("handleSeats", () => {
expect(handleSeatsCall).toEqual(
expect.objectContaining({
bookerEmail: booker.email,
reqBookingUid: bookingUid,
reqBodyUser: reqBookingUser,
tAttendees: expect.any(Function),
additionalNotes: expect.anything(),
@@ -338,7 +337,6 @@ describe("handleSeats", () => {
expect.objectContaining({
rescheduleUid: bookingUid,
bookerEmail: booker.email,
reqBookingUid: bookingUid,
reqBodyUser: reqBookingUser,
tAttendees: expect.any(Function),
additionalNotes: expect.anything(),
@@ -511,6 +509,135 @@ describe("handleSeats", () => {
);
});
// Testing in case of a wave of people book a time slot at the same time
test("Attendee should be added to existing seated event when bookingUid is not present", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const booker = getBooker({
email: "seat2@example.com",
name: "Seat 2",
});
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
});
const bookingId = 1;
const bookingUid = "abc123";
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const bookingStartTime = `${plus1DateString}T04:00:00Z`;
const bookingEndTime = `${plus1DateString}T04:30:00Z`;
await createBookingScenario(
getScenarioData({
eventTypes: [
{
id: 1,
slug: "seated-event",
slotInterval: 30,
length: 30,
users: [
{
id: 101,
},
],
seatsPerTimeSlot: 3,
seatsShowAttendees: false,
},
],
bookings: [
{
id: bookingId,
uid: bookingUid,
eventTypeId: 1,
status: BookingStatus.ACCEPTED,
startTime: bookingStartTime,
endTime: bookingEndTime,
metadata: {
videoCallUrl: "https://existing-daily-video-call-url.example.com",
},
references: [
{
type: appStoreMetadata.dailyvideo.type,
uid: "MOCK_ID",
meetingId: "MOCK_ID",
meetingPassword: "MOCK_PASS",
meetingUrl: "http://mock-dailyvideo.example.com",
credentialId: null,
},
],
attendees: [
getMockBookingAttendee({
id: 1,
name: "Seat 1",
email: "seat1@test.com",
locale: "en",
timeZone: "America/Toronto",
bookingSeat: {
referenceUid: "booking-seat-1",
data: {},
},
}),
],
},
],
organizer,
})
);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
videoMeetingData: {
id: "MOCK_ID",
password: "MOCK_PASS",
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
const reqBookingUser = "seatedAttendee";
const mockBookingData = getMockRequestDataForBooking({
data: {
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
user: reqBookingUser,
},
});
const { req } = createMockNextJsRequest({
method: "POST",
body: mockBookingData,
});
await handleNewBooking(req);
const newAttendee = await prismaMock.attendee.findFirst({
where: {
email: booker.email,
bookingId: bookingId,
},
include: {
bookingSeat: true,
},
});
// Check for the existence of the new attendee w/ booking seat
expect(newAttendee?.bookingSeat).toEqual(
expect.objectContaining({
referenceUid: expect.any(String),
data: expect.any(Object),
bookingId: bookingId,
})
);
});
test("If attendee is already a part of the booking then throw an error", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;