// /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 };