fix: Unable to cancel a team event if any event host is an attendee (#22233)
* fix * Update handleCancelBooking.ts * Update handleCancelBooking.ts * update comment * fix type error * added test * Update packages/features/bookings/lib/handleCancelBooking.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update handleCancelBooking.test.ts * refactor --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
parent
4404699fa6
commit
076afdc00e
@@ -185,8 +185,10 @@ async function handler(input: CancelBookingInput) {
|
||||
const teamMembersPromises = [];
|
||||
const attendeesListPromises = [];
|
||||
const hostsPresent = !!bookingToDelete.eventType?.hosts;
|
||||
const hostEmails = new Set(bookingToDelete.eventType?.hosts?.map((host) => host.user.email) ?? []);
|
||||
|
||||
for (const attendee of bookingToDelete.attendees) {
|
||||
for (let index = 0; index < bookingToDelete.attendees.length; index++) {
|
||||
const attendee = bookingToDelete.attendees[index];
|
||||
const attendeeObject = {
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
@@ -198,18 +200,18 @@ async function handler(input: CancelBookingInput) {
|
||||
},
|
||||
};
|
||||
|
||||
// Check for the presence of hosts to determine if it is a team event type
|
||||
if (hostsPresent) {
|
||||
// If the attendee is a host then they are a team member
|
||||
const teamMember = bookingToDelete.eventType?.hosts.some((host) => host.user.email === attendee.email);
|
||||
if (teamMember) {
|
||||
// The first attendee is the booker in all cases, so always consider them as an attendee.
|
||||
if (index === 0) {
|
||||
attendeesListPromises.push(attendeeObject);
|
||||
} else {
|
||||
const isTeamEvent = hostEmails.size > 0;
|
||||
const isTeamMember = isTeamEvent && hostEmails.has(attendee.email);
|
||||
|
||||
if (isTeamMember) {
|
||||
teamMembersPromises.push(attendeeObject);
|
||||
// If not then they are an attendee
|
||||
} else {
|
||||
attendeesListPromises.push(attendeeObject);
|
||||
}
|
||||
} else {
|
||||
attendeesListPromises.push(attendeeObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -263,4 +263,161 @@ describe("Cancel Booking", () => {
|
||||
|
||||
expect(processPaymentRefund).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Should successfully cancel round robin team event when host is also an attendee with workflow emails", async () => {
|
||||
const handleCancelBooking = (await import("@calcom/features/bookings/lib/handleCancelBooking")).default;
|
||||
|
||||
const organizer = getOrganizer({
|
||||
name: "Host Organizer",
|
||||
email: "host-organizer@example.com",
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const hostAttendee = getOrganizer({
|
||||
name: "Host Attendee",
|
||||
email: "host-attendee@example.com",
|
||||
id: 102,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const uidOfBookingToBeCancelled = "round-robin-booking-uid";
|
||||
const idOfBookingToBeCancelled = 2030;
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
|
||||
await createBookingScenario(
|
||||
getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
eventTriggers: ["BOOKING_CANCELLED"],
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
active: true,
|
||||
eventTypeId: 2,
|
||||
appId: null,
|
||||
},
|
||||
],
|
||||
eventTypes: [
|
||||
{
|
||||
id: 2,
|
||||
slotInterval: 30,
|
||||
length: 30,
|
||||
schedulingType: "ROUND_ROBIN",
|
||||
teamId: 1,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
{
|
||||
id: 102,
|
||||
},
|
||||
],
|
||||
hosts: [
|
||||
{
|
||||
userId: 101,
|
||||
isFixed: false,
|
||||
},
|
||||
{
|
||||
userId: 102,
|
||||
isFixed: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
workflows: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Cancellation Email Workflow",
|
||||
teamId: 1,
|
||||
trigger: "EVENT_CANCELLED",
|
||||
action: "EMAIL_ATTENDEE",
|
||||
template: "REMINDER",
|
||||
activeOn: [2],
|
||||
},
|
||||
],
|
||||
bookings: [
|
||||
{
|
||||
id: idOfBookingToBeCancelled,
|
||||
uid: uidOfBookingToBeCancelled,
|
||||
eventTypeId: 2,
|
||||
userId: 101,
|
||||
responses: {
|
||||
email: hostAttendee.email,
|
||||
name: hostAttendee.name,
|
||||
location: { optionValue: "", value: BookingLocations.CalVideo },
|
||||
},
|
||||
status: BookingStatus.ACCEPTED,
|
||||
startTime: `${plus1DateString}T05:00:00.000Z`,
|
||||
endTime: `${plus1DateString}T05:30:00.000Z`,
|
||||
attendees: [
|
||||
{
|
||||
email: hostAttendee.email,
|
||||
timeZone: "Asia/Kolkata",
|
||||
locale: "en",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
teams: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Test Team",
|
||||
slug: "test-team",
|
||||
},
|
||||
],
|
||||
users: [organizer, hostAttendee],
|
||||
apps: [TestData.apps["daily-video"]],
|
||||
})
|
||||
);
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
videoMeetingData: {
|
||||
id: "MOCK_ID",
|
||||
password: "MOCK_PASS",
|
||||
url: `http://mock-dailyvideo.example.com/meeting-2`,
|
||||
},
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar", {
|
||||
create: {
|
||||
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID_2",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await handleCancelBooking({
|
||||
bookingData: {
|
||||
id: idOfBookingToBeCancelled,
|
||||
uid: uidOfBookingToBeCancelled,
|
||||
cancelledBy: organizer.email,
|
||||
cancellationReason: "Testing round robin cancellation with host as attendee",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.bookingId).toBe(idOfBookingToBeCancelled);
|
||||
expect(result.bookingUid).toBe(uidOfBookingToBeCancelled);
|
||||
expect(result.onlyRemovedAttendee).toBe(false);
|
||||
|
||||
expectBookingCancelledWebhookToHaveBeenFired({
|
||||
booker: hostAttendee,
|
||||
organizer,
|
||||
location: BookingLocations.CalVideo,
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
payload: {
|
||||
cancelledBy: organizer.email,
|
||||
organizer: {
|
||||
id: organizer.id,
|
||||
username: organizer.username,
|
||||
email: organizer.email,
|
||||
name: organizer.name,
|
||||
timeZone: organizer.timeZone,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user