Fix workflows on seated events (#8034)
* Fix workflows on seated events * Fix new args required in handleConfirmation * Update function import to new name * added email to override for workflows
This commit is contained in:
parent
7fde30edf9
commit
5c763389f7
|
@ -1,7 +1,7 @@
|
|||
import type { TFunction } from "next-i18next";
|
||||
|
||||
import { guessEventLocationType } from "@calcom/app-store/locations";
|
||||
import { getVideoCallUrl } from "@calcom/lib/CalEventParser";
|
||||
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
|
@ -19,7 +19,7 @@ export function LocationInfo(props: { calEvent: CalendarEvent; t: TFunction }) {
|
|||
let meetingUrl = location?.search(/^https?:/) !== -1 ? location : undefined;
|
||||
|
||||
if (props.calEvent) {
|
||||
meetingUrl = getVideoCallUrl(props.calEvent) || meetingUrl;
|
||||
meetingUrl = getVideoCallUrlFromCalEvent(props.calEvent) || meetingUrl;
|
||||
}
|
||||
|
||||
const isPhone = location?.startsWith("+");
|
||||
|
|
|
@ -331,11 +331,11 @@ async function handler(req: CustomRequest) {
|
|||
|
||||
//Workflows - schedule reminders
|
||||
if (bookingToDelete.eventType?.workflows) {
|
||||
await sendCancelledReminders(
|
||||
bookingToDelete.eventType?.workflows,
|
||||
bookingToDelete.smsReminderNumber,
|
||||
evt
|
||||
);
|
||||
await sendCancelledReminders({
|
||||
workflows: bookingToDelete.eventType?.workflows,
|
||||
smsReminderNumber: bookingToDelete.smsReminderNumber,
|
||||
evt,
|
||||
});
|
||||
}
|
||||
|
||||
let updatedBookings: {
|
||||
|
|
|
@ -175,14 +175,12 @@ export async function handleConfirmation(args: {
|
|||
|
||||
const isFirstBooking = index === 0;
|
||||
|
||||
await scheduleWorkflowReminders(
|
||||
updatedBookings[index]?.eventType?.workflows || [],
|
||||
updatedBookings[index].smsReminderNumber,
|
||||
evtOfBooking,
|
||||
false,
|
||||
false,
|
||||
isFirstBooking
|
||||
);
|
||||
await scheduleWorkflowReminders({
|
||||
workflows: updatedBookings[index]?.eventType?.workflows || [],
|
||||
smsReminderNumber: updatedBookings[index].smsReminderNumber,
|
||||
calendarEvent: evtOfBooking,
|
||||
isFirstRecurringEvent: isFirstBooking,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -38,7 +38,7 @@ import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/rem
|
|||
import { deleteScheduledSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||
import { getVideoCallUrl } from "@calcom/lib/CalEventParser";
|
||||
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
|
||||
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||
|
@ -911,15 +911,15 @@ async function handler(
|
|||
return deletedReferences;
|
||||
};
|
||||
|
||||
const handleSeats = async (): Promise<
|
||||
| (Partial<Booking> & {
|
||||
appsStatus?: AppsStatus[];
|
||||
seatReferenceUid?: string;
|
||||
paymentUid?: string;
|
||||
message?: string;
|
||||
})
|
||||
| null
|
||||
> => {
|
||||
const handleSeats = async () => {
|
||||
let resultBooking:
|
||||
| (Partial<Booking> & {
|
||||
appsStatus?: AppsStatus[];
|
||||
seatReferenceUid?: string;
|
||||
paymentUid?: string;
|
||||
message?: string;
|
||||
})
|
||||
| null = null;
|
||||
const booking = await prisma.booking.findUniqueOrThrow({
|
||||
where: {
|
||||
uid: rescheduleUid || reqBody.bookingUid,
|
||||
|
@ -1093,144 +1093,151 @@ async function handler(
|
|||
cancellationReason: "$RCH$" + (rescheduleReason ? rescheduleReason : ""), // Removable code prefix to differentiate cancellation from rescheduling for email
|
||||
});
|
||||
}
|
||||
const resultBooking = await resultBookingQuery(newBooking.id);
|
||||
const foundBooking = await findBookingQuery(newBooking.id);
|
||||
|
||||
return { ...resultBooking, appsStatus: newBooking.appsStatus };
|
||||
}
|
||||
resultBooking = { ...foundBooking, appsStatus: newBooking.appsStatus };
|
||||
} else {
|
||||
// Merge two bookings together
|
||||
const attendeesToMove = [],
|
||||
attendeesToDelete = [];
|
||||
|
||||
// Merge two bookings together
|
||||
const attendeesToMove = [],
|
||||
attendeesToDelete = [];
|
||||
|
||||
for (const attendee of booking.attendees) {
|
||||
// If the attendee already exists on the new booking then delete the attendee record of the old booking
|
||||
if (
|
||||
newTimeSlotBooking.attendees.some(
|
||||
(newBookingAttendee) => newBookingAttendee.email === attendee.email
|
||||
)
|
||||
) {
|
||||
attendeesToDelete.push(attendee.id);
|
||||
// If the attendee does not exist on the new booking then move that attendee record to the new booking
|
||||
} else {
|
||||
attendeesToMove.push({ id: attendee.id, seatReferenceId: attendee.bookingSeat?.id });
|
||||
for (const attendee of booking.attendees) {
|
||||
// If the attendee already exists on the new booking then delete the attendee record of the old booking
|
||||
if (
|
||||
newTimeSlotBooking.attendees.some(
|
||||
(newBookingAttendee) => newBookingAttendee.email === attendee.email
|
||||
)
|
||||
) {
|
||||
attendeesToDelete.push(attendee.id);
|
||||
// If the attendee does not exist on the new booking then move that attendee record to the new booking
|
||||
} else {
|
||||
attendeesToMove.push({ id: attendee.id, seatReferenceId: attendee.bookingSeat?.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm that the new event will have enough available seats
|
||||
if (
|
||||
!eventType.seatsPerTimeSlot ||
|
||||
attendeesToMove.length +
|
||||
newTimeSlotBooking.attendees.filter((attendee) => attendee.bookingSeat).length >
|
||||
eventType.seatsPerTimeSlot
|
||||
) {
|
||||
throw new HttpError({ statusCode: 409, message: "Booking does not have enough available seats" });
|
||||
}
|
||||
// Confirm that the new event will have enough available seats
|
||||
if (
|
||||
!eventType.seatsPerTimeSlot ||
|
||||
attendeesToMove.length +
|
||||
newTimeSlotBooking.attendees.filter((attendee) => attendee.bookingSeat).length >
|
||||
eventType.seatsPerTimeSlot
|
||||
) {
|
||||
throw new HttpError({ statusCode: 409, message: "Booking does not have enough available seats" });
|
||||
}
|
||||
|
||||
const moveAttendeeCalls = [];
|
||||
for (const attendeeToMove of attendeesToMove) {
|
||||
moveAttendeeCalls.push(
|
||||
prisma.attendee.update({
|
||||
where: {
|
||||
id: attendeeToMove.id,
|
||||
},
|
||||
data: {
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
bookingSeat: {
|
||||
upsert: {
|
||||
create: {
|
||||
referenceUid: uuid(),
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
},
|
||||
update: {
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
const moveAttendeeCalls = [];
|
||||
for (const attendeeToMove of attendeesToMove) {
|
||||
moveAttendeeCalls.push(
|
||||
prisma.attendee.update({
|
||||
where: {
|
||||
id: attendeeToMove.id,
|
||||
},
|
||||
data: {
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
bookingSeat: {
|
||||
upsert: {
|
||||
create: {
|
||||
referenceUid: uuid(),
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
},
|
||||
update: {
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
...moveAttendeeCalls,
|
||||
// Delete any attendees that are already a part of that new time slot booking
|
||||
prisma.attendee.deleteMany({
|
||||
await Promise.all([
|
||||
...moveAttendeeCalls,
|
||||
// Delete any attendees that are already a part of that new time slot booking
|
||||
prisma.attendee.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: attendeesToDelete,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const updatedNewBooking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: {
|
||||
in: attendeesToDelete,
|
||||
},
|
||||
id: newTimeSlotBooking.id,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
include: {
|
||||
attendees: true,
|
||||
references: true,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedNewBooking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: newTimeSlotBooking.id,
|
||||
},
|
||||
include: {
|
||||
attendees: true,
|
||||
references: true,
|
||||
},
|
||||
});
|
||||
if (!updatedNewBooking) {
|
||||
throw new HttpError({ statusCode: 404, message: "Updated booking not found" });
|
||||
}
|
||||
|
||||
if (!updatedNewBooking) {
|
||||
throw new HttpError({ statusCode: 404, message: "Updated booking not found" });
|
||||
// Update the evt object with the new attendees
|
||||
const updatedBookingAttendees = updatedNewBooking.attendees.map((attendee) => {
|
||||
const evtAttendee = {
|
||||
...attendee,
|
||||
language: { translate: tAttendees, locale: language ?? "en" },
|
||||
};
|
||||
return evtAttendee;
|
||||
});
|
||||
|
||||
evt.attendees = updatedBookingAttendees;
|
||||
|
||||
addVideoCallDataToEvt(updatedNewBooking.references);
|
||||
|
||||
const copyEvent = cloneDeep(evt);
|
||||
|
||||
const updateManager = await eventManager.reschedule(
|
||||
copyEvent,
|
||||
rescheduleUid,
|
||||
newTimeSlotBooking.id
|
||||
);
|
||||
|
||||
const results = updateManager.results;
|
||||
|
||||
const calendarResult = results.find((result) => result.type.includes("_calendar"));
|
||||
|
||||
evt.iCalUID = Array.isArray(calendarResult?.updatedEvent)
|
||||
? calendarResult?.updatedEvent[0]?.iCalUID
|
||||
: calendarResult?.updatedEvent?.iCalUID || undefined;
|
||||
|
||||
// TODO send reschedule emails to attendees of the old booking
|
||||
await sendRescheduledEmails({
|
||||
...copyEvent,
|
||||
additionalNotes, // Resets back to the additionalNote input and not the override value
|
||||
cancellationReason: "$RCH$" + (rescheduleReason ? rescheduleReason : ""), // Removable code prefix to differentiate cancellation from rescheduling for email
|
||||
});
|
||||
|
||||
// Update the old booking with the cancelled status
|
||||
await prisma.booking.update({
|
||||
where: {
|
||||
id: booking.id,
|
||||
},
|
||||
data: {
|
||||
status: BookingStatus.CANCELLED,
|
||||
},
|
||||
});
|
||||
|
||||
const foundBooking = await findBookingQuery(newTimeSlotBooking.id);
|
||||
|
||||
resultBooking = { ...foundBooking };
|
||||
}
|
||||
|
||||
// Update the evt object with the new attendees
|
||||
const updatedBookingAttendees = updatedNewBooking.attendees.map((attendee) => {
|
||||
const evtAttendee = { ...attendee, language: { translate: tAttendees, locale: language ?? "en" } };
|
||||
return evtAttendee;
|
||||
});
|
||||
|
||||
evt.attendees = updatedBookingAttendees;
|
||||
|
||||
addVideoCallDataToEvt(updatedNewBooking.references);
|
||||
|
||||
const copyEvent = cloneDeep(evt);
|
||||
|
||||
const updateManager = await eventManager.reschedule(copyEvent, rescheduleUid, newTimeSlotBooking.id);
|
||||
|
||||
const results = updateManager.results;
|
||||
|
||||
const calendarResult = results.find((result) => result.type.includes("_calendar"));
|
||||
|
||||
evt.iCalUID = Array.isArray(calendarResult?.updatedEvent)
|
||||
? calendarResult?.updatedEvent[0]?.iCalUID
|
||||
: calendarResult?.updatedEvent?.iCalUID || undefined;
|
||||
|
||||
// TODO send reschedule emails to attendees of the old booking
|
||||
await sendRescheduledEmails({
|
||||
...copyEvent,
|
||||
additionalNotes, // Resets back to the additionalNote input and not the override value
|
||||
cancellationReason: "$RCH$" + (rescheduleReason ? rescheduleReason : ""), // Removable code prefix to differentiate cancellation from rescheduling for email
|
||||
});
|
||||
|
||||
// Update the old booking with the cancelled status
|
||||
await prisma.booking.update({
|
||||
where: {
|
||||
id: booking.id,
|
||||
},
|
||||
data: {
|
||||
status: BookingStatus.CANCELLED,
|
||||
},
|
||||
});
|
||||
|
||||
const resultBooking = await resultBookingQuery(newTimeSlotBooking.id);
|
||||
|
||||
return { ...resultBooking };
|
||||
}
|
||||
|
||||
// seatAttendee is null when the organizer is rescheduling.
|
||||
const seatAttendee: Partial<Person> | null = bookingSeat?.attendee || null;
|
||||
|
||||
seatAttendee.language = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" };
|
||||
|
||||
if (seatAttendee) {
|
||||
seatAttendee["language"] = { translate: tAttendees, locale: bookingSeat?.attendee.locale ?? "en" };
|
||||
}
|
||||
// If there is no booking then remove the attendee from the old booking and create a new one
|
||||
if (!newTimeSlotBooking) {
|
||||
await prisma.attendee.delete({
|
||||
where: {
|
||||
id: seatAttendee.id,
|
||||
id: seatAttendee?.id,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1259,24 +1266,26 @@ async function handler(
|
|||
|
||||
// Need to change the new seat reference and attendee record to remove it from the old booking and add it to the new booking
|
||||
// https://stackoverflow.com/questions/4980963/database-insert-new-rows-or-update-existing-ones
|
||||
await Promise.all([
|
||||
await prisma.attendee.update({
|
||||
where: {
|
||||
id: seatAttendee.id,
|
||||
},
|
||||
data: {
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
},
|
||||
}),
|
||||
await prisma.bookingSeat.update({
|
||||
where: {
|
||||
id: bookingSeat.id,
|
||||
},
|
||||
data: {
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
if (seatAttendee?.id && bookingSeat?.id) {
|
||||
await Promise.all([
|
||||
await prisma.attendee.update({
|
||||
where: {
|
||||
id: seatAttendee.id,
|
||||
},
|
||||
data: {
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
},
|
||||
}),
|
||||
await prisma.bookingSeat.update({
|
||||
where: {
|
||||
id: bookingSeat.id,
|
||||
},
|
||||
data: {
|
||||
bookingId: newTimeSlotBooking.id,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
const copyEvent = cloneDeep(evt);
|
||||
|
||||
|
@ -1296,9 +1305,9 @@ async function handler(
|
|||
});
|
||||
await lastAttendeeDeleteBooking(originalRescheduledBooking, filteredAttendees, originalBookingEvt);
|
||||
|
||||
const resultBooking = await resultBookingQuery(newTimeSlotBooking.id);
|
||||
const foundBooking = await findBookingQuery(newTimeSlotBooking.id);
|
||||
|
||||
return { ...resultBooking, seatReferenceUid: bookingSeat.referenceUid };
|
||||
resultBooking = { ...foundBooking, seatReferenceUid: bookingSeat?.referenceUid };
|
||||
} else {
|
||||
// Need to add translation for attendees to pass type checks. Since these values are never written to the db we can just use the new attendee language
|
||||
const bookingAttendees = booking.attendees.map((attendee) => {
|
||||
|
@ -1324,7 +1333,7 @@ async function handler(
|
|||
|
||||
const attendeeUniqueId = uuid();
|
||||
|
||||
const bookingUpdated = await prisma.booking.update({
|
||||
await prisma.booking.update({
|
||||
where: {
|
||||
uid: reqBody.bookingUid,
|
||||
},
|
||||
|
@ -1373,7 +1382,7 @@ async function handler(
|
|||
const eventManager = new EventManager({ ...organizerUser, credentials });
|
||||
await eventManager.updateCalendarAttendees(evt, booking);
|
||||
|
||||
const resultBooking = await resultBookingQuery(booking.id);
|
||||
const foundBooking = await findBookingQuery(booking.id);
|
||||
|
||||
if (!Number.isNaN(paymentAppData.price) && paymentAppData.price > 0 && !!booking) {
|
||||
const credentialPaymentAppCategories = await prisma.credential.findMany({
|
||||
|
@ -1416,16 +1425,33 @@ async function handler(
|
|||
bookerEmail
|
||||
);
|
||||
|
||||
return {
|
||||
...resultBooking,
|
||||
message: "Payment required",
|
||||
paymentUid: payment?.uid,
|
||||
seatReferenceUid: bookingSeat?.referenceUid,
|
||||
};
|
||||
resultBooking = { ...foundBooking };
|
||||
resultBooking["message"] = "Payment required";
|
||||
resultBooking["paymentUid"] = payment?.uid;
|
||||
}
|
||||
|
||||
return { ...resultBooking, seatReferenceUid: evt.attendeeSeatId };
|
||||
resultBooking = { ...foundBooking, seatReferenceUid: evt.attendeeSeatId };
|
||||
}
|
||||
|
||||
// Here we should handle every after action that needs to be done after booking creation
|
||||
|
||||
// Obtain event metadata that includes videoCallUrl
|
||||
const metadata = { videoCallUrl: evt.videoCallData?.url };
|
||||
try {
|
||||
await scheduleWorkflowReminders({
|
||||
workflows: eventType.workflows,
|
||||
smsReminderNumber: smsReminderNumber || null,
|
||||
calendarEvent: { ...evt, responses, ...{ metadata } },
|
||||
requiresConfirmation: evt.requiresConfirmation || false,
|
||||
isRescheduleEvent: !!rescheduleUid,
|
||||
isFirstRecurringEvent: true,
|
||||
emailAttendeeSendToOverride: bookerEmail,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("Error while scheduling workflow reminders", error);
|
||||
}
|
||||
|
||||
return resultBooking;
|
||||
};
|
||||
// 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)) {
|
||||
|
@ -1779,7 +1805,6 @@ async function handler(
|
|||
|
||||
results = createManager.results;
|
||||
referencesToCreate = createManager.referencesToCreate;
|
||||
|
||||
videoCallUrl = evt.videoCallData && evt.videoCallData.url ? evt.videoCallData.url : null;
|
||||
|
||||
if (results.length > 0 && results.every((res) => !res.success)) {
|
||||
|
@ -1907,7 +1932,11 @@ async function handler(
|
|||
videoCallUrl = booking.location;
|
||||
}
|
||||
|
||||
const metadata = videoCallUrl ? { videoCallUrl: getVideoCallUrl(evt) || videoCallUrl } : undefined;
|
||||
const metadata = videoCallUrl
|
||||
? {
|
||||
videoCallUrl: getVideoCallUrlFromCalEvent(evt),
|
||||
}
|
||||
: undefined;
|
||||
if (isConfirmedByDefault) {
|
||||
const eventTrigger: WebhookTriggerEvents = rescheduleUid
|
||||
? WebhookTriggerEvents.BOOKING_RESCHEDULED
|
||||
|
@ -2023,15 +2052,17 @@ async function handler(
|
|||
log.error("Error while creating booking references", error);
|
||||
}
|
||||
|
||||
const metadataFromEvent = { videoCallUrl };
|
||||
|
||||
try {
|
||||
await scheduleWorkflowReminders(
|
||||
eventType.workflows,
|
||||
smsReminderNumber || null,
|
||||
{ ...evt, responses, ...{ metadata } },
|
||||
evt.requiresConfirmation || false,
|
||||
rescheduleUid ? true : false,
|
||||
true
|
||||
);
|
||||
await scheduleWorkflowReminders({
|
||||
workflows: eventType.workflows,
|
||||
smsReminderNumber: smsReminderNumber || null,
|
||||
calendarEvent: { ...evt, responses, ...{ metadata: metadataFromEvent } },
|
||||
requiresConfirmation: evt.requiresConfirmation || false,
|
||||
isRescheduleEvent: !!rescheduleUid,
|
||||
isFirstRecurringEvent: true,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("Error while scheduling workflow reminders", error);
|
||||
}
|
||||
|
@ -2082,7 +2113,7 @@ function handleCustomInputs(
|
|||
});
|
||||
}
|
||||
|
||||
const resultBookingQuery = async (bookingId: number) => {
|
||||
const findBookingQuery = async (bookingId: number) => {
|
||||
const foundBooking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
id: bookingId,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { TimeUnit } from "@prisma/client";
|
||||
import { WorkflowTriggerEvents, WorkflowTemplates, WorkflowActions, WorkflowMethods } from "@prisma/client";
|
||||
import client from "@sendgrid/client";
|
||||
import type { MailData } from "@sendgrid/helpers/classes/mail";
|
||||
import sgMail from "@sendgrid/mail";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
|
@ -30,7 +31,7 @@ export const scheduleEmailReminder = async (
|
|||
time: number | null;
|
||||
timeUnit: TimeUnit | null;
|
||||
},
|
||||
sendTo: string,
|
||||
sendTo: MailData["to"],
|
||||
emailSubject: string,
|
||||
emailBody: string,
|
||||
workflowStepId: number,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client";
|
||||
import { WorkflowActions, WorkflowTriggerEvents } from "@prisma/client";
|
||||
import type { MailData } from "@sendgrid/helpers/classes/mail";
|
||||
|
||||
import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
@ -7,21 +8,33 @@ import type { CalendarEvent } from "@calcom/types/Calendar";
|
|||
import { scheduleEmailReminder } from "./emailReminderManager";
|
||||
import { scheduleSMSReminder } from "./smsReminderManager";
|
||||
|
||||
export const scheduleWorkflowReminders = async (
|
||||
export interface ScheduleWorkflowRemindersArgs {
|
||||
workflows: (WorkflowsOnEventTypes & {
|
||||
workflow: Workflow & {
|
||||
steps: WorkflowStep[];
|
||||
};
|
||||
})[],
|
||||
smsReminderNumber: string | null,
|
||||
evt: CalendarEvent & { metadata?: { videoCallUrl: string } },
|
||||
needsConfirmation: boolean,
|
||||
isRescheduleEvent: boolean,
|
||||
isFirstRecurringEvent: boolean
|
||||
) => {
|
||||
if (workflows.length > 0 && !needsConfirmation) {
|
||||
workflows.forEach((workflowReference) => {
|
||||
if (workflowReference.workflow.steps.length === 0) return;
|
||||
})[];
|
||||
smsReminderNumber: string | null;
|
||||
calendarEvent: CalendarEvent & { metadata?: { videoCallUrl: string | undefined } };
|
||||
requiresConfirmation?: boolean;
|
||||
isRescheduleEvent?: boolean;
|
||||
isFirstRecurringEvent?: boolean;
|
||||
emailAttendeeSendToOverride?: string;
|
||||
}
|
||||
|
||||
export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersArgs) => {
|
||||
const {
|
||||
workflows,
|
||||
smsReminderNumber,
|
||||
calendarEvent: evt,
|
||||
requiresConfirmation = false,
|
||||
isRescheduleEvent = false,
|
||||
isFirstRecurringEvent = false,
|
||||
emailAttendeeSendToOverride = "",
|
||||
} = args;
|
||||
if (workflows.length > 0 && !requiresConfirmation) {
|
||||
for (const workflowReference of workflows) {
|
||||
if (workflowReference.workflow.steps.length === 0) continue;
|
||||
|
||||
const workflow = workflowReference.workflow;
|
||||
if (
|
||||
|
@ -32,7 +45,7 @@ export const scheduleWorkflowReminders = async (
|
|||
(workflow.trigger === WorkflowTriggerEvents.RESCHEDULE_EVENT && isRescheduleEvent) ||
|
||||
workflow.trigger === WorkflowTriggerEvents.AFTER_EVENT
|
||||
) {
|
||||
workflow.steps.forEach(async (step) => {
|
||||
for (const step of workflow.steps) {
|
||||
if (step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.SMS_NUMBER) {
|
||||
const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo;
|
||||
await scheduleSMSReminder(
|
||||
|
@ -60,14 +73,16 @@ export const scheduleWorkflowReminders = async (
|
|||
|
||||
switch (step.action) {
|
||||
case WorkflowActions.EMAIL_HOST:
|
||||
sendTo = evt.organizer.email;
|
||||
sendTo = evt.organizer?.email || "";
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ATTENDEE:
|
||||
sendTo = evt.attendees[0].email;
|
||||
sendTo = !!emailAttendeeSendToOverride
|
||||
? emailAttendeeSendToOverride
|
||||
: evt.attendees?.[0]?.email || "";
|
||||
break;
|
||||
}
|
||||
|
||||
scheduleEmailReminder(
|
||||
await scheduleEmailReminder(
|
||||
evt,
|
||||
workflow.trigger,
|
||||
step.action,
|
||||
|
@ -83,27 +98,31 @@ export const scheduleWorkflowReminders = async (
|
|||
step.sender || SENDER_NAME
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const sendCancelledReminders = async (
|
||||
export interface SendCancelledRemindersArgs {
|
||||
workflows: (WorkflowsOnEventTypes & {
|
||||
workflow: Workflow & {
|
||||
steps: WorkflowStep[];
|
||||
};
|
||||
})[],
|
||||
smsReminderNumber: string | null,
|
||||
evt: CalendarEvent
|
||||
) => {
|
||||
})[];
|
||||
smsReminderNumber: string | null;
|
||||
evt: CalendarEvent;
|
||||
}
|
||||
|
||||
export const sendCancelledReminders = async (args: SendCancelledRemindersArgs) => {
|
||||
const { workflows, smsReminderNumber, evt } = args;
|
||||
|
||||
if (workflows.length > 0) {
|
||||
workflows
|
||||
.filter((workflowRef) => workflowRef.workflow.trigger === WorkflowTriggerEvents.EVENT_CANCELLED)
|
||||
.forEach((workflowRef) => {
|
||||
const workflow = workflowRef.workflow;
|
||||
workflow.steps.forEach(async (step) => {
|
||||
for (const workflowRef of workflows) {
|
||||
const { workflow } = workflowRef;
|
||||
|
||||
if (workflow.trigger === WorkflowTriggerEvents.EVENT_CANCELLED) {
|
||||
for (const step of workflow.steps) {
|
||||
if (step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.SMS_NUMBER) {
|
||||
const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo;
|
||||
await scheduleSMSReminder(
|
||||
|
@ -127,17 +146,18 @@ export const sendCancelledReminders = async (
|
|||
step.action === WorkflowActions.EMAIL_ATTENDEE ||
|
||||
step.action === WorkflowActions.EMAIL_HOST
|
||||
) {
|
||||
let sendTo = "";
|
||||
let sendTo: MailData["to"] = "";
|
||||
|
||||
switch (step.action) {
|
||||
case WorkflowActions.EMAIL_HOST:
|
||||
sendTo = evt.organizer.email;
|
||||
break;
|
||||
case WorkflowActions.EMAIL_ATTENDEE:
|
||||
sendTo = evt.attendees[0].email;
|
||||
sendTo = evt.attendees.map((a) => a.email);
|
||||
break;
|
||||
}
|
||||
scheduleEmailReminder(
|
||||
|
||||
await scheduleEmailReminder(
|
||||
evt,
|
||||
workflow.trigger,
|
||||
step.action,
|
||||
|
@ -153,7 +173,8 @@ export const sendCancelledReminders = async (
|
|||
step.sender || SENDER_NAME
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -120,7 +120,7 @@ export const getDescription = (calEvent: CalendarEvent) => {
|
|||
`;
|
||||
};
|
||||
export const getLocation = (calEvent: CalendarEvent) => {
|
||||
const meetingUrl = getVideoCallUrl(calEvent);
|
||||
const meetingUrl = getVideoCallUrlFromCalEvent(calEvent);
|
||||
if (meetingUrl) {
|
||||
return meetingUrl;
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ export const getPublicVideoCallUrl = (calEvent: CalendarEvent): string => {
|
|||
return WEBAPP_URL + "/video/" + getUid(calEvent);
|
||||
};
|
||||
|
||||
export const getVideoCallUrl = (calEvent: CalendarEvent): string => {
|
||||
export const getVideoCallUrlFromCalEvent = (calEvent: CalendarEvent): string => {
|
||||
if (calEvent.videoCallData) {
|
||||
if (isDailyVideoCall(calEvent)) {
|
||||
return getPublicVideoCallUrl(calEvent);
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { faker } from "@faker-js/faker";
|
||||
|
||||
import { getLocation, getPublicVideoCallUrl, getVideoCallPassword, getVideoCallUrl } from "../CalEventParser";
|
||||
import {
|
||||
getLocation,
|
||||
getPublicVideoCallUrl,
|
||||
getVideoCallPassword,
|
||||
getVideoCallUrlFromCalEvent,
|
||||
} from "../CalEventParser";
|
||||
import { buildCalendarEvent, buildVideoCallData } from "./builder";
|
||||
|
||||
jest.mock("@calcom/lib/constants", () => ({
|
||||
|
@ -20,7 +25,7 @@ describe("getLocation", () => {
|
|||
}),
|
||||
});
|
||||
|
||||
expect(getLocation(calEvent)).toEqual(getVideoCallUrl(calEvent));
|
||||
expect(getLocation(calEvent)).toEqual(getVideoCallUrlFromCalEvent(calEvent));
|
||||
});
|
||||
it("should return an integration provider name from event", () => {
|
||||
const provideName = "Cal.com";
|
||||
|
@ -49,7 +54,7 @@ describe("getVideoCallUrl", () => {
|
|||
}),
|
||||
});
|
||||
|
||||
expect(getVideoCallUrl(calEvent)).toEqual(getPublicVideoCallUrl(calEvent));
|
||||
expect(getVideoCallUrlFromCalEvent(calEvent)).toEqual(getPublicVideoCallUrl(calEvent));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user