Add sessionHolding template, unique 8-digit codes, reason slot for sessionCancelled, and disable unused notifications
This commit is contained in:
@@ -9,12 +9,13 @@ const bcrypt = require('bcryptjs');
|
||||
const { calculateMeta, escapeRegex, getSearchTerm } = require('../../utils/pagination');
|
||||
const { calculateRegistrationPricing } = require('../../utils/registrationPricing');
|
||||
const { generateUsername, generateSimplePassword } = require('../../utils/credentials');
|
||||
const { sendAccountCreatedSms } = require('../../utils/senders/smsMessages');
|
||||
const { sendAccountCreatedSms, sendClassRequestApprovedSms, sendClassRequestRejectedSms, sendPendingRegistrationSms } = require('../../utils/senders/smsMessages');
|
||||
const { notifyAction } = require('../../utils/actionNotify');
|
||||
const { resolveNotifyFlags } = require('../../utils/notifyResolver');
|
||||
const { mergeFullName, normalizeGender } = require('../../utils/userProfile');
|
||||
const classService = require('../classes/classService');
|
||||
const paymentService = require('../payments/paymentService');
|
||||
const logger = require('../../utils/logger');
|
||||
const { recordAndSend } = require('../../utils/senders/notificationRecorder');
|
||||
|
||||
const POPULATE_LIST = [
|
||||
{ path: 'user', select: 'name phoneNumber nationalIdCode email username' },
|
||||
@@ -148,7 +149,18 @@ const findOrCreateUser = async (body) => {
|
||||
});
|
||||
|
||||
try {
|
||||
await sendAccountCreatedSms(normalizedPhone, username, plainPassword, user._id);
|
||||
const notify = await resolveNotifyFlags({}, 'accountCreated');
|
||||
if (notify.sms || notify.email || notify.bot) {
|
||||
await notifyAction({
|
||||
actionKey: 'accountCreated',
|
||||
userId: user._id,
|
||||
phoneNumber: normalizedPhone,
|
||||
email,
|
||||
subject: 'ایجاد حساب کاربری',
|
||||
body: `حساب کاربری شما ایجاد شد. نام کاربری: ${username} — کد کاربری: ${user.uniqueCode || ''}`,
|
||||
smsHandler: () => sendAccountCreatedSms(normalizedPhone, username, plainPassword, user._id, user.uniqueCode)
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[findOrCreateUser] Account SMS failed for ${normalizedPhone}: ${err.message}`);
|
||||
}
|
||||
@@ -214,6 +226,27 @@ const createPendingStudentAfterPayment = async (body) => {
|
||||
paymentReference: paymentReference || undefined
|
||||
});
|
||||
|
||||
try {
|
||||
const notify = await resolveNotifyFlags({}, 'pendingRegistration');
|
||||
if (notify.sms || notify.email || notify.bot) {
|
||||
await notifyAction({
|
||||
actionKey: 'pendingRegistration',
|
||||
userId: user._id,
|
||||
phoneNumber: user.phoneNumber,
|
||||
email: user.email,
|
||||
subject: 'دریافت درخواست ثبتنام',
|
||||
body: `درخواست ثبتنام شما در «${cls.name}» دریافت شد. کد درخواست: ${pending.uniqueCode || ''}`,
|
||||
smsHandler: () => sendPendingRegistrationSms(user.phoneNumber, {
|
||||
fullName: user.name || '',
|
||||
className: cls.name || '',
|
||||
registrationCode: pending.uniqueCode || ''
|
||||
}, user._id)
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[createPendingStudentAfterPayment] Notification failed: ${err.message}`);
|
||||
}
|
||||
|
||||
return PendingStudent.findById(pending._id).populate(POPULATE_LIST).lean();
|
||||
};
|
||||
|
||||
@@ -262,26 +295,27 @@ const getPendingStudentById = async (id) => {
|
||||
return pending;
|
||||
};
|
||||
|
||||
const sendClassRequestApprovedSms = async (user, courseTitle) => {
|
||||
const sendClassRequestApprovedNotification = async (user, courseTitle, classCode = '') => {
|
||||
if (!user?.phoneNumber) return;
|
||||
const body = `درخواست تشکیل کلاس جدید برای «${courseTitle}» تأیید شد. بهزودی با شما هماهنگ میکنیم.`;
|
||||
await recordAndSend({
|
||||
await notifyAction({
|
||||
actionKey: 'classRequestApproved',
|
||||
userId: user._id,
|
||||
channel: 'sms',
|
||||
subject: 'تأیید درخواست کلاس',
|
||||
body,
|
||||
relatedEvent: 'class_request.approved',
|
||||
sendFn: async () => {
|
||||
logger.info(`[class_request.approved] SMS queued for ${user.phoneNumber}: ${body}`);
|
||||
return { skipped: true, reason: 'template_pending' };
|
||||
}
|
||||
phoneNumber: user.phoneNumber,
|
||||
email: user.email,
|
||||
subject: 'تأیید درخواست تشکیل کلاس',
|
||||
body: `درخواست تشکیل کلاس «${courseTitle}» تأیید شد.`,
|
||||
smsHandler: () => sendClassRequestApprovedSms(user.phoneNumber, {
|
||||
fullName: user.name || '',
|
||||
courseName: courseTitle,
|
||||
classCode
|
||||
}, user._id)
|
||||
});
|
||||
};
|
||||
|
||||
const approvePendingStudent = async (id, actorId, body = {}) => {
|
||||
const pending = await PendingStudent.findById(id)
|
||||
.populate({ path: 'user', select: 'name phoneNumber' })
|
||||
.populate({ path: 'class', select: 'name course', populate: { path: 'course', select: 'title' } })
|
||||
.populate({ path: 'class', select: 'name course uniqueCode', populate: { path: 'course', select: 'title' } })
|
||||
.populate({ path: 'course', select: 'title' });
|
||||
|
||||
if (!pending) throw new AppError('NOT_FOUND', null, 'درخواست در انتظار یافت نشد.');
|
||||
@@ -321,7 +355,11 @@ const approvePendingStudent = async (id, actorId, body = {}) => {
|
||||
}, actorId);
|
||||
} else {
|
||||
const courseTitle = pending.course?.title || pending.class?.course?.title || pending.class?.name || 'دوره';
|
||||
await sendClassRequestApprovedSms(pending.user, courseTitle);
|
||||
await sendClassRequestApprovedNotification(
|
||||
pending.user,
|
||||
courseTitle,
|
||||
pending.class?.uniqueCode || ''
|
||||
);
|
||||
}
|
||||
|
||||
await PendingStudent.findByIdAndDelete(id);
|
||||
@@ -330,7 +368,10 @@ const approvePendingStudent = async (id, actorId, body = {}) => {
|
||||
};
|
||||
|
||||
const rejectPendingStudent = async (id, actorId, body = {}) => {
|
||||
const pending = await PendingStudent.findById(id);
|
||||
const pending = await PendingStudent.findById(id)
|
||||
.populate({ path: 'user', select: 'name phoneNumber email' })
|
||||
.populate({ path: 'class', select: 'name', populate: { path: 'course', select: 'title' } })
|
||||
.populate({ path: 'course', select: 'title' });
|
||||
if (!pending) throw new AppError('NOT_FOUND', null, 'درخواست در انتظار یافت نشد.');
|
||||
if (!['pending_review', 'pending_payment'].includes(pending.status)) {
|
||||
throw new AppError('VALIDATION_FAILED', null, 'این درخواست قابل رد کردن نیست.');
|
||||
@@ -340,6 +381,26 @@ const rejectPendingStudent = async (id, actorId, body = {}) => {
|
||||
logger.info(`[rejectPendingStudent] ${id} by ${actorId}: ${String(body.adminNotes).trim()}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const courseTitle = pending.course?.title || pending.class?.course?.title || pending.class?.name || 'دوره';
|
||||
await notifyAction({
|
||||
actionKey: 'classRequestRejected',
|
||||
userId: pending.user?._id,
|
||||
phoneNumber: pending.user?.phoneNumber,
|
||||
email: pending.user?.email,
|
||||
subject: 'رد درخواست ثبتنام',
|
||||
body: `درخواست شما برای «${courseTitle}» رد شد.`,
|
||||
smsHandler: () => sendClassRequestRejectedSms(pending.user.phoneNumber, {
|
||||
fullName: pending.user?.name || '',
|
||||
courseName: courseTitle,
|
||||
reason: body.adminNotes ? String(body.adminNotes).trim() : '—',
|
||||
registrationCode: pending.uniqueCode || ''
|
||||
}, pending.user?._id)
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`[rejectPendingStudent] Notification failed: ${err.message}`);
|
||||
}
|
||||
|
||||
await PendingStudent.findByIdAndDelete(id);
|
||||
return { rejected: true };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user