2023-04-25 22:39:47 +00:00
|
|
|
import type { Prisma } from "@prisma/client";
|
|
|
|
|
2023-08-10 18:52:36 +00:00
|
|
|
import {
|
|
|
|
isSMSOrWhatsappAction,
|
|
|
|
isTextMessageToAttendeeAction,
|
|
|
|
} from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
|
2023-04-25 22:39:47 +00:00
|
|
|
import {
|
|
|
|
deleteScheduledEmailReminder,
|
|
|
|
scheduleEmailReminder,
|
|
|
|
} from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager";
|
|
|
|
import {
|
|
|
|
deleteScheduledSMSReminder,
|
|
|
|
scheduleSMSReminder,
|
|
|
|
} from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
|
2023-07-11 15:48:44 +00:00
|
|
|
import {
|
|
|
|
deleteScheduledWhatsappReminder,
|
|
|
|
scheduleWhatsappReminder,
|
|
|
|
} from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager";
|
2023-05-09 07:50:17 +00:00
|
|
|
import { IS_SELF_HOSTED, SENDER_ID, SENDER_NAME } from "@calcom/lib/constants";
|
2023-05-19 08:10:11 +00:00
|
|
|
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
2023-07-19 14:30:37 +00:00
|
|
|
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
2023-04-25 22:39:47 +00:00
|
|
|
import type { PrismaClient } from "@calcom/prisma/client";
|
2023-05-02 11:44:05 +00:00
|
|
|
import { BookingStatus, WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums";
|
2023-04-25 22:39:47 +00:00
|
|
|
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
|
|
|
|
|
|
|
import { TRPCError } from "@trpc/server";
|
|
|
|
|
2023-08-10 18:52:36 +00:00
|
|
|
import { isVerifiedHandler } from "../kycVerification/isVerified.handler";
|
2023-05-19 08:10:11 +00:00
|
|
|
import { hasTeamPlanHandler } from "../teams/hasTeamPlan.handler";
|
2023-04-25 22:39:47 +00:00
|
|
|
import type { TUpdateInputSchema } from "./update.schema";
|
|
|
|
import {
|
|
|
|
getSender,
|
|
|
|
isAuthorized,
|
|
|
|
removeSmsReminderFieldForBooking,
|
|
|
|
upsertSmsReminderFieldForBooking,
|
|
|
|
} from "./util";
|
|
|
|
|
|
|
|
type UpdateOptions = {
|
|
|
|
ctx: {
|
|
|
|
user: NonNullable<TrpcSessionUser>;
|
|
|
|
prisma: PrismaClient;
|
|
|
|
};
|
|
|
|
input: TUpdateInputSchema;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
|
|
|
const { user } = ctx;
|
|
|
|
const { id, name, activeOn, steps, trigger, time, timeUnit } = input;
|
|
|
|
|
|
|
|
const userWorkflow = await ctx.prisma.workflow.findUnique({
|
|
|
|
where: {
|
|
|
|
id,
|
|
|
|
},
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
userId: true,
|
|
|
|
teamId: true,
|
|
|
|
user: {
|
|
|
|
select: {
|
|
|
|
teams: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
steps: true,
|
|
|
|
activeOn: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const isUserAuthorized = await isAuthorized(userWorkflow, ctx.prisma, ctx.user.id, true);
|
|
|
|
|
|
|
|
if (!isUserAuthorized || !userWorkflow) {
|
|
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (steps.find((step) => step.workflowId != id)) {
|
|
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
|
|
}
|
|
|
|
|
2023-05-24 18:24:12 +00:00
|
|
|
const isCurrentUsernamePremium = hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false;
|
2023-05-19 08:10:11 +00:00
|
|
|
|
|
|
|
let isTeamsPlan = false;
|
|
|
|
if (!isCurrentUsernamePremium) {
|
|
|
|
const { hasTeamPlan } = await hasTeamPlanHandler({ ctx });
|
|
|
|
isTeamsPlan = !!hasTeamPlan;
|
|
|
|
}
|
|
|
|
const hasPaidPlan = IS_SELF_HOSTED || isCurrentUsernamePremium || isTeamsPlan;
|
|
|
|
|
2023-08-10 18:52:36 +00:00
|
|
|
const kycVerified = await isVerifiedHandler({ ctx });
|
|
|
|
const isKYCVerified = kycVerified.isKYCVerified;
|
|
|
|
|
2023-05-15 13:56:26 +00:00
|
|
|
const activeOnEventTypes = await ctx.prisma.eventType.findMany({
|
|
|
|
where: {
|
|
|
|
id: {
|
|
|
|
in: activeOn,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
children: {
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const activeOnWithChildren = activeOnEventTypes
|
|
|
|
.map((eventType) => [eventType.id].concat(eventType.children.map((child) => child.id)))
|
|
|
|
.flat();
|
|
|
|
|
2023-04-25 22:39:47 +00:00
|
|
|
const oldActiveOnEventTypes = await ctx.prisma.workflowsOnEventTypes.findMany({
|
|
|
|
where: {
|
|
|
|
workflowId: id,
|
|
|
|
},
|
|
|
|
select: {
|
|
|
|
eventTypeId: true,
|
2023-05-15 13:56:26 +00:00
|
|
|
eventType: {
|
|
|
|
include: {
|
|
|
|
children: true,
|
|
|
|
},
|
|
|
|
},
|
2023-04-25 22:39:47 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-05-15 13:56:26 +00:00
|
|
|
const oldActiveOnEventTypeIds = oldActiveOnEventTypes
|
|
|
|
.map((eventTypeRel) =>
|
|
|
|
[eventTypeRel.eventType.id].concat(eventTypeRel.eventType.children.map((child) => child.id))
|
|
|
|
)
|
|
|
|
.flat();
|
|
|
|
|
2023-04-25 22:39:47 +00:00
|
|
|
const newActiveEventTypes = activeOn.filter((eventType) => {
|
|
|
|
if (
|
|
|
|
!oldActiveOnEventTypes ||
|
|
|
|
!oldActiveOnEventTypes
|
|
|
|
.map((oldEventType) => {
|
|
|
|
return oldEventType.eventTypeId;
|
|
|
|
})
|
|
|
|
.includes(eventType)
|
|
|
|
) {
|
|
|
|
return eventType;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
//check if new event types belong to user or team
|
|
|
|
for (const newEventTypeId of newActiveEventTypes) {
|
|
|
|
const newEventType = await ctx.prisma.eventType.findFirst({
|
|
|
|
where: {
|
|
|
|
id: newEventTypeId,
|
|
|
|
},
|
|
|
|
include: {
|
|
|
|
users: true,
|
|
|
|
team: {
|
|
|
|
include: {
|
|
|
|
members: true,
|
|
|
|
},
|
|
|
|
},
|
2023-05-15 13:56:26 +00:00
|
|
|
children: true,
|
2023-04-25 22:39:47 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (newEventType) {
|
|
|
|
if (userWorkflow.teamId && userWorkflow.teamId !== newEventType.teamId) {
|
|
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!userWorkflow.teamId &&
|
|
|
|
userWorkflow.userId &&
|
|
|
|
newEventType.userId !== userWorkflow.userId &&
|
|
|
|
!newEventType?.users.find((eventTypeUser) => eventTypeUser.id === userWorkflow.userId)
|
|
|
|
) {
|
|
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//remove all scheduled Email and SMS reminders for eventTypes that are not active any more
|
2023-05-15 13:56:26 +00:00
|
|
|
const removedEventTypes = oldActiveOnEventTypeIds.filter((eventTypeId) => {
|
|
|
|
if (!activeOnWithChildren.includes(eventTypeId)) {
|
|
|
|
return eventTypeId;
|
|
|
|
}
|
|
|
|
});
|
2023-04-25 22:39:47 +00:00
|
|
|
|
|
|
|
const remindersToDeletePromise: Prisma.PrismaPromise<
|
|
|
|
{
|
|
|
|
id: number;
|
|
|
|
referenceId: string | null;
|
|
|
|
method: string;
|
|
|
|
scheduled: boolean;
|
|
|
|
}[]
|
|
|
|
>[] = [];
|
|
|
|
|
|
|
|
removedEventTypes.forEach((eventTypeId) => {
|
|
|
|
const reminderToDelete = ctx.prisma.workflowReminder.findMany({
|
|
|
|
where: {
|
|
|
|
booking: {
|
|
|
|
eventTypeId: eventTypeId,
|
|
|
|
userId: ctx.user.id,
|
|
|
|
},
|
|
|
|
workflowStepId: {
|
|
|
|
in: userWorkflow.steps.map((step) => {
|
|
|
|
return step.id;
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
referenceId: true,
|
|
|
|
method: true,
|
|
|
|
scheduled: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
remindersToDeletePromise.push(reminderToDelete);
|
|
|
|
});
|
|
|
|
|
|
|
|
const remindersToDelete = await Promise.all(remindersToDeletePromise);
|
|
|
|
|
|
|
|
//cancel workflow reminders for all bookings from event types that got disabled
|
|
|
|
remindersToDelete.flat().forEach((reminder) => {
|
|
|
|
if (reminder.method === WorkflowMethods.EMAIL) {
|
|
|
|
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
|
|
|
|
} else if (reminder.method === WorkflowMethods.SMS) {
|
|
|
|
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
|
2023-07-11 15:48:44 +00:00
|
|
|
} else if (reminder.method === WorkflowMethods.WHATSAPP) {
|
|
|
|
deleteScheduledWhatsappReminder(reminder.id, reminder.referenceId);
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
//update active on & reminders for new eventTypes
|
|
|
|
await ctx.prisma.workflowsOnEventTypes.deleteMany({
|
|
|
|
where: {
|
|
|
|
workflowId: id,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
let newEventTypes: number[] = [];
|
|
|
|
if (activeOn.length) {
|
|
|
|
if (trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT) {
|
|
|
|
newEventTypes = newActiveEventTypes;
|
|
|
|
}
|
|
|
|
if (newEventTypes.length > 0) {
|
|
|
|
//create reminders for all bookings with newEventTypes
|
|
|
|
const bookingsForReminders = await ctx.prisma.booking.findMany({
|
|
|
|
where: {
|
2023-05-15 13:56:26 +00:00
|
|
|
OR: [
|
|
|
|
{ eventTypeId: { in: newEventTypes } },
|
|
|
|
{
|
|
|
|
eventType: {
|
|
|
|
parentId: {
|
|
|
|
in: newEventTypes,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
2023-04-25 22:39:47 +00:00
|
|
|
status: BookingStatus.ACCEPTED,
|
|
|
|
startTime: {
|
|
|
|
gte: new Date(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
include: {
|
|
|
|
attendees: true,
|
|
|
|
eventType: true,
|
|
|
|
user: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
steps.forEach(async (step) => {
|
2023-07-17 23:38:37 +00:00
|
|
|
if (
|
|
|
|
step.action !== WorkflowActions.SMS_ATTENDEE &&
|
|
|
|
step.action !== WorkflowActions.WHATSAPP_ATTENDEE
|
|
|
|
) {
|
2023-04-25 22:39:47 +00:00
|
|
|
//as we do not have attendees phone number (user is notified about that when setting this action)
|
|
|
|
bookingsForReminders.forEach(async (booking) => {
|
2023-07-17 23:38:37 +00:00
|
|
|
const defaultLocale = "en";
|
2023-04-25 22:39:47 +00:00
|
|
|
const bookingInfo = {
|
|
|
|
uid: booking.uid,
|
|
|
|
attendees: booking.attendees.map((attendee) => {
|
|
|
|
return {
|
|
|
|
name: attendee.name,
|
|
|
|
email: attendee.email,
|
|
|
|
timeZone: attendee.timeZone,
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: attendee.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
};
|
|
|
|
}),
|
|
|
|
organizer: booking.user
|
|
|
|
? {
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: booking.user.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
name: booking.user.name || "",
|
|
|
|
email: booking.user.email,
|
|
|
|
timeZone: booking.user.timeZone,
|
2023-07-19 14:30:37 +00:00
|
|
|
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
|
|
|
: { name: "", email: "", timeZone: "", language: { locale: "" } },
|
|
|
|
startTime: booking.startTime.toISOString(),
|
|
|
|
endTime: booking.endTime.toISOString(),
|
|
|
|
title: booking.title,
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: booking?.user?.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
eventType: {
|
|
|
|
slug: booking.eventType?.slug,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
if (
|
|
|
|
step.action === WorkflowActions.EMAIL_HOST ||
|
|
|
|
step.action === WorkflowActions.EMAIL_ATTENDEE /*||
|
|
|
|
step.action === WorkflowActions.EMAIL_ADDRESS*/
|
|
|
|
) {
|
2023-05-09 17:08:14 +00:00
|
|
|
let sendTo: string[] = [];
|
2023-04-25 22:39:47 +00:00
|
|
|
|
|
|
|
switch (step.action) {
|
|
|
|
case WorkflowActions.EMAIL_HOST:
|
2023-05-09 17:08:14 +00:00
|
|
|
sendTo = [bookingInfo.organizer?.email];
|
2023-04-25 22:39:47 +00:00
|
|
|
break;
|
|
|
|
case WorkflowActions.EMAIL_ATTENDEE:
|
2023-05-09 17:08:14 +00:00
|
|
|
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
|
2023-04-25 22:39:47 +00:00
|
|
|
break;
|
|
|
|
/*case WorkflowActions.EMAIL_ADDRESS:
|
|
|
|
sendTo = step.sendTo || "";*/
|
|
|
|
}
|
|
|
|
|
|
|
|
await scheduleEmailReminder(
|
|
|
|
bookingInfo,
|
|
|
|
trigger,
|
|
|
|
step.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
sendTo,
|
|
|
|
step.emailSubject || "",
|
|
|
|
step.reminderBody || "",
|
|
|
|
step.id,
|
|
|
|
step.template,
|
|
|
|
step.senderName || SENDER_NAME
|
|
|
|
);
|
|
|
|
} else if (step.action === WorkflowActions.SMS_NUMBER) {
|
|
|
|
await scheduleSMSReminder(
|
|
|
|
bookingInfo,
|
|
|
|
step.sendTo || "",
|
|
|
|
trigger,
|
|
|
|
step.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
step.reminderBody || "",
|
|
|
|
step.id,
|
|
|
|
step.template,
|
|
|
|
step.sender || SENDER_ID,
|
|
|
|
user.id,
|
|
|
|
userWorkflow.teamId
|
|
|
|
);
|
2023-07-11 15:48:44 +00:00
|
|
|
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER) {
|
|
|
|
await scheduleWhatsappReminder(
|
|
|
|
bookingInfo,
|
|
|
|
step.sendTo || "",
|
|
|
|
trigger,
|
|
|
|
step.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
step.reminderBody || "",
|
|
|
|
step.id || 0,
|
|
|
|
step.template,
|
|
|
|
user.id,
|
|
|
|
userWorkflow.teamId
|
|
|
|
);
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
//create all workflow - eventtypes relationships
|
2023-05-15 13:56:26 +00:00
|
|
|
activeOnEventTypes.forEach(async (eventType) => {
|
2023-04-25 22:39:47 +00:00
|
|
|
await ctx.prisma.workflowsOnEventTypes.createMany({
|
|
|
|
data: {
|
|
|
|
workflowId: id,
|
2023-05-15 13:56:26 +00:00
|
|
|
eventTypeId: eventType.id,
|
2023-04-25 22:39:47 +00:00
|
|
|
},
|
|
|
|
});
|
2023-05-15 13:56:26 +00:00
|
|
|
|
|
|
|
if (eventType.children.length) {
|
|
|
|
eventType.children.forEach(async (chEventType) => {
|
|
|
|
await ctx.prisma.workflowsOnEventTypes.createMany({
|
|
|
|
data: {
|
|
|
|
workflowId: id,
|
|
|
|
eventTypeId: chEventType.id,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2023-04-25 22:39:47 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
userWorkflow.steps.map(async (oldStep) => {
|
|
|
|
const newStep = steps.filter((s) => s.id === oldStep.id)[0];
|
|
|
|
const remindersFromStep = await ctx.prisma.workflowReminder.findMany({
|
|
|
|
where: {
|
|
|
|
workflowStepId: oldStep.id,
|
|
|
|
},
|
|
|
|
include: {
|
|
|
|
booking: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
//step was deleted
|
|
|
|
if (!newStep) {
|
|
|
|
// cancel all workflow reminders from deleted steps
|
|
|
|
if (remindersFromStep.length > 0) {
|
|
|
|
remindersFromStep.forEach((reminder) => {
|
|
|
|
if (reminder.method === WorkflowMethods.EMAIL) {
|
|
|
|
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
|
|
|
|
} else if (reminder.method === WorkflowMethods.SMS) {
|
|
|
|
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
|
2023-07-11 15:48:44 +00:00
|
|
|
} else if (reminder.method === WorkflowMethods.WHATSAPP) {
|
|
|
|
deleteScheduledWhatsappReminder(reminder.id, reminder.referenceId);
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
await ctx.prisma.workflowStep.delete({
|
|
|
|
where: {
|
|
|
|
id: oldStep.id,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
//step was edited
|
|
|
|
} else if (JSON.stringify(oldStep) !== JSON.stringify(newStep)) {
|
2023-07-11 15:48:44 +00:00
|
|
|
if (!hasPaidPlan && !isSMSOrWhatsappAction(oldStep.action) && isSMSOrWhatsappAction(newStep.action)) {
|
2023-04-25 22:39:47 +00:00
|
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
|
|
}
|
2023-08-10 18:52:36 +00:00
|
|
|
if (!isKYCVerified && isTextMessageToAttendeeAction(newStep.action)) {
|
|
|
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Account needs to be verified" });
|
|
|
|
}
|
2023-07-11 15:48:44 +00:00
|
|
|
const requiresSender =
|
|
|
|
newStep.action === WorkflowActions.SMS_NUMBER || newStep.action === WorkflowActions.WHATSAPP_NUMBER;
|
2023-04-25 22:39:47 +00:00
|
|
|
await ctx.prisma.workflowStep.update({
|
|
|
|
where: {
|
|
|
|
id: oldStep.id,
|
|
|
|
},
|
|
|
|
data: {
|
|
|
|
action: newStep.action,
|
2023-07-11 15:48:44 +00:00
|
|
|
sendTo: requiresSender /*||
|
2023-04-25 22:39:47 +00:00
|
|
|
newStep.action === WorkflowActions.EMAIL_ADDRESS*/
|
2023-07-11 15:48:44 +00:00
|
|
|
? newStep.sendTo
|
|
|
|
: null,
|
2023-04-25 22:39:47 +00:00
|
|
|
stepNumber: newStep.stepNumber,
|
|
|
|
workflowId: newStep.workflowId,
|
|
|
|
reminderBody: newStep.reminderBody,
|
|
|
|
emailSubject: newStep.emailSubject,
|
|
|
|
template: newStep.template,
|
|
|
|
numberRequired: newStep.numberRequired,
|
|
|
|
sender: getSender({
|
|
|
|
action: newStep.action,
|
|
|
|
sender: newStep.sender || null,
|
|
|
|
senderName: newStep.senderName,
|
|
|
|
}),
|
|
|
|
numberVerificationPending: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
//cancel all reminders of step and create new ones (not for newEventTypes)
|
|
|
|
const remindersToUpdate = remindersFromStep.filter((reminder) => {
|
|
|
|
if (reminder.booking?.eventTypeId && !newEventTypes.includes(reminder.booking?.eventTypeId)) {
|
|
|
|
return reminder;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
//cancel all workflow reminders from steps that were edited
|
|
|
|
remindersToUpdate.forEach(async (reminder) => {
|
|
|
|
if (reminder.method === WorkflowMethods.EMAIL) {
|
|
|
|
deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
|
|
|
|
} else if (reminder.method === WorkflowMethods.SMS) {
|
|
|
|
deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const eventTypesToUpdateReminders = activeOn.filter((eventTypeId) => {
|
|
|
|
if (!newEventTypes.includes(eventTypeId)) {
|
|
|
|
return eventTypeId;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (
|
|
|
|
eventTypesToUpdateReminders &&
|
|
|
|
(trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT)
|
|
|
|
) {
|
|
|
|
const bookingsOfEventTypes = await ctx.prisma.booking.findMany({
|
|
|
|
where: {
|
|
|
|
eventTypeId: {
|
|
|
|
in: eventTypesToUpdateReminders,
|
|
|
|
},
|
|
|
|
status: BookingStatus.ACCEPTED,
|
|
|
|
startTime: {
|
|
|
|
gte: new Date(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
include: {
|
|
|
|
attendees: true,
|
|
|
|
eventType: true,
|
|
|
|
user: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
bookingsOfEventTypes.forEach(async (booking) => {
|
2023-07-17 23:38:37 +00:00
|
|
|
const defaultLocale = "en";
|
2023-04-25 22:39:47 +00:00
|
|
|
const bookingInfo = {
|
|
|
|
uid: booking.uid,
|
|
|
|
attendees: booking.attendees.map((attendee) => {
|
|
|
|
return {
|
|
|
|
name: attendee.name,
|
|
|
|
email: attendee.email,
|
|
|
|
timeZone: attendee.timeZone,
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: attendee.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
};
|
|
|
|
}),
|
|
|
|
organizer: booking.user
|
|
|
|
? {
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: booking.user.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
name: booking.user.name || "",
|
|
|
|
email: booking.user.email,
|
|
|
|
timeZone: booking.user.timeZone,
|
2023-07-19 14:30:37 +00:00
|
|
|
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
|
|
|
: { name: "", email: "", timeZone: "", language: { locale: "" } },
|
|
|
|
startTime: booking.startTime.toISOString(),
|
|
|
|
endTime: booking.endTime.toISOString(),
|
|
|
|
title: booking.title,
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: booking?.user?.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
eventType: {
|
|
|
|
slug: booking.eventType?.slug,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
if (
|
|
|
|
newStep.action === WorkflowActions.EMAIL_HOST ||
|
|
|
|
newStep.action === WorkflowActions.EMAIL_ATTENDEE /*||
|
|
|
|
newStep.action === WorkflowActions.EMAIL_ADDRESS*/
|
|
|
|
) {
|
2023-05-09 17:08:14 +00:00
|
|
|
let sendTo: string[] = [];
|
2023-04-25 22:39:47 +00:00
|
|
|
|
|
|
|
switch (newStep.action) {
|
|
|
|
case WorkflowActions.EMAIL_HOST:
|
2023-05-09 17:08:14 +00:00
|
|
|
sendTo = [bookingInfo.organizer?.email];
|
2023-04-25 22:39:47 +00:00
|
|
|
break;
|
|
|
|
case WorkflowActions.EMAIL_ATTENDEE:
|
2023-05-09 17:08:14 +00:00
|
|
|
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
|
2023-04-25 22:39:47 +00:00
|
|
|
break;
|
|
|
|
/*case WorkflowActions.EMAIL_ADDRESS:
|
|
|
|
sendTo = newStep.sendTo || "";*/
|
|
|
|
}
|
|
|
|
|
|
|
|
await scheduleEmailReminder(
|
|
|
|
bookingInfo,
|
|
|
|
trigger,
|
|
|
|
newStep.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
sendTo,
|
|
|
|
newStep.emailSubject || "",
|
|
|
|
newStep.reminderBody || "",
|
|
|
|
newStep.id,
|
|
|
|
newStep.template,
|
|
|
|
newStep.senderName || SENDER_NAME
|
|
|
|
);
|
|
|
|
} else if (newStep.action === WorkflowActions.SMS_NUMBER) {
|
|
|
|
await scheduleSMSReminder(
|
|
|
|
bookingInfo,
|
|
|
|
newStep.sendTo || "",
|
|
|
|
trigger,
|
|
|
|
newStep.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
newStep.reminderBody || "",
|
|
|
|
newStep.id || 0,
|
|
|
|
newStep.template,
|
|
|
|
newStep.sender || SENDER_ID,
|
|
|
|
user.id,
|
|
|
|
userWorkflow.teamId
|
|
|
|
);
|
2023-07-11 15:48:44 +00:00
|
|
|
} else if (newStep.action === WorkflowActions.WHATSAPP_NUMBER) {
|
|
|
|
await scheduleWhatsappReminder(
|
|
|
|
bookingInfo,
|
|
|
|
newStep.sendTo || "",
|
|
|
|
trigger,
|
|
|
|
newStep.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
newStep.reminderBody || "",
|
|
|
|
newStep.id || 0,
|
|
|
|
newStep.template,
|
|
|
|
user.id,
|
|
|
|
userWorkflow.teamId
|
|
|
|
);
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
//added steps
|
|
|
|
const addedSteps = steps.map((s) => {
|
|
|
|
if (s.id <= 0) {
|
2023-07-11 15:48:44 +00:00
|
|
|
if (isSMSOrWhatsappAction(s.action) && !hasPaidPlan) {
|
2023-04-25 22:39:47 +00:00
|
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
|
|
}
|
2023-08-10 18:52:36 +00:00
|
|
|
if (isTextMessageToAttendeeAction(s.action)) {
|
|
|
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Account needs to be verified" });
|
|
|
|
}
|
2023-04-25 22:39:47 +00:00
|
|
|
const { id: _stepId, ...stepToAdd } = s;
|
|
|
|
return stepToAdd;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (addedSteps) {
|
|
|
|
const eventTypesToCreateReminders = activeOn.map((activeEventType) => {
|
|
|
|
if (activeEventType && !newEventTypes.includes(activeEventType)) {
|
|
|
|
return activeEventType;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
addedSteps.forEach(async (step) => {
|
|
|
|
if (step) {
|
|
|
|
const { senderName, ...newStep } = step;
|
|
|
|
newStep.sender = getSender({
|
|
|
|
action: newStep.action,
|
|
|
|
sender: newStep.sender || null,
|
|
|
|
senderName: senderName,
|
|
|
|
});
|
|
|
|
const createdStep = await ctx.prisma.workflowStep.create({
|
|
|
|
data: { ...newStep, numberVerificationPending: false },
|
|
|
|
});
|
|
|
|
if (
|
|
|
|
(trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT) &&
|
|
|
|
eventTypesToCreateReminders &&
|
2023-07-17 23:38:37 +00:00
|
|
|
step.action !== WorkflowActions.SMS_ATTENDEE &&
|
|
|
|
step.action !== WorkflowActions.WHATSAPP_ATTENDEE
|
2023-04-25 22:39:47 +00:00
|
|
|
) {
|
|
|
|
const bookingsForReminders = await ctx.prisma.booking.findMany({
|
|
|
|
where: {
|
|
|
|
eventTypeId: { in: eventTypesToCreateReminders as number[] },
|
|
|
|
status: BookingStatus.ACCEPTED,
|
|
|
|
startTime: {
|
|
|
|
gte: new Date(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
include: {
|
|
|
|
attendees: true,
|
|
|
|
eventType: true,
|
|
|
|
user: true,
|
|
|
|
},
|
|
|
|
});
|
2023-05-12 17:31:01 +00:00
|
|
|
for (const booking of bookingsForReminders) {
|
2023-07-17 23:38:37 +00:00
|
|
|
const defaultLocale = "en";
|
2023-04-25 22:39:47 +00:00
|
|
|
const bookingInfo = {
|
|
|
|
uid: booking.uid,
|
|
|
|
attendees: booking.attendees.map((attendee) => {
|
|
|
|
return {
|
|
|
|
name: attendee.name,
|
|
|
|
email: attendee.email,
|
|
|
|
timeZone: attendee.timeZone,
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: attendee.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
};
|
|
|
|
}),
|
|
|
|
organizer: booking.user
|
|
|
|
? {
|
|
|
|
name: booking.user.name || "",
|
|
|
|
email: booking.user.email,
|
|
|
|
timeZone: booking.user.timeZone,
|
2023-07-19 14:30:37 +00:00
|
|
|
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: booking.user.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
|
|
|
: { name: "", email: "", timeZone: "", language: { locale: "" } },
|
|
|
|
startTime: booking.startTime.toISOString(),
|
|
|
|
endTime: booking.endTime.toISOString(),
|
|
|
|
title: booking.title,
|
2023-07-17 23:38:37 +00:00
|
|
|
language: { locale: booking?.user?.locale || defaultLocale },
|
2023-04-25 22:39:47 +00:00
|
|
|
eventType: {
|
|
|
|
slug: booking.eventType?.slug,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
if (
|
|
|
|
step.action === WorkflowActions.EMAIL_ATTENDEE ||
|
|
|
|
step.action === WorkflowActions.EMAIL_HOST /*||
|
|
|
|
step.action === WorkflowActions.EMAIL_ADDRESS*/
|
|
|
|
) {
|
2023-05-09 17:08:14 +00:00
|
|
|
let sendTo: string[] = [];
|
2023-04-25 22:39:47 +00:00
|
|
|
|
|
|
|
switch (step.action) {
|
|
|
|
case WorkflowActions.EMAIL_HOST:
|
2023-05-09 17:08:14 +00:00
|
|
|
sendTo = [bookingInfo.organizer?.email];
|
2023-04-25 22:39:47 +00:00
|
|
|
break;
|
|
|
|
case WorkflowActions.EMAIL_ATTENDEE:
|
2023-05-09 17:08:14 +00:00
|
|
|
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
|
2023-04-25 22:39:47 +00:00
|
|
|
break;
|
|
|
|
/*case WorkflowActions.EMAIL_ADDRESS:
|
|
|
|
sendTo = step.sendTo || "";*/
|
|
|
|
}
|
|
|
|
|
|
|
|
await scheduleEmailReminder(
|
|
|
|
bookingInfo,
|
|
|
|
trigger,
|
|
|
|
step.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
sendTo,
|
|
|
|
step.emailSubject || "",
|
|
|
|
step.reminderBody || "",
|
|
|
|
createdStep.id,
|
|
|
|
step.template,
|
|
|
|
step.senderName || SENDER_NAME
|
|
|
|
);
|
|
|
|
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
|
|
|
|
await scheduleSMSReminder(
|
|
|
|
bookingInfo,
|
|
|
|
step.sendTo,
|
|
|
|
trigger,
|
|
|
|
step.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
step.reminderBody || "",
|
|
|
|
createdStep.id,
|
|
|
|
step.template,
|
|
|
|
step.sender || SENDER_ID,
|
|
|
|
user.id,
|
|
|
|
userWorkflow.teamId
|
|
|
|
);
|
2023-07-11 15:48:44 +00:00
|
|
|
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) {
|
|
|
|
await scheduleWhatsappReminder(
|
|
|
|
bookingInfo,
|
|
|
|
step.sendTo,
|
|
|
|
trigger,
|
|
|
|
step.action,
|
|
|
|
{
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
step.reminderBody || "",
|
|
|
|
createdStep.id,
|
|
|
|
step.template,
|
|
|
|
user.id,
|
|
|
|
userWorkflow.teamId
|
|
|
|
);
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
2023-05-12 17:31:01 +00:00
|
|
|
}
|
2023-04-25 22:39:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
//update trigger, name, time, timeUnit
|
|
|
|
await ctx.prisma.workflow.update({
|
|
|
|
where: {
|
|
|
|
id,
|
|
|
|
},
|
|
|
|
data: {
|
|
|
|
name,
|
|
|
|
trigger,
|
|
|
|
time,
|
|
|
|
timeUnit,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const workflow = await ctx.prisma.workflow.findFirst({
|
|
|
|
where: {
|
|
|
|
id,
|
|
|
|
},
|
|
|
|
include: {
|
|
|
|
activeOn: {
|
|
|
|
select: {
|
|
|
|
eventType: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
team: {
|
|
|
|
select: {
|
|
|
|
id: true,
|
|
|
|
slug: true,
|
|
|
|
members: true,
|
2023-06-26 10:37:59 +00:00
|
|
|
name: true,
|
2023-04-25 22:39:47 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
steps: {
|
|
|
|
orderBy: {
|
|
|
|
stepNumber: "asc",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
// Remove or add booking field for sms reminder number
|
|
|
|
const smsReminderNumberNeeded =
|
2023-07-11 15:48:44 +00:00
|
|
|
activeOn.length &&
|
|
|
|
steps.some(
|
|
|
|
(step) =>
|
|
|
|
step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.WHATSAPP_ATTENDEE
|
|
|
|
);
|
2023-04-25 22:39:47 +00:00
|
|
|
|
|
|
|
for (const removedEventType of removedEventTypes) {
|
|
|
|
await removeSmsReminderFieldForBooking({
|
|
|
|
workflowId: id,
|
|
|
|
eventTypeId: removedEventType,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-05-15 13:56:26 +00:00
|
|
|
for (const eventTypeId of activeOnWithChildren) {
|
2023-04-25 22:39:47 +00:00
|
|
|
if (smsReminderNumberNeeded) {
|
|
|
|
await upsertSmsReminderFieldForBooking({
|
|
|
|
workflowId: id,
|
|
|
|
isSmsReminderNumberRequired: steps.some(
|
2023-07-11 15:48:44 +00:00
|
|
|
(s) =>
|
|
|
|
(s.action === WorkflowActions.SMS_ATTENDEE || s.action === WorkflowActions.WHATSAPP_ATTENDEE) &&
|
|
|
|
s.numberRequired
|
2023-04-25 22:39:47 +00:00
|
|
|
),
|
|
|
|
eventTypeId,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
await removeSmsReminderFieldForBooking({ workflowId: id, eventTypeId });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
workflow,
|
|
|
|
};
|
|
|
|
};
|