diff --git a/packages/features/ee/billing/credit-service.ts b/packages/features/ee/billing/credit-service.ts index 864114c4e355e5..8b8b78819f5a6e 100644 --- a/packages/features/ee/billing/credit-service.ts +++ b/packages/features/ee/billing/credit-service.ts @@ -605,11 +605,15 @@ export class CreditService { "@calcom/features/ee/workflows/lib/reminders/reminderScheduler" ); promises.push( - cancelScheduledMessagesAndScheduleEmails({ teamId: result.teamId, userId: result.userId }).catch( - (error) => { - log.error("Failed to cancel scheduled messages", error, { result }); - } - ) + cancelScheduledMessagesAndScheduleEmails({ + teamId: result.teamId, + userIdsWithNoCredits: await this._getUserIdsWithoutCredits({ + teamId: result.teamId ?? null, + userId: result.userId ?? null, + }), + }).catch((error) => { + log.error("Failed to cancel scheduled messages", error, { result }); + }) ); } @@ -826,4 +830,34 @@ export class CreditService { }; }); } + + private async _getUserIdsWithoutCredits({ + teamId, + userId, + }: { + teamId: number | null; + userId: number | null; + }) { + let userIdsWithNoCredits: number[] = userId ? [userId] : []; + if (teamId) { + const teamMembers = await prisma.membership.findMany({ + where: { + teamId, + accepted: true, + }, + }); + + userIdsWithNoCredits = ( + await Promise.all( + teamMembers.map(async (member) => { + const hasCredits = await this.hasAvailableCredits({ userId: member.userId }); + return { userId: member.userId, hasCredits }; + }) + ) + ) + .filter(({ hasCredits }) => !hasCredits) + .map(({ userId }) => userId); + } + return userIdsWithNoCredits; + } } diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.test.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.test.ts index aa8549db6aa9b7..fbd3c98b0241e9 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.test.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.test.ts @@ -34,8 +34,6 @@ describe("reminderScheduler", () => { describe("cancelScheduledMessagesAndScheduleEmails", () => { it("should cancel SMS messages and schedule emails for team", async () => { - prismaMock.membership.findMany.mockResolvedValue([]); - const mockScheduledMessages = [ { id: 1, @@ -63,7 +61,7 @@ describe("reminderScheduler", () => { prismaMock.workflowReminder.updateMany.mockResolvedValue({ count: 1 }); - await cancelScheduledMessagesAndScheduleEmails({ teamId: 10 }); + await cancelScheduledMessagesAndScheduleEmails({ teamId: 10, userIdsWithNoCredits: [1, 2, 3] }); expect(twilioProvider.cancelSMS).toHaveBeenCalledWith("sms-123"); @@ -76,12 +74,13 @@ describe("reminderScheduler", () => { ); const callArgs = prismaMock.workflowReminder.findMany.mock.calls[0][0]; - expect(callArgs.where.workflowStep.workflow.OR).toEqual([{ userId: { in: [] } }, { teamId: 10 }]); + expect(callArgs.where.workflowStep.workflow.OR).toEqual([ + { userId: { in: [1, 2, 3] } }, + { teamId: 10 }, + ]); }); it("should cancel SMS messages and schedule emails for user", async () => { - prismaMock.membership.findMany.mockResolvedValue([]); - const mockScheduledMessages = [ { id: 1, @@ -109,7 +108,7 @@ describe("reminderScheduler", () => { prismaMock.workflowReminder.updateMany.mockResolvedValue({ count: 1 }); - await cancelScheduledMessagesAndScheduleEmails({ userId: 11 }); + await cancelScheduledMessagesAndScheduleEmails({ userIdsWithNoCredits: [11] }); const callArgs = prismaMock.workflowReminder.findMany.mock.calls[0][0]; expect(callArgs.where.workflowStep.workflow.OR).toEqual([{ userId: { in: [11] } }]); diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts index 8ddd80a8343693..63c47a4971861b 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts @@ -11,14 +11,14 @@ import * as twilio from "@calcom/features/ee/workflows/lib/reminders/providers/t import type { Workflow, WorkflowStep } from "@calcom/features/ee/workflows/lib/types"; import { getSubmitterEmail } from "@calcom/features/tasker/tasks/triggerFormSubmittedNoEvent/formSubmissionValidation"; import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; -import { checkSMSRateLimit } from "@calcom/lib/smsLockState"; import { SENDER_NAME } from "@calcom/lib/constants"; import { formatCalEventExtended } from "@calcom/lib/formatCalendarEvent"; import { withReporting } from "@calcom/lib/sentryWrapper"; import { getTranslation } from "@calcom/lib/server/i18n"; +import { checkSMSRateLimit } from "@calcom/lib/smsLockState"; import prisma from "@calcom/prisma"; import { SchedulingType } from "@calcom/prisma/enums"; -import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums"; +import { WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { scheduleAIPhoneCall } from "./aiPhoneCallManager"; @@ -288,86 +288,18 @@ const _sendCancelledReminders = async (args: SendCancelledRemindersArgs) => { const _cancelScheduledMessagesAndScheduleEmails = async ({ teamId, - userId, + userIdsWithNoCredits, }: { teamId?: number | null; - userId?: number | null; + userIdsWithNoCredits: number[]; }) => { - const { CreditService } = await import("@calcom/features/ee/billing/credit-service"); - - let userIdsWithNoCredits: number[] = userId ? [userId] : []; - - if (teamId) { - const teamMembers = await prisma.membership.findMany({ - where: { - teamId, - accepted: true, - }, - }); - - const creditService = new CreditService(); - - userIdsWithNoCredits = ( - await Promise.all( - teamMembers.map(async (member) => { - const hasCredits = await creditService.hasAvailableCredits({ userId: member.userId }); - return { userId: member.userId, hasCredits }; - }) - ) - ) - .filter(({ hasCredits }) => !hasCredits) - .map(({ userId }) => userId); - } + const { WorkflowReminderRepository } = await import( + "@calcom/features/ee/workflows/repositories/WorkflowReminderRepository" + ); - const scheduledMessages = await prisma.workflowReminder.findMany({ - where: { - workflowStep: { - workflow: { - OR: [ - { - userId: { - in: userIdsWithNoCredits, - }, - }, - ...(teamId ? [{ teamId }] : []), - ], - }, - }, - scheduled: true, - OR: [{ cancelled: false }, { cancelled: null }], - referenceId: { - not: null, - }, - method: { - in: [WorkflowMethods.SMS, WorkflowMethods.WHATSAPP], - }, - }, - select: { - referenceId: true, - workflowStep: { - select: { - action: true, - }, - }, - scheduledDate: true, - uuid: true, - id: true, - booking: { - select: { - attendees: { - select: { - email: true, - locale: true, - }, - }, - user: { - select: { - email: true, - }, - }, - }, - }, - }, + const scheduledMessages = await WorkflowReminderRepository.findScheduledMessagesToCancel({ + teamId, + userIdsWithNoCredits, }); await Promise.allSettled(scheduledMessages.map((msg) => twilio.cancelSMS(msg.referenceId ?? ""))); @@ -393,16 +325,8 @@ const _cancelScheduledMessagesAndScheduleEmails = async ({ }) ); - await prisma.workflowReminder.updateMany({ - where: { - id: { - in: scheduledMessages.map((msg) => msg.id), - }, - }, - data: { - method: WorkflowMethods.EMAIL, - referenceId: null, - }, + await WorkflowReminderRepository.updateRemindersToEmail({ + reminderIds: scheduledMessages.map((msg) => msg.id), }); }; // Export functions wrapped with withReporting diff --git a/packages/features/ee/workflows/repositories/WorkflowReminderRepository.ts b/packages/features/ee/workflows/repositories/WorkflowReminderRepository.ts new file mode 100644 index 00000000000000..60d6727d2c1b2d --- /dev/null +++ b/packages/features/ee/workflows/repositories/WorkflowReminderRepository.ts @@ -0,0 +1,77 @@ +import { prisma } from "@calcom/prisma"; +import { WorkflowMethods } from "@calcom/prisma/enums"; + +export class WorkflowReminderRepository { + static async findScheduledMessagesToCancel({ + teamId, + userIdsWithNoCredits, + }: { + teamId?: number | null; + userIdsWithNoCredits: number[]; + }) { + return await prisma.workflowReminder.findMany({ + where: { + workflowStep: { + workflow: { + OR: [ + { + userId: { + in: userIdsWithNoCredits, + }, + }, + ...(teamId ? [{ teamId }] : []), + ], + }, + }, + scheduled: true, + OR: [{ cancelled: false }, { cancelled: null }], + referenceId: { + not: null, + }, + method: { + in: [WorkflowMethods.SMS, WorkflowMethods.WHATSAPP], + }, + }, + select: { + referenceId: true, + workflowStep: { + select: { + action: true, + }, + }, + scheduledDate: true, + uuid: true, + id: true, + booking: { + select: { + attendees: { + select: { + email: true, + locale: true, + }, + }, + user: { + select: { + email: true, + }, + }, + }, + }, + }, + }); + } + + static async updateRemindersToEmail({ reminderIds }: { reminderIds: number[] }): Promise { + await prisma.workflowReminder.updateMany({ + where: { + id: { + in: reminderIds, + }, + }, + data: { + method: WorkflowMethods.EMAIL, + referenceId: null, + }, + }); + } +}