* refactor: migrate workflows utilities from trpc to features layer Move workflow utility functions from packages/trpc/server/routers/viewer/workflows/util.ts to packages/features/ee/workflows/lib/workflowUtils.ts to break circular dependencies. Functions migrated: - isAuthorized - getAllWorkflowsFromEventType - scheduleWorkflowNotifications - scheduleBookingReminders Changes: - Created new workflowUtils.ts in features layer with migrated functions - Added getBookingsForWorkflowReminders to WorkflowRepository (no prisma outside repositories) - Updated all imports in features layer to use new location - Updated trpc util.ts to re-export from features for backward compatibility - Fixed pre-existing lint warning (any -> unknown) in handleMarkNoShow.ts This is part of the effort to remove circular dependencies between packages/features and packages/trpc. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * refactor: remove re-exports from trpc util.ts, update imports to use features layer Per user request, removed the backward compatibility re-exports from packages/trpc/server/routers/viewer/workflows/util.ts and updated all imports in the trpc package to import directly from the features layer. Files updated to import from @calcom/features/ee/workflows/lib/workflowUtils: - confirm.handler.ts (getAllWorkflowsFromEventType) - delete.handler.ts (isAuthorized) - update.handler.ts (isAuthorized, scheduleWorkflowNotifications) - getAllActiveWorkflows.handler.ts (getAllWorkflowsFromEventType) - get.handler.ts (isAuthorized) - util.test.ts (isAuthorized) - delete.handler.test.ts (isAuthorized) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update test imports to use features layer for workflow utilities Updated test files to import scheduleBookingReminders and scheduleWorkflowNotifications from @calcom/features/ee/workflows/lib/workflowUtils instead of @calcom/trpc/server/routers/viewer/workflows/util. Files updated: - packages/features/ee/workflows/lib/test/workflows.test.ts - packages/features/tasker/tasks/scanWorkflowBody.test.ts Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * refactor: split workflowUtils.ts into individual files Split the monolithic workflowUtils.ts into separate files for each function: - isAuthorized.ts - Authorization check for workflow access - getAllWorkflowsFromEventType.ts - Get workflows for an event type - scheduleWorkflowNotifications.ts - Schedule workflow notifications - scheduleBookingReminders.ts - Schedule booking reminders The workflowUtils.ts now re-exports from these individual files for backward compatibility. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix import * fix more * wip * wip * remove workflowUtils * wip * refactor deleteRemindersOfActiveOnIds * fix --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
222 lines
8.4 KiB
TypeScript
222 lines
8.4 KiB
TypeScript
import { scheduleAIPhoneCall } from "@calcom/ee/workflows/lib/reminders/aiPhoneCallManager";
|
|
import { scheduleEmailReminder } from "@calcom/ee/workflows/lib/reminders/emailReminderManager";
|
|
import { scheduleSMSReminder } from "@calcom/ee/workflows/lib/reminders/smsReminderManager";
|
|
import { scheduleWhatsappReminder } from "@calcom/ee/workflows/lib/reminders/whatsappReminderManager";
|
|
import { CreditService } from "@calcom/features/ee/billing/credit-service";
|
|
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
|
|
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
|
import type { WorkflowStep } from "@calcom/prisma/client";
|
|
import type { TimeUnit } from "@calcom/prisma/enums";
|
|
import { SchedulingType, WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums";
|
|
import type { CalEventResponses } from "@calcom/types/Calendar";
|
|
|
|
import type { getBookings } from "./scheduleWorkflowNotifications";
|
|
import { verifyEmailSender } from "./verifyEmailSender";
|
|
|
|
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
|
|
type Bookings = UnwrapPromise<ReturnType<typeof getBookings>>;
|
|
|
|
// some parts of scheduleWorkflowReminders (reminderSchedule.ts) is quite similar to this code
|
|
// we should consider refactoring this to reuse similar code snippets
|
|
export async function scheduleBookingReminders(
|
|
bookings: Bookings,
|
|
workflowSteps: Partial<WorkflowStep>[],
|
|
time: number | null,
|
|
timeUnit: TimeUnit | null,
|
|
trigger: WorkflowTriggerEvents,
|
|
userId: number,
|
|
teamId: number | null,
|
|
isOrg: boolean
|
|
) {
|
|
if (!bookings.length) return;
|
|
if (trigger !== WorkflowTriggerEvents.BEFORE_EVENT && trigger !== WorkflowTriggerEvents.AFTER_EVENT) return;
|
|
|
|
const bookerUrl = await getBookerBaseUrl(isOrg ? teamId : null);
|
|
|
|
const creditService = new CreditService();
|
|
|
|
//create reminders for all bookings for each workflow step
|
|
const promiseSteps = workflowSteps.map(async (step) => {
|
|
const promiseScheduleReminders = bookings.map(async (booking) => {
|
|
const defaultLocale = "en";
|
|
const bookingInfo = {
|
|
uid: booking.uid,
|
|
bookerUrl,
|
|
type: booking.eventType?.slug || "event",
|
|
attendees: booking.attendees.map((attendee) => {
|
|
return {
|
|
name: attendee.name,
|
|
email: attendee.email,
|
|
timeZone: attendee.timeZone,
|
|
language: { locale: attendee.locale || defaultLocale },
|
|
};
|
|
}),
|
|
organizer: booking.user
|
|
? {
|
|
language: { locale: booking.user.locale || defaultLocale },
|
|
name: booking.user.name || "",
|
|
email: booking?.userPrimaryEmail ?? booking.user.email,
|
|
timeZone: booking.user.timeZone,
|
|
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
|
|
}
|
|
: { name: "", email: "", timeZone: "", language: { locale: "" } },
|
|
startTime: booking.startTime?.toISOString(),
|
|
endTime: booking.endTime?.toISOString(),
|
|
title: booking.title,
|
|
language: { locale: booking?.user?.locale || defaultLocale },
|
|
hideOrganizerEmail: booking.eventType?.hideOrganizerEmail,
|
|
eventType: {
|
|
slug: booking.eventType?.slug || "",
|
|
schedulingType: booking.eventType?.schedulingType,
|
|
hosts: booking.eventType?.hosts,
|
|
},
|
|
metadata: booking.metadata,
|
|
customReplyToEmail: booking.eventType?.customReplyToEmail,
|
|
responses: booking.responses as CalEventResponses | null,
|
|
};
|
|
if (
|
|
step.action === WorkflowActions.EMAIL_HOST ||
|
|
step.action === WorkflowActions.EMAIL_ATTENDEE ||
|
|
step.action === WorkflowActions.EMAIL_ADDRESS
|
|
) {
|
|
let sendTo: string[] = [];
|
|
|
|
switch (step.action) {
|
|
case WorkflowActions.EMAIL_HOST: {
|
|
sendTo = [bookingInfo.organizer?.email];
|
|
const schedulingType = bookingInfo.eventType.schedulingType;
|
|
const hosts = bookingInfo.eventType.hosts
|
|
?.filter((host) => bookingInfo.attendees.some((attendee) => attendee.email === host.user.email))
|
|
.map(({ user }) => user.destinationCalendar?.primaryEmail ?? user.email);
|
|
if (
|
|
hosts &&
|
|
(schedulingType === SchedulingType.ROUND_ROBIN || schedulingType === SchedulingType.COLLECTIVE)
|
|
) {
|
|
sendTo = sendTo.concat(hosts);
|
|
}
|
|
break;
|
|
}
|
|
case WorkflowActions.EMAIL_ATTENDEE:
|
|
sendTo = bookingInfo.attendees.map((attendee) => attendee.email);
|
|
break;
|
|
case WorkflowActions.EMAIL_ADDRESS:
|
|
await verifyEmailSender(step.sendTo || "", userId, teamId);
|
|
sendTo = [step.sendTo || ""];
|
|
break;
|
|
}
|
|
|
|
await scheduleEmailReminder({
|
|
evt: bookingInfo,
|
|
triggerEvent: trigger,
|
|
action: step.action,
|
|
timeSpan: {
|
|
time,
|
|
timeUnit,
|
|
},
|
|
sendTo,
|
|
emailSubject: step.emailSubject || "",
|
|
emailBody: step.reminderBody || "",
|
|
template: step.template,
|
|
sender: step.sender,
|
|
workflowStepId: step.id,
|
|
verifiedAt: step?.verifiedAt ?? null,
|
|
});
|
|
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
|
|
await scheduleSMSReminder({
|
|
evt: bookingInfo,
|
|
reminderPhone: step.sendTo,
|
|
triggerEvent: trigger,
|
|
action: step.action,
|
|
timeSpan: {
|
|
time,
|
|
timeUnit,
|
|
},
|
|
message: step.reminderBody || "",
|
|
workflowStepId: step.id,
|
|
template: step.template,
|
|
sender: step.sender,
|
|
userId: userId,
|
|
teamId: teamId,
|
|
verifiedAt: step?.verifiedAt ?? null,
|
|
creditCheckFn: creditService.hasAvailableCredits.bind(creditService),
|
|
});
|
|
} else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) {
|
|
await scheduleWhatsappReminder({
|
|
evt: bookingInfo,
|
|
reminderPhone: step.sendTo,
|
|
triggerEvent: trigger,
|
|
action: step.action,
|
|
timeSpan: {
|
|
time,
|
|
timeUnit,
|
|
},
|
|
message: step.reminderBody || "",
|
|
workflowStepId: step.id || 0,
|
|
template: step.template,
|
|
userId: userId,
|
|
teamId: teamId,
|
|
verifiedAt: step?.verifiedAt ?? null,
|
|
creditCheckFn: creditService.hasAvailableCredits.bind(creditService),
|
|
});
|
|
} else if (booking.smsReminderNumber) {
|
|
if (step.action === WorkflowActions.SMS_ATTENDEE) {
|
|
await scheduleSMSReminder({
|
|
evt: bookingInfo,
|
|
reminderPhone: booking.smsReminderNumber,
|
|
triggerEvent: trigger,
|
|
action: step.action,
|
|
timeSpan: {
|
|
time,
|
|
timeUnit,
|
|
},
|
|
message: step.reminderBody || "",
|
|
workflowStepId: step.id,
|
|
template: step.template,
|
|
sender: step.sender,
|
|
userId: userId,
|
|
teamId: teamId,
|
|
verifiedAt: step?.verifiedAt ?? null,
|
|
creditCheckFn: creditService.hasAvailableCredits.bind(creditService),
|
|
});
|
|
} else if (step.action === WorkflowActions.WHATSAPP_ATTENDEE) {
|
|
await scheduleWhatsappReminder({
|
|
evt: bookingInfo,
|
|
reminderPhone: booking.smsReminderNumber,
|
|
triggerEvent: trigger,
|
|
action: step.action,
|
|
timeSpan: {
|
|
time,
|
|
timeUnit,
|
|
},
|
|
message: step.reminderBody || "",
|
|
workflowStepId: step.id,
|
|
template: step.template,
|
|
sender: step.sender,
|
|
userId: userId,
|
|
teamId: teamId,
|
|
verifiedAt: step?.verifiedAt ?? null,
|
|
creditCheckFn: creditService.hasAvailableCredits.bind(creditService),
|
|
});
|
|
}
|
|
} else if (step.action === WorkflowActions.CAL_AI_PHONE_CALL) {
|
|
await scheduleAIPhoneCall({
|
|
evt: bookingInfo,
|
|
triggerEvent: trigger,
|
|
timeSpan: {
|
|
time,
|
|
timeUnit,
|
|
},
|
|
workflowStepId: step.id,
|
|
userId,
|
|
teamId,
|
|
verifiedAt: step?.verifiedAt ?? null,
|
|
submittedPhoneNumber: booking.smsReminderNumber,
|
|
routedEventTypeId: null,
|
|
});
|
|
}
|
|
});
|
|
await Promise.all(promiseScheduleReminders);
|
|
});
|
|
return Promise.all(promiseSteps);
|
|
}
|