refactor: modularize notification job creation and enforce plan-based channel availability in policy resolver

This commit is contained in:
2026-07-27 22:51:33 -03:00
parent dc020d94d2
commit 68d98e8b40
6 changed files with 333 additions and 58 deletions
@@ -1,5 +1,6 @@
import {
CreateJobParams,
NotificationChannel,
} from "../NotificationJobs/NotificationJobs.Interface";
import { NotificationJobsAdapterMongoose } from "../NotificationJobs/NotificationJobs.Adapter.Mongoose";
import { PolicyResolver, ResolvedPolicy } from "./PolicyResolver";
@@ -121,6 +122,79 @@ export class NotificationJobService {
return jobs;
}
async createMandatoryCreationJob(params: CreateJobServiceParams): Promise<any[]> {
const payloadChannels = this.getPayloadChannels(params.payload);
const channels = await this.policyResolver.resolveAvailableChannels(
params.companyId,
payloadChannels
);
if (channels.length === 0) {
return [];
}
const scheduledAt = params.scheduledAt || new Date();
const jobs: any[] = [];
for (const channel of channels) {
const job = await this.jobAdapter.create({
companyId: params.companyId,
appointmentId: params.appointmentId,
clientId: params.clientId,
channel,
type: "creation",
scheduledAt,
payload: params.payload,
});
jobs.push(job);
}
return jobs;
}
async createReminderJobs(params: CreateJobServiceParams): Promise<any[]> {
const resolvedPolicy = await this.policyResolver.resolve(
params.companyId,
params.clientId
);
if (!params.appointmentStart || resolvedPolicy.channels.length === 0) {
return [];
}
const now = new Date();
const jobs: any[] = [];
const enabledReminderRules = (resolvedPolicy.reminderRules || []).filter(
(rule) => rule.enabled
);
for (const rule of enabledReminderRules) {
const reminderScheduledAt = this.adjustForQuietHours(
dayjs(params.appointmentStart).subtract(rule.offset, "minute").toDate(),
resolvedPolicy
);
if (reminderScheduledAt < now || reminderScheduledAt >= params.appointmentStart) {
continue;
}
for (const channel of resolvedPolicy.channels) {
const job = await this.jobAdapter.create({
companyId: params.companyId,
appointmentId: params.appointmentId,
clientId: params.clientId,
channel,
type: "reminder",
scheduledAt: reminderScheduledAt,
payload: params.reminderPayload,
});
jobs.push(job);
}
}
return jobs;
}
/**
* Cancel all pending jobs for an appointment.
* Called when an appointment is deleted or rescheduled.
@@ -165,6 +239,24 @@ export class NotificationJobService {
return scheduledAt;
}
private getPayloadChannels(payload?: CreateJobParams["payload"]): NotificationChannel[] {
const channels: NotificationChannel[] = [];
if (payload?.email) {
channels.push("email");
}
if (payload?.phoneNumber) {
channels.push("whatsapp");
}
if (payload?.userId) {
channels.push("system");
}
return channels;
}
private isTimeInQuietHours(
hour: number,
minute: number,