Files
calendar/packages/features/bookings/lib/tasker/BookingEmailAndSmsTaskService.ts
T
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
f98acb7301 refactor: migrate workflows utilities from trpc to features layer (#25534)
* 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>
2025-12-16 10:17:46 +00:00

154 lines
6.1 KiB
TypeScript

import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
import dayjs from "@calcom/dayjs";
import { CalendarEventBuilder } from "@calcom/features/CalendarEventBuilder";
import type { BookingEmailSmsHandler } from "@calcom/features/bookings/lib/BookingEmailSmsHandler";
import { getOriginalRescheduledBooking } from "@calcom/features/bookings/lib/handleNewBooking/originalRescheduledBookingUtils";
import type { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
import { getAllWorkflowsFromEventType } from "@calcom/features/ee/workflows/lib/getAllWorkflowsFromEventType";
import type { EventNameObjectType } from "@calcom/features/eventtypes/lib/eventNaming";
import type { ITaskerDependencies } from "@calcom/lib/tasker/types";
import { SchedulingType } from "@calcom/prisma/enums";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { JsonObject } from "@calcom/types/Json";
import { BookingEmailAndSmsAsyncTasksPayload, BookingTasks } from "./types";
export interface IBookingTaskServiceDependencies {
emailsAndSmsHandler: BookingEmailSmsHandler;
bookingRepository: BookingRepository;
}
export class BookingEmailAndSmsTaskService implements BookingTasks {
constructor(
public readonly dependencies: { logger: ITaskerDependencies["logger"] } & IBookingTaskServiceDependencies
) {}
private async _getVerifiedBookingData(payload: BookingEmailAndSmsAsyncTasksPayload) {
const { bookingId, ...bookingMeta } = payload;
const { bookingRepository } = this.dependencies;
const booking = await bookingRepository.getBookingForCalEventBuilder(bookingId);
if (!booking) {
throw new Error(`Booking with id '${bookingId}' was not found.`);
}
if (!booking.eventType) {
throw new Error(`EventType of Booking with id '${bookingId}' was not found.`);
}
const calendarEvent = (await CalendarEventBuilder.fromBooking(booking, bookingMeta)).build();
if (!calendarEvent) {
throw new Error(`CalendarEvent could not be built from Booking with id '${bookingId}'.`);
}
return {
booking: booking,
eventType: booking.eventType,
calendarEvent: calendarEvent as NonNullable<CalendarEvent>,
};
}
async request(payload: Parameters<BookingTasks["request"]>[0]) {
const { eventType, calendarEvent } = await this._getVerifiedBookingData(payload);
await this.dependencies.emailsAndSmsHandler.send({
action: "BOOKING_REQUESTED",
data: {
evt: calendarEvent,
attendees: calendarEvent.attendees,
eventType: {
schedulingType: eventType.schedulingType ?? null,
metadata: eventTypeMetaDataSchemaWithTypedApps.parse(eventType.metadata),
},
additionalNotes: calendarEvent.additionalNotes,
},
});
}
async confirm(payload: Parameters<BookingTasks["confirm"]>[0]) {
const { booking, eventType, calendarEvent } = await this._getVerifiedBookingData(payload);
const attendeesEmail = booking.attendees.map((attendee) => attendee.email);
const bookedTeamMembers = eventType.team
? eventType.team.members.filter((teamUser) => attendeesEmail.includes(teamUser.user.email))
: [];
const eventNameObject = {
attendeeName: calendarEvent.organizer.name || "Nameless",
eventType: eventType.title,
eventName: eventType.eventName,
// we send on behalf of team if >1 round robin attendee | collective
teamName:
eventType.schedulingType === "COLLECTIVE" || bookedTeamMembers.length > 1
? eventType.team?.name
: null,
host: calendarEvent.organizer.name || "Nameless",
location: booking.location,
eventDuration: dayjs(booking.endTime).diff(booking.startTime, "minutes"),
bookingFields: eventType.bookingFields as JsonObject,
t: calendarEvent.organizer.language.translate,
} satisfies EventNameObjectType;
const workflows = await getAllWorkflowsFromEventType(
{
...eventType,
metadata: eventTypeMetaDataSchemaWithTypedApps.parse(eventType.metadata),
},
calendarEvent.organizer.id
);
await this.dependencies.emailsAndSmsHandler.send({
action: "BOOKING_CONFIRMED",
data: {
eventType: {
schedulingType: booking.eventType?.schedulingType ?? null,
metadata: eventTypeMetaDataSchemaWithTypedApps.parse(eventType.metadata),
},
eventNameObject,
workflows: workflows,
evt: calendarEvent,
additionalInformation: calendarEvent.additionalInformation ?? {},
additionalNotes: calendarEvent.additionalNotes,
customInputs: calendarEvent.customInputs,
},
});
}
async reschedule(payload: Parameters<BookingTasks["reschedule"]>[0]) {
const { booking, eventType, calendarEvent } = await this._getVerifiedBookingData(payload);
const originalBookingData = await getOriginalRescheduledBooking(
booking.uid,
!!booking.eventType?.seatsPerTimeSlot
);
const changedOrganizer =
!!originalBookingData &&
(eventType.schedulingType === SchedulingType.ROUND_ROBIN ||
eventType.schedulingType === SchedulingType.COLLECTIVE) &&
originalBookingData.userId !== calendarEvent.organizer.id;
await this.dependencies.emailsAndSmsHandler.send({
action: "BOOKING_RESCHEDULED",
data: {
evt: calendarEvent,
eventType: {
schedulingType: eventType.schedulingType ?? null,
metadata: eventTypeMetaDataSchemaWithTypedApps.parse(eventType.metadata),
},
additionalInformation: calendarEvent.additionalInformation ?? {},
additionalNotes: calendarEvent.additionalNotes,
iCalUID: calendarEvent.iCalUID ?? "",
originalRescheduledBooking: originalBookingData,
rescheduleReason: (calendarEvent?.responses?.["rescheduleReason"]?.value as string) ?? undefined,
users: eventType.hosts.map((h) => h.user) ?? [],
changedOrganizer: changedOrganizer,
isRescheduledByBooker: payload.isRescheduledByBooker ?? false,
},
});
}
async rrReschedule(payload: Parameters<BookingTasks["rrReschedule"]>[0]) {
return this.reschedule(payload);
}
}