Add sessionHolding template, unique 8-digit codes, reason slot for sessionCancelled, and disable unused notifications

This commit is contained in:
2026-08-21 10:44:51 +03:30
parent 5328f36f06
commit 091e519280
28 changed files with 2180 additions and 280 deletions
+53 -8
View File
@@ -2,9 +2,51 @@
const cron = require('node-cron');
const Payment = require('../components/payments/paymentModel');
const User = require('../components/users/userModel');
const Course = require('../components/courses/courseModel');
const eventEmitter = require('../events/eventEmitter');
const EVENT_NAMES = require('../constants/eventNames');
const logger = require('../utils/logger');
const { getPayableAmount } = require('../utils/paymentAmount');
const { notifyAction } = require('../utils/actionNotify');
const { sendPaymentReminderSms } = require('../utils/senders/smsMessages');
const { resolveNotifyFlags } = require('../utils/notifyResolver');
const formatDueDate = (date) => {
if (!date) return '';
const parsed = new Date(date);
if (Number.isNaN(parsed.getTime())) return '';
return parsed.toLocaleDateString('fa-IR');
};
const notifyPaymentReminder = async (payment, user) => {
const notify = await resolveNotifyFlags({}, 'paymentReminder');
if (!notify.sms && !notify.email && !notify.bot) return;
let courseName = '-';
if (payment.course) {
const course = await Course.findById(payment.course).select('title').lean();
if (course?.title) courseName = course.title;
}
const amountDue = getPayableAmount(payment) - (payment.paidAmount || 0);
await notifyAction({
actionKey: 'paymentReminder',
userId: user._id,
phoneNumber: user.phoneNumber,
email: user.email,
subject: 'یادآوری سررسید پرداخت',
body: `یادآوری: مبلغ ${amountDue} تومان تا ${formatDueDate(payment.dueDate)} سررسید دارد. کد صورتحساب: ${payment.uniqueCode || ''}`,
smsHandler: () => sendPaymentReminderSms(user.phoneNumber, {
fullName: user.name || '',
amount: amountDue,
dueDate: formatDueDate(payment.dueDate),
invoiceCode: payment.uniqueCode || '',
course: courseName
}, user._id)
});
};
const runPaymentReminderJob = async () => {
try {
@@ -12,37 +54,41 @@ const runPaymentReminderJob = async () => {
const threeDaysFromNow = new Date();
threeDaysFromNow.setDate(now.getDate() + 3);
// Find pending/partiallyPaid payments due in the next 3 days
const upcomingPayments = await Payment.find({
status: { $in: ['pending', 'partiallyPaid'] },
status: { $in: ['pending', 'partial'] },
dueDate: { $gte: now, $lte: threeDaysFromNow }
}).populate('user');
}).populate({ path: 'user', select: 'name phoneNumber email' });
for (const payment of upcomingPayments) {
if (payment.user) {
eventEmitter.emit(EVENT_NAMES.PAYMENT_REMINDER_DUE, {
paymentId: payment._id,
userId: payment.user._id,
amountDue: payment.amount - payment.amountPaid,
amountDue: getPayableAmount(payment) - (payment.paidAmount || 0),
dueDate: payment.dueDate
});
try {
await notifyPaymentReminder(payment, payment.user);
} catch (err) {
logger.error(`[PaymentReminderJob] Notification failed for payment ${payment._id}: ${err.message}`);
}
}
}
// Find overdue payments and update status to overdue
const overduePayments = await Payment.find({
status: { $in: ['pending', 'partiallyPaid'] },
status: { $in: ['pending', 'partial'] },
dueDate: { $lt: now }
});
for (const payment of overduePayments) {
const previousStatus = payment.status;
payment.status = 'overdue';
await payment.save();
if (payment.user) {
eventEmitter.emit(EVENT_NAMES.PAYMENT_STATUS_CHANGED, {
paymentId: payment._id,
userId: payment.user,
oldStatus: 'pending',
oldStatus: previousStatus,
newStatus: 'overdue'
});
}
@@ -53,7 +99,6 @@ const runPaymentReminderJob = async () => {
};
const startPaymentReminderJob = () => {
// Run daily at midnight
cron.schedule('0 0 * * *', async () => {
await runPaymentReminderJob();
});