Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dayjs from "@calcom/dayjs";
import { BookingSeatRepository } from "@calcom/features/bookings/repositories/BookingSeatRepository";
import { EmailWorkflowService } from "@calcom/features/ee/workflows/lib/service/EmailWorkflowService";
import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService";
Expand All @@ -7,7 +8,7 @@ import logger from "@calcom/lib/logger";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma from "@calcom/prisma";
import type { TimeUnit } from "@calcom/prisma/enums";
import { WorkflowMethods, type WorkflowTemplates, type WorkflowTriggerEvents } from "@calcom/prisma/enums";
import { WorkflowMethods, WorkflowTriggerEvents, type WorkflowTemplates } from "@calcom/prisma/enums";

import type { BookingInfo, FormSubmissionData, ScheduleEmailReminderAction } from "../types";
import { sendOrScheduleWorkflowEmails } from "./providers/emailProvider";
Expand Down Expand Up @@ -127,6 +128,18 @@ const scheduleEmailReminderForEvt = async (args: scheduleEmailReminderArgs & { e
timeUnit: timeSpan.timeUnit,
evt,
});

if (
scheduledDate &&
triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT &&
dayjs(scheduledDate).isBefore(dayjs())
) {
log.debug(
`Skipping reminder for workflow step ${workflowStepId} - scheduled date ${scheduledDate} is in the past`
);
return;
}

const workflowReminderRepository = new WorkflowReminderRepository(prisma);
const bookingSeatRepository = new BookingSeatRepository(prisma);
const emailWorkflowService = new EmailWorkflowService(workflowReminderRepository, bookingSeatRepository);
Expand Down
11 changes: 11 additions & 0 deletions packages/features/ee/workflows/lib/reminders/smsReminderManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@ const scheduleSMSReminderForEvt = async (
scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null;
}

if (
scheduledDate &&
triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT &&
dayjs(scheduledDate).isBefore(dayjs())
) {
log.debug(
`Skipping reminder for workflow step ${workflowStepId} - scheduled date ${scheduledDate} is in the past`
);
return;
}

const useTwilio = shouldUseTwilio(triggerEvent, scheduledDate);
if (useTwilio) {
const attendeeToBeUsedInSMS = getAttendeeToBeUsedInSMS(action, evt, reminderPhone);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs &
scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null;
}

if (
scheduledDate &&
triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT &&
dayjs(scheduledDate).isBefore(currentDate)
) {
log.debug(
`Skipping reminder for workflow step ${workflowStepId} - scheduled date ${scheduledDate} is in the past`
);
return;
}

const name = action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].name : evt.organizer.name;
const attendeeName =
action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.organizer.name : evt.attendees[0].name;
Expand Down
90 changes: 84 additions & 6 deletions packages/features/ee/workflows/lib/service/WorkflowService.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dayjs from "@calcom/dayjs";
import { describe, expect, vi, beforeEach } from "vitest";

import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
Expand Down Expand Up @@ -249,10 +250,14 @@ describe("WorkflowService.scheduleLazyEmailWorkflow", () => {
timeUnit: TimeUnit.HOUR,
};

// Use future dates to ensure scheduled date is not in the past
const futureStartTime = dayjs().add(3, "days").toISOString();
const futureEndTime = dayjs().add(3, "days").add(1, "hour").toISOString();

const mockEvt = {
uid: "booking-123",
startTime: "2024-12-01T10:00:00Z",
endTime: "2024-12-01T11:00:00Z",
startTime: futureStartTime,
endTime: futureEndTime,
};

const mockWorkflowReminder = {
Expand Down Expand Up @@ -297,10 +302,14 @@ describe("WorkflowService.scheduleLazyEmailWorkflow", () => {
timeUnit: TimeUnit.HOUR,
};

// Use future dates to ensure scheduled date is not in the past
const futureStartTime = dayjs().add(1, "day").toISOString();
const futureEndTime = dayjs().add(1, "day").add(1, "hour").toISOString();

const mockEvt = {
uid: "booking-456",
startTime: "2024-12-01T10:00:00Z",
endTime: "2024-12-01T11:00:00Z",
startTime: futureStartTime,
endTime: futureEndTime,
};

const mockWorkflowReminder = {
Expand Down Expand Up @@ -337,10 +346,14 @@ describe("WorkflowService.scheduleLazyEmailWorkflow", () => {
timeUnit: TimeUnit.HOUR,
};

// Use future dates to ensure scheduled date is not in the past
const futureStartTime = dayjs().add(3, "days").toISOString();
const futureEndTime = dayjs().add(3, "days").add(1, "hour").toISOString();

const mockEvt = {
uid: "booking-789",
startTime: "2024-12-01T10:00:00Z",
endTime: "2024-12-01T11:00:00Z",
startTime: futureStartTime,
endTime: futureEndTime,
};

const mockWorkflowReminder = {
Expand Down Expand Up @@ -407,6 +420,71 @@ describe("WorkflowService.scheduleLazyEmailWorkflow", () => {
expect(mockWorkflowReminderCreate).not.toHaveBeenCalled();
expect(mockTasker.create).not.toHaveBeenCalled();
});

test("should skip reminder if scheduled date is in the past for BEFORE_EVENT", async () => {
const mockWorkflow = {
time: 48,
timeUnit: TimeUnit.HOUR,
};

// Event is only 24 hours away, but reminder is set for 48 hours before
// This means the scheduled date would be in the past
const tomorrowStartTime = dayjs().add(1, "day").toISOString();
const tomorrowEndTime = dayjs().add(1, "day").add(1, "hour").toISOString();

const mockEvt = {
uid: "booking-past-reminder",
startTime: tomorrowStartTime,
endTime: tomorrowEndTime,
};

await WorkflowService.scheduleLazyEmailWorkflow({
workflowTriggerEvent: "BEFORE_EVENT",
workflowStepId: 1,
workflow: mockWorkflow,
evt: mockEvt,
});

// Should not create reminder or schedule task when scheduled date is in the past
expect(mockWorkflowReminderCreate).not.toHaveBeenCalled();
expect(mockTasker.create).not.toHaveBeenCalled();
});

test("should not skip reminder for AFTER_EVENT even if calculated date seems past", async () => {
const mockWorkflow = {
time: 1,
timeUnit: TimeUnit.HOUR,
};

// For AFTER_EVENT, we add time to endTime, so it should always be in the future
const pastStartTime = dayjs().subtract(1, "hour").toISOString();
const pastEndTime = dayjs().subtract(30, "minutes").toISOString();

const mockEvt = {
uid: "booking-after-event",
startTime: pastStartTime,
endTime: pastEndTime,
};

const mockWorkflowReminder = {
id: 4,
uuid: "reminder-uuid-after",
};

mockWorkflowReminderCreate.mockResolvedValue(mockWorkflowReminder);
mockTasker.create.mockResolvedValue({ id: "task-after" });

await WorkflowService.scheduleLazyEmailWorkflow({
workflowTriggerEvent: "AFTER_EVENT",
workflowStepId: 4,
workflow: mockWorkflow,
evt: mockEvt,
});

// AFTER_EVENT reminders should still be scheduled (they're relative to endTime + time)
expect(mockWorkflowReminderCreate).toHaveBeenCalled();
expect(mockTasker.create).toHaveBeenCalled();
});
});

describe("WorkflowService.processWorkflowScheduledDate", () => {
Expand Down
10 changes: 10 additions & 0 deletions packages/features/ee/workflows/lib/service/WorkflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,16 @@ export class WorkflowService {
return;
}

if (
workflowTriggerEvent === WorkflowTriggerEvents.BEFORE_EVENT &&
dayjs(scheduledDate).isBefore(dayjs())
) {
log.debug(
`Skipping lazy email reminder for workflow step ${workflowStepId} - scheduled date ${scheduledDate} is in the past`
);
return;
}

let workflowReminder: { id: number | null; uuid: string | null };
try {
const workflowReminderRepository = new WorkflowReminderRepository(prisma);
Expand Down
Loading