196 lines
7.0 KiB
TypeScript
196 lines
7.0 KiB
TypeScript
import mongoose, { Model } from "mongoose";
|
|
import { INotificationJobDocument, NotificationJobType } from "./NotificationJob.Adapter.Mongoose.js";
|
|
|
|
const { Schema, model, models } = mongoose;
|
|
|
|
interface AppointmentDocument {
|
|
_id: unknown;
|
|
companyId: unknown;
|
|
serviceId?: unknown;
|
|
clientId?: unknown;
|
|
userId?: unknown;
|
|
start: Date;
|
|
}
|
|
|
|
interface ClientDocument {
|
|
_id: unknown;
|
|
userId?: unknown;
|
|
firstName?: string;
|
|
lastName?: string;
|
|
email?: string;
|
|
}
|
|
|
|
interface UserDocument {
|
|
_id: unknown;
|
|
email?: string;
|
|
firstName?: string;
|
|
lastName?: string;
|
|
}
|
|
|
|
interface ServiceDocument {
|
|
_id: unknown;
|
|
name?: string;
|
|
}
|
|
|
|
interface CompanyDocument {
|
|
_id: unknown;
|
|
name?: string;
|
|
ownerId?: unknown;
|
|
}
|
|
|
|
export interface ResolvedNotificationContent {
|
|
email?: string;
|
|
phoneNumber?: string;
|
|
userId?: string;
|
|
companyOwnerId?: string;
|
|
subject: string;
|
|
message: string;
|
|
emailTemplateId?: string;
|
|
emailContext?: Record<string, unknown>;
|
|
}
|
|
|
|
const appointmentSchema = new Schema(
|
|
{
|
|
companyId: { type: Schema.Types.ObjectId, required: true, ref: "Companie" },
|
|
serviceId: { type: Schema.Types.ObjectId, required: false, ref: "Service" },
|
|
clientId: { type: Schema.Types.ObjectId, required: false, ref: "Client" },
|
|
userId: { type: Schema.Types.ObjectId, required: false, ref: "User" },
|
|
start: { type: Date, required: true },
|
|
},
|
|
{ strict: false }
|
|
);
|
|
|
|
const clientSchema = new Schema(
|
|
{
|
|
userId: { type: Schema.Types.ObjectId, required: false, ref: "User" },
|
|
firstName: { type: String, required: false },
|
|
lastName: { type: String, required: false },
|
|
email: { type: String, required: false },
|
|
},
|
|
{ strict: false }
|
|
);
|
|
|
|
const userSchema = new Schema(
|
|
{
|
|
email: { type: String, required: false },
|
|
firstName: { type: String, required: false },
|
|
lastName: { type: String, required: false },
|
|
},
|
|
{ strict: false }
|
|
);
|
|
|
|
const serviceSchema = new Schema(
|
|
{
|
|
name: { type: String, required: false },
|
|
},
|
|
{ strict: false }
|
|
);
|
|
|
|
const companySchema = new Schema(
|
|
{
|
|
name: { type: String, required: false },
|
|
ownerId: { type: Schema.Types.ObjectId, required: false, ref: "User" },
|
|
},
|
|
{ strict: false }
|
|
);
|
|
|
|
const Appointment = (models.Appointment || model<AppointmentDocument>("Appointment", appointmentSchema)) as Model<AppointmentDocument>;
|
|
const Client = (models.Client || model<ClientDocument>("Client", clientSchema)) as Model<ClientDocument>;
|
|
const User = (models.User || model<UserDocument>("User", userSchema)) as Model<UserDocument>;
|
|
const Service = (models.Service || model<ServiceDocument>("Service", serviceSchema)) as Model<ServiceDocument>;
|
|
const Company = (models.Company || model<CompanyDocument>("Company", companySchema)) as Model<CompanyDocument>;
|
|
|
|
function compactName(...parts: Array<string | undefined>): string {
|
|
return parts.filter(Boolean).join(" ").trim();
|
|
}
|
|
|
|
function formatDateTime(value?: Date): string {
|
|
if (!value) return "fecha pendiente";
|
|
return new Intl.DateTimeFormat("es-AR", {
|
|
dateStyle: "short",
|
|
timeStyle: "short",
|
|
timeZone: "America/Argentina/Buenos_Aires",
|
|
}).format(value);
|
|
}
|
|
|
|
export function buildNotificationText(params: {
|
|
type: NotificationJobType;
|
|
appointment?: AppointmentDocument | null;
|
|
client?: ClientDocument | null;
|
|
service?: ServiceDocument | null;
|
|
company?: CompanyDocument | null;
|
|
}): Pick<ResolvedNotificationContent, "subject" | "message"> {
|
|
const companyName = params.company?.name || "TurnosXpress";
|
|
const serviceName = params.service?.name || "tu turno";
|
|
const appointmentDate = formatDateTime(params.appointment?.start);
|
|
|
|
switch (params.type) {
|
|
case "creation":
|
|
return {
|
|
subject: `Alta de turno en ${companyName}`,
|
|
message: `Tu turno para ${serviceName} en ${companyName} fue confirmado para el ${appointmentDate}.`,
|
|
};
|
|
case "reminder":
|
|
return {
|
|
subject: `Recordatorio de turno en ${companyName}`,
|
|
message: `Te recordamos tu turno para ${serviceName} en ${companyName} el ${appointmentDate}.`,
|
|
};
|
|
case "update":
|
|
return {
|
|
subject: `Turno actualizado en ${companyName}`,
|
|
message: `Tu turno para ${serviceName} en ${companyName} fue actualizado. Nueva fecha: ${appointmentDate}.`,
|
|
};
|
|
case "cancellation":
|
|
return {
|
|
subject: `Turno cancelado en ${companyName}`,
|
|
message: `Tu turno para ${serviceName} en ${companyName} del ${appointmentDate} fue cancelado.`,
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function resolveNotificationContent(job: INotificationJobDocument): Promise<ResolvedNotificationContent> {
|
|
if ((job.type === "creation" || job.type === "reminder" || job.type === "update" || job.type === "cancellation") && job.payload) {
|
|
const { email, phoneNumber, userId, companyOwnerId } = job.payload;
|
|
const subject = job.channel === "email"
|
|
? job.payload.emailSubject
|
|
: job.channel === "system"
|
|
? job.payload.systemSubject
|
|
: job.payload.subject;
|
|
const message = job.channel === "whatsapp"
|
|
? job.payload.wapMessage
|
|
: job.channel === "email"
|
|
? job.payload.emailMessage
|
|
: job.payload.systemMessage;
|
|
const fallbackSubject = subject || job.payload.subject;
|
|
const fallbackMessage = message || job.payload.message;
|
|
if (fallbackSubject?.trim() && fallbackMessage?.trim()) {
|
|
return {
|
|
email: email?.trim(),
|
|
phoneNumber: phoneNumber?.trim(),
|
|
userId: userId ? String(userId) : undefined,
|
|
companyOwnerId: companyOwnerId ? String(companyOwnerId) : undefined,
|
|
subject: fallbackSubject.trim(),
|
|
message: fallbackMessage.trim(),
|
|
emailTemplateId: job.payload.emailTemplateId?.trim(),
|
|
emailContext: job.payload.emailContext,
|
|
};
|
|
}
|
|
}
|
|
|
|
const appointment = await Appointment.findOne({ _id: job.appointmentId }).lean().exec();
|
|
const client = await Client.findOne({ _id: job.clientId }).lean().exec();
|
|
const company = await Company.findOne({ _id: job.companyId }).lean().exec();
|
|
const service = appointment?.serviceId ? await Service.findOne({ _id: appointment.serviceId }).lean().exec() : null;
|
|
const userId = client?.userId || appointment?.userId || company?.ownerId;
|
|
const user = userId ? await User.findOne({ _id: userId }).lean().exec() : null;
|
|
const text = buildNotificationText({ type: job.type, appointment, client, service, company });
|
|
const email = client?.email || user?.email;
|
|
|
|
return {
|
|
email: email?.trim(),
|
|
userId: userId ? String(userId) : undefined,
|
|
subject: text.subject.trim(),
|
|
message: text.message.trim(),
|
|
};
|
|
}
|