Files

424 lines
14 KiB
JavaScript

// /utils/senders/smsMessages.js
'use strict';
const { sendSingleSms } = require('./sms.base');
const { recordAndSend } = require('./notificationRecorder');
const { getSmsTemplate } = require('../../components/settings/settingService');
const { buildSmsParameters, renderSmsText } = require('../../components/settings/smsTemplates');
const { isSmsBypassNumber } = require('../../components/settings/messagingFlags');
const User = require('../../components/users/userModel');
const logger = require('../logger');
const resolveUserIdByPhone = async (phoneNumber) => {
if (!phoneNumber) return null;
const user = await User.findOne({ phoneNumber: String(phoneNumber) }).select('_id').lean();
return user?._id || null;
};
const resolveUserName = async (userId, fallback = '') => {
if (!userId) return fallback;
const user = await User.findById(userId).select('name uniqueCode').lean();
return {
fullName: user?.name || fallback,
userCode: user?.uniqueCode || ''
};
};
const sendTemplateSms = async ({
templateKey,
receiver,
userId = null,
subject,
body,
relatedEvent,
slotValues = {}
}) => {
const resolvedUserId = userId || await resolveUserIdByPhone(receiver);
const { templateId, variables, enabled, text: templateText } = await getSmsTemplate(templateKey);
const isBypass = await isSmsBypassNumber(receiver);
if (enabled === false && !isBypass) {
logger.info(`[SMS] Template ${templateKey} is disabled. Skipping SMS send to ${receiver}.`);
return { success: true, skipped: true, reason: 'template_disabled' };
}
const paramsContext = {
phoneNumber: receiver,
mobile: receiver,
...slotValues
};
const renderedExactBody = renderSmsText(templateText, variables, paramsContext);
const finalBody = (renderedExactBody && renderedExactBody.trim()) ? renderedExactBody.trim() : (body || subject || 'پیامک');
return recordAndSend({
userId: resolvedUserId,
channel: 'sms',
subject,
body: finalBody,
relatedEvent,
sendFn: () => sendSingleSms(receiver, templateId, buildSmsParameters(variables, paramsContext))
});
};
const sendAccountCredentialsSms = async ({
receiver,
username,
password,
userId = null,
subject,
body,
relatedEvent,
templateKey = 'accountCreated',
userCode = ''
}) => {
const resolvedUserId = userId || await resolveUserIdByPhone(receiver);
let fullName = '';
let code = userCode;
if (resolvedUserId) {
const profile = await resolveUserName(resolvedUserId, username);
fullName = profile.fullName;
code = code || profile.userCode;
}
return sendTemplateSms({
templateKey,
receiver,
userId: resolvedUserId,
subject,
body,
relatedEvent,
slotValues: {
username,
password,
fullName: fullName || username,
name: fullName || username,
userCode: code
}
});
};
const sendAccountCreatedSms = async (receiver, username, password, userId = null, userCode = '') => {
return sendAccountCredentialsSms({
receiver,
username,
password,
userId,
userCode,
subject: 'ایجاد حساب کاربری',
body: `حساب کاربری شما ایجاد شد. نام کاربری: ${username}`,
relatedEvent: 'user.created',
templateKey: 'accountCreated'
});
};
const sendPasswordResetSms = async (receiver, username, password, userId = null, userCode = '') => {
return sendAccountCredentialsSms({
receiver,
username,
password,
userId,
userCode,
subject: 'بازنشانی رمز عبور',
body: `رمز عبور شما بازنشانی شد. نام کاربری: ${username}`,
relatedEvent: 'user.password_reset',
templateKey: 'passwordReset'
});
};
const sendClassRegisteredSms = async (receiver, className, userId = null, extraContext = {}) => {
const resolvedUserId = userId || await resolveUserIdByPhone(receiver);
let fullName = extraContext.fullName || '';
if (!fullName && resolvedUserId) {
const profile = await resolveUserName(resolvedUserId);
fullName = profile.fullName;
}
return sendTemplateSms({
templateKey: 'classRegistered',
receiver,
userId: resolvedUserId,
subject: 'ثبت‌نام در کلاس',
body: `ثبت‌نام شما در کلاس «${className}» انجام شد.`,
relatedEvent: 'user.enrolled',
slotValues: {
className,
courseName: extraContext.courseName || className,
fullName: fullName || 'کارآموز',
classStartDate: extraContext.classStartDate || '',
classDays: extraContext.classDays || '',
courseTime: extraContext.courseTime || '',
classCode: extraContext.classCode || '',
...extraContext
}
});
};
const sendClassReminderSms = async (receiver, className, time, place = '', userId = null, extraContext = {}) => {
const resolvedUserId = userId || await resolveUserIdByPhone(receiver);
let fullName = extraContext.fullName || '';
if (!fullName && resolvedUserId) {
const profile = await resolveUserName(resolvedUserId);
fullName = profile.fullName;
}
return sendTemplateSms({
templateKey: 'classReminder',
receiver,
userId: resolvedUserId,
subject: 'یادآوری کلاس',
body: `یادآوری کلاس «${className}» ساعت ${time} — مکان: ${place || '-'}`,
relatedEvent: 'session.reminder',
slotValues: {
className,
topic: extraContext.topic || className,
courseName: extraContext.courseName || className,
time,
classTime: extraContext.classTime || extraContext.courseTime || extraContext.time || time || '',
sessionDate: extraContext.sessionDate || '',
place: place || '-',
fullName: fullName || 'کارآموز',
classStartDate: extraContext.classStartDate || '',
classDays: extraContext.classDays || '',
courseTime: extraContext.courseTime || extraContext.classTime || extraContext.time || time || '',
classCode: extraContext.classCode || '',
...extraContext
}
});
};
const { formatPrice } = require('../paymentAmount');
const sendInvoiceCreatedSms = async (receiver, payload, userId = null) => {
const resolvedUserId = userId || await resolveUserIdByPhone(receiver);
const data = typeof payload === 'object' && payload !== null ? payload : { amount: payload };
const fullNameLabel = data.fullName || 'کارآموز';
const amountLabel = data.amount != null ? formatPrice(data.amount) : '';
const courseLabel = data.course || '-';
return sendTemplateSms({
templateKey: 'invoiceCreated',
receiver,
userId: resolvedUserId,
subject: 'ایجاد صورتحساب',
body: `کارآموز عزیز، ${fullNameLabel}، یک صورتحساب به مبلغ ${amountLabel} تومان بابت دوره «${courseLabel}» برای شما ایجاد شد.`,
relatedEvent: 'payment.created',
slotValues: {
fullName: fullNameLabel,
amount: amountLabel,
course: courseLabel,
classStartDate: data.classStartDate || '',
classDays: data.classDays || '',
courseTime: data.courseTime || '',
invoiceCode: data.invoiceCode || ''
}
});
};
const sendPaymentStatusChangedSms = async (receiver, payload, userId = null) => {
const data = payload || {};
const amountLabel = data.amount != null ? formatPrice(data.amount) : '';
return sendTemplateSms({
templateKey: 'paymentStatusChanged',
receiver,
userId,
subject: 'تغییر وضعیت پرداخت',
body: `وضعیت صورتحساب شما به «${data.statusLabel || data.status || '-'}» تغییر کرد.`,
relatedEvent: 'payment.status_changed',
slotValues: {
fullName: data.fullName || 'کارآموز',
status: data.statusLabel || data.status || '-',
amount: amountLabel,
invoiceCode: data.invoiceCode || '',
course: data.course || '-'
}
});
};
const sendPaymentReminderSms = async (receiver, payload, userId = null) => {
const data = payload || {};
const amountLabel = data.amount != null ? formatPrice(data.amount) : '-';
return sendTemplateSms({
templateKey: 'paymentReminder',
receiver,
userId,
subject: 'یادآوری سررسید پرداخت',
body: `یادآوری: مبلغ ${amountLabel} تومان تا ${data.dueDate || '-'} سررسید دارد.`,
relatedEvent: 'payment.reminder_due',
slotValues: {
fullName: data.fullName || 'کارآموز',
amount: data.amount != null ? formatPrice(data.amount) : '',
dueDate: data.dueDate || '',
invoiceCode: data.invoiceCode || '',
course: data.course || '-'
}
});
};
const sendTransactionRecordedSms = async (receiver, payload, userId = null) => {
const data = payload || {};
const amountLabel = data.amount != null ? formatPrice(data.amount) : '-';
return sendTemplateSms({
templateKey: 'transactionRecorded',
receiver,
userId,
subject: 'ثبت تراکنش',
body: `تراکنش به مبلغ ${amountLabel} تومان ثبت شد.`,
relatedEvent: 'payment.transaction_added',
slotValues: {
fullName: data.fullName || 'کارآموز',
amount: data.amount != null ? formatPrice(data.amount) : '',
invoiceCode: data.invoiceCode || '',
transactionCode: data.transactionCode || '',
receiptNumber: data.receiptNumber || ''
}
});
};
const sendSessionCancelledSms = async (receiver, payload, userId = null) => {
const data = payload || {};
return sendTemplateSms({
templateKey: 'sessionCancelled',
receiver,
userId,
subject: 'لغو جلسه',
body: `جلسه «${data.topic || data.className || 'کلاس'}» لغو شد.`,
relatedEvent: 'session.cancelled',
slotValues: {
fullName: data.fullName || 'کارآموز',
className: data.className || '-',
topic: data.topic || '-',
sessionDate: data.sessionDate || '',
reason: data.reason || '-',
classCode: data.classCode || ''
}
});
};
const sendSessionHoldingSms = async (receiver, payload, userId = null) => {
const data = payload || {};
return sendTemplateSms({
templateKey: 'sessionHolding',
receiver,
userId,
subject: 'برگزاری جلسه طبق برنامه',
body: `جلسه «${data.topic || data.className || 'کلاس'}» کلاس ${data.className || ''} در تاریخ ${data.sessionDate || '-'} و ساعت ${data.classTime || data.time || '-'} طبق برنامه برگزار خواهد شد.`,
relatedEvent: 'session.holding',
slotValues: {
fullName: data.fullName || 'کارآموز',
topic: data.topic || '-',
className: data.className || '-',
sessionDate: data.sessionDate || '',
classTime: data.classTime || data.time || '',
courseName: data.courseName || data.className || '-',
classCode: data.classCode || '',
place: data.place || '-'
}
});
};
const sendClassRequestApprovedSms = async (receiver, payload, userId = null) => {
const data = payload || {};
return sendTemplateSms({
templateKey: 'classRequestApproved',
receiver,
userId,
subject: 'تأیید درخواست تشکیل کلاس',
body: `درخواست تشکیل کلاس «${data.courseName || '-'}» تأیید شد.`,
relatedEvent: 'class_request.approved',
slotValues: {
fullName: data.fullName || 'کارآموز',
courseName: data.courseName || '-',
classCode: data.classCode || ''
}
});
};
const sendClassRequestRejectedSms = async (receiver, payload, userId = null) => {
const data = payload || {};
return sendTemplateSms({
templateKey: 'classRequestRejected',
receiver,
userId,
subject: 'رد درخواست ثبت‌نام',
body: `درخواست شما برای «${data.courseName || '-'}» رد شد.`,
relatedEvent: 'class_request.rejected',
slotValues: {
fullName: data.fullName || 'کارآموز',
courseName: data.courseName || '-',
reason: data.reason || '-',
registrationCode: data.registrationCode || ''
}
});
};
const sendPendingRegistrationSms = async (receiver, payload, userId = null) => {
const data = payload || {};
return sendTemplateSms({
templateKey: 'pendingRegistration',
receiver,
userId,
subject: 'دریافت درخواست ثبت‌نام',
body: `درخواست ثبت‌نام شما در «${data.className || '-'}» دریافت شد.`,
relatedEvent: 'pending.registration_received',
slotValues: {
fullName: data.fullName || 'کارآموز',
className: data.className || '-',
registrationCode: data.registrationCode || ''
}
});
};
const sendCertificateIssuedSms = async (receiver, payload, userId = null) => {
const data = payload || {};
return sendTemplateSms({
templateKey: 'certificateIssued',
receiver,
userId,
subject: 'صدور گواهینامه',
body: `گواهینامه «${data.certificateTitle || '-'}» برای شما صادر شد.`,
relatedEvent: 'certificate.issued',
slotValues: {
fullName: data.fullName || 'کارآموز',
certificateTitle: data.certificateTitle || '-',
certificateCode: data.certificateCode || '',
courseName: data.courseName || '-'
}
});
};
const sendClassPlanProfessorSms = async (receiver, payload, userId = null) => {
const data = payload || {};
return sendTemplateSms({
templateKey: 'classPlanProfessor',
receiver,
userId,
subject: 'برنامه کلاس به استاد',
body: `استاد ${data.professorName || ''}، برنامه کلاس ${data.className || ''}: ${data.classDays || ''}، ${data.classTimes || ''} از ${data.classStartDate || ''} الی ${data.classEndDate || ''}`,
relatedEvent: 'class.plan_to_professor',
slotValues: {
professorName: data.professorName || 'استاد',
className: data.className || '-',
classDays: data.classDays || '',
classTimes: data.classTimes || '',
classStartDate: data.classStartDate || '',
classEndDate: data.classEndDate || '',
phoneNumber: receiver
}
});
};
module.exports = {
sendAccountCreatedSms,
sendPasswordResetSms,
sendClassRegisteredSms,
sendClassReminderSms,
sendInvoiceCreatedSms,
sendPaymentStatusChangedSms,
sendPaymentReminderSms,
sendTransactionRecordedSms,
sendSessionCancelledSms,
sendSessionHoldingSms,
sendClassRequestApprovedSms,
sendClassRequestRejectedSms,
sendPendingRegistrationSms,
sendCertificateIssuedSms,
sendClassPlanProfessorSms
};