fix: incorrect booking seats full error on collective seated event (#15602)

* fix: wrong booking seats full error on collective event

* test: verify seat availability calculation based on booked seats, not total attendees

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
This commit is contained in:
Kartik Saini
2024-07-08 18:02:02 +00:00
committed by GitHub
co-authored by Keith Williams Amit Sharma
parent 263c0ac7ef
commit 4a2a2d165b
2 changed files with 187 additions and 1 deletions
@@ -47,7 +47,10 @@ const createNewSeat = async (
evt = { ...evt, attendees: [...bookingAttendees, invitee[0]] };
if (eventType.seatsPerTimeSlot && eventType.seatsPerTimeSlot <= seatedBooking.attendees.length) {
if (
eventType.seatsPerTimeSlot &&
eventType.seatsPerTimeSlot <= seatedBooking.attendees.filter((attendee) => !!attendee.bookingSeat).length
) {
throw new HttpError({ statusCode: 409, message: ErrorCode.BookingSeatsFull });
}
@@ -5,6 +5,8 @@ import {
TestData,
getOrganizer,
createBookingScenario,
getGoogleCalendarCredential,
Timezones,
getScenarioData,
mockSuccessfulVideoMeetingCreation,
BookingLocations,
@@ -20,6 +22,7 @@ import { describe, test, vi, expect } from "vitest";
import { appStoreMetadata } from "@calcom/app-store/apps.metadata.generated";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import * as handleSeatsModule from "../handleSeats";
@@ -873,6 +876,186 @@ describe("handleSeats", () => {
await expect(() => handleNewBooking(req)).rejects.toThrowError(ErrorCode.BookingSeatsFull);
});
test("Verify Seat Availability Calculation Based on Booked Seats, Not Total Attendees", 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,
defaultScheduleId: null,
schedules: [TestData.schedules.IstMorningShift],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "organizer@google-calendar.com",
},
});
const otherTeamMembers = [
{
name: "Other Team Member 1",
username: "other-team-member-1",
timeZone: Timezones["+5:30"],
defaultScheduleId: null,
email: "other-team-member-1@example.com",
id: 102,
schedules: [TestData.schedules.IstEveningShift],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "other-team-member-1@google-calendar.com",
},
},
];
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: "collective-team-seated-event",
slotInterval: 30,
length: 30,
schedulingType: SchedulingType.COLLECTIVE,
users: [
{
id: 101,
},
{
id: 102,
},
],
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "event-type-1@google-calendar.com",
},
seatsPerTimeSlot: 2,
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: "Other Team Member 1",
email: "other-team-member-1@example.com",
locale: "en",
timeZone: "America/Toronto",
}),
getMockBookingAttendee({
id: 2,
name: "Seat 1",
email: "seat1@test.com",
locale: "en",
timeZone: "America/Toronto",
bookingSeat: {
referenceUid: "booking-seat-1",
data: {},
},
}),
],
},
],
organizer,
usersApartFromOrganizer: otherTeamMembers,
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
})
);
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 },
},
bookingUid: bookingUid,
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 with booking seat
expect(newAttendee?.bookingSeat).toEqual(
expect.objectContaining({
referenceUid: expect.any(String),
data: expect.any(Object),
bookingId: bookingId,
})
);
// Verify that the booking seat count is now 2 out of 2
const bookingSeatCount = await prismaMock.bookingSeat.count({
where: {
bookingId: bookingId,
},
});
expect(bookingSeatCount).toBe(2);
});
});
describe("Rescheduling a booking", () => {