feat: implement comprehensive notification engine with policies, preferences, and automated dispatching services
This commit is contained in:
@@ -32,6 +32,7 @@ jest.mock("../../Companies/Companies", () => ({
|
||||
companies: {
|
||||
findOne: jest.fn(),
|
||||
},
|
||||
getCompanyAddress: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -41,6 +42,7 @@ jest.mock("../../Employees/Employee", () => ({
|
||||
employees: {
|
||||
findOne: jest.fn(),
|
||||
},
|
||||
checkPermission: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -151,6 +153,9 @@ jest.mock("../../../helpers/check", () => ({
|
||||
jest.mock("../../Templates/Templates", () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
templates: {
|
||||
findOne: jest.fn(),
|
||||
},
|
||||
sendEmail: jest.fn(),
|
||||
},
|
||||
}));
|
||||
@@ -180,6 +185,8 @@ import UsersManager from "../../Users/Users";
|
||||
import EmployeesList from "../../Employees/Employee";
|
||||
import ClientsManager from "../../Clients/Clients";
|
||||
import ServiceList from "../../Services/Service";
|
||||
import CompaniesManager from "../../Companies/Companies";
|
||||
import Templates from "../../Templates/Templates";
|
||||
|
||||
describe("getAppointmentEvent — historical read snapshot fallback", () => {
|
||||
let originalAdapter: any;
|
||||
@@ -385,3 +392,265 @@ describe("getAppointmentEvent — historical read snapshot fallback", () => {
|
||||
expect(result.collaboratorName).toBe("Jane Roe");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancellation notification jobs", () => {
|
||||
let originalAdapter: any;
|
||||
let originalJobService: any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
originalAdapter = AppointmentsList.Appointments;
|
||||
originalJobService = (AppointmentsList as any).jobService;
|
||||
|
||||
(AppointmentsList as any).Appointments = {
|
||||
findOne: jest.fn().mockResolvedValue({
|
||||
_id: "appt-001",
|
||||
companyId: "company-001",
|
||||
clientId: "client-001",
|
||||
employeeId: "employee-001",
|
||||
serviceId: "service-001",
|
||||
start: new Date("2026-07-22T11:00:00.000Z"),
|
||||
price: 1500,
|
||||
}),
|
||||
};
|
||||
(AppointmentsList as any).jobService = {
|
||||
createJob: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
|
||||
(CompaniesManager.companies.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "company-001",
|
||||
name: "clases llavallol",
|
||||
ownerId: "owner-001",
|
||||
templateEmailCancellationId: "email-template-001",
|
||||
templateWapCancellationId: "wap-template-001",
|
||||
address: "Av. Siempre Viva 123",
|
||||
});
|
||||
(CompaniesManager.getCompanyAddress as jest.Mock).mockReturnValue("Av. Siempre Viva 123");
|
||||
(ClientsManager.clients.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "client-001",
|
||||
userId: "client-user-001",
|
||||
firstName: "Ada",
|
||||
lastName: "Lovelace",
|
||||
email: "ada@example.com",
|
||||
});
|
||||
(ClientsManager.getClientFullName as jest.Mock).mockReturnValue("Ada Lovelace");
|
||||
(EmployeesList.employees.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "employee-001",
|
||||
userId: "employee-user-001",
|
||||
});
|
||||
(EmployeesList.checkPermission as jest.Mock | undefined)?.mockResolvedValue?.(true);
|
||||
(UsersManager.users.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "employee-user-001",
|
||||
firstName: "Grace",
|
||||
lastName: "Hopper",
|
||||
});
|
||||
(UsersManager.getUserFullName as jest.Mock).mockReturnValue("Grace Hopper");
|
||||
(ServiceList.services.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "service-001",
|
||||
name: "Contabilidad",
|
||||
});
|
||||
(Templates.templates.findOne as jest.Mock).mockImplementation(({ id }) => {
|
||||
if (id === "email-template-001") {
|
||||
return Promise.resolve({
|
||||
template: "EMAIL CANCEL <---CLIENTE---> <---SERVICIO---> <---ORGANIZACION---> <---FECHA---> <---HORARIO--->",
|
||||
});
|
||||
}
|
||||
|
||||
if (id === "wap-template-001") {
|
||||
return Promise.resolve({
|
||||
template: "WAP CANCEL <---CLIENTE---> <---SERVICIO---> <---ORGANIZACION---> <---FECHA---> <---HORARIO--->",
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(AppointmentsList as any).Appointments = originalAdapter;
|
||||
(AppointmentsList as any).jobService = originalJobService;
|
||||
});
|
||||
|
||||
it("stores organization-rendered cancellation template content per channel", async () => {
|
||||
const emailContent = await (AppointmentsList as any).tryToSendNotification({
|
||||
appointmentId: "appt-001",
|
||||
sessionUser: "owner-001",
|
||||
type: "cancellation",
|
||||
channel: "email",
|
||||
});
|
||||
const wapContent = await (AppointmentsList as any).tryToSendNotification({
|
||||
appointmentId: "appt-001",
|
||||
sessionUser: "owner-001",
|
||||
type: "cancellation",
|
||||
channel: "whatsapp",
|
||||
});
|
||||
|
||||
await (AppointmentsList as any).createCancellationNotificationJobs({
|
||||
appointmentId: "appt-001",
|
||||
companyId: "company-001",
|
||||
clientId: "client-001",
|
||||
clientUserId: "client-user-001",
|
||||
clientEmail: "ada@example.com",
|
||||
clientPhoneNumber: "5491112345678",
|
||||
companyOwnerId: "owner-001",
|
||||
companyName: "clases llavallol",
|
||||
emailMessage: emailContent.message,
|
||||
wapMessage: wapContent.message,
|
||||
});
|
||||
|
||||
expect((AppointmentsList as any).jobService.createJob).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "cancellation",
|
||||
payload: expect.objectContaining({
|
||||
emailSubject: "TurnosXpress :: Turno cancelado",
|
||||
emailMessage: "EMAIL CANCEL Ada Lovelace Contabilidad clases llavallol 22/07/2026 08:00hs.",
|
||||
wapMessage: "WAP CANCEL Ada Lovelace Contabilidad clases llavallol 22/07/2026 08:00hs.",
|
||||
systemSubject: "Turno cancelado en clases llavallol",
|
||||
systemMessage: "EMAIL CANCEL Ada Lovelace Contabilidad clases llavallol 22/07/2026 08:00hs.",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("creation notification jobs", () => {
|
||||
let originalAdapter: any;
|
||||
let originalJobService: any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
originalAdapter = AppointmentsList.Appointments;
|
||||
originalJobService = (AppointmentsList as any).jobService;
|
||||
|
||||
(AppointmentsList as any).Appointments = {
|
||||
findOne: jest.fn().mockResolvedValue({
|
||||
_id: "appt-001",
|
||||
companyId: "company-001",
|
||||
clientId: "client-001",
|
||||
employeeId: "employee-001",
|
||||
serviceId: "service-001",
|
||||
start: new Date("2026-07-22T11:00:00.000Z"),
|
||||
price: 1500,
|
||||
}),
|
||||
};
|
||||
(AppointmentsList as any).jobService = {
|
||||
createJob: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
|
||||
(CompaniesManager.companies.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "company-001",
|
||||
name: "clases llavallol",
|
||||
ownerId: "owner-001",
|
||||
templateEmailAltaId: "email-alta-template-001",
|
||||
templateWapAltaId: "wap-alta-template-001",
|
||||
address: "Av. Siempre Viva 123",
|
||||
});
|
||||
(CompaniesManager.getCompanyAddress as jest.Mock).mockReturnValue("Av. Siempre Viva 123");
|
||||
(ClientsManager.clients.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "client-001",
|
||||
userId: "client-user-001",
|
||||
firstName: "Ada",
|
||||
lastName: "Lovelace",
|
||||
email: "ada@example.com",
|
||||
});
|
||||
(ClientsManager.getClientFullName as jest.Mock).mockReturnValue("Ada Lovelace");
|
||||
(EmployeesList.employees.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "employee-001",
|
||||
userId: "employee-user-001",
|
||||
});
|
||||
(EmployeesList.checkPermission as jest.Mock | undefined)?.mockResolvedValue?.(true);
|
||||
(UsersManager.users.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "employee-user-001",
|
||||
firstName: "Grace",
|
||||
lastName: "Hopper",
|
||||
});
|
||||
(UsersManager.getUserFullName as jest.Mock).mockReturnValue("Grace Hopper");
|
||||
(ServiceList.services.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "service-001",
|
||||
name: "Historia",
|
||||
});
|
||||
(Templates.templates.findOne as jest.Mock).mockImplementation(({ id }) => {
|
||||
if (id === "email-alta-template-001") {
|
||||
return Promise.resolve({
|
||||
template: "EMAIL ALTA <---CLIENTE---> <---SERVICIO---> <---ORGANIZACION---> <---FECHA---> <---HORARIO--->",
|
||||
});
|
||||
}
|
||||
|
||||
if (id === "wap-alta-template-001") {
|
||||
return Promise.resolve({
|
||||
template: "WAP ALTA <---CLIENTE---> <---SERVICIO---> <---ORGANIZACION---> <---FECHA---> <---HORARIO--->",
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(AppointmentsList as any).Appointments = originalAdapter;
|
||||
(AppointmentsList as any).jobService = originalJobService;
|
||||
});
|
||||
|
||||
it("stores organization-rendered Alta template content per channel", async () => {
|
||||
const emailContent = await (AppointmentsList as any).tryToSendNotification({
|
||||
appointmentId: "appt-001",
|
||||
sessionUser: "owner-001",
|
||||
type: "creation",
|
||||
channel: "email",
|
||||
});
|
||||
const wapContent = await (AppointmentsList as any).tryToSendNotification({
|
||||
appointmentId: "appt-001",
|
||||
sessionUser: "owner-001",
|
||||
type: "creation",
|
||||
channel: "whatsapp",
|
||||
});
|
||||
|
||||
await (AppointmentsList as any).createCreationNotificationJobs({
|
||||
appointmentId: "appt-001",
|
||||
companyId: "company-001",
|
||||
clientId: "client-001",
|
||||
clientUserId: "client-user-001",
|
||||
clientEmail: "ada@example.com",
|
||||
clientPhoneNumber: "5491112345678",
|
||||
companyOwnerId: "owner-001",
|
||||
companyName: "clases llavallol",
|
||||
appointmentStart: new Date("2026-07-22T11:00:00.000Z"),
|
||||
emailMessage: emailContent.message,
|
||||
wapMessage: wapContent.message,
|
||||
});
|
||||
|
||||
expect((AppointmentsList as any).jobService.createJob).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "creation",
|
||||
payload: expect.objectContaining({
|
||||
emailSubject: "TurnosXpress :: Alta de turno",
|
||||
emailMessage: "EMAIL ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
|
||||
wapMessage: "WAP ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
|
||||
systemSubject: "Turno reservado en clases llavallol",
|
||||
systemMessage: "EMAIL ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to organization WAP Alta template for email when email Alta is absent", async () => {
|
||||
(CompaniesManager.companies.findOne as jest.Mock).mockResolvedValue({
|
||||
_id: "company-001",
|
||||
name: "clases llavallol",
|
||||
ownerId: "owner-001",
|
||||
templateWapAltaId: "wap-alta-template-001",
|
||||
address: "Av. Siempre Viva 123",
|
||||
});
|
||||
|
||||
const emailContent = await (AppointmentsList as any).tryToSendNotification({
|
||||
appointmentId: "appt-001",
|
||||
sessionUser: "owner-001",
|
||||
type: "creation",
|
||||
channel: "email",
|
||||
});
|
||||
|
||||
expect(emailContent.message).toBe("WAP ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user