feat(sms): send class start date, days, and course time
Expose schedule fields on class and invoice templates so SMS can include Jalali start date, weekdays, and hour range.
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
|
||||
const Class = require('./classModel');
|
||||
const User = require('../users/userModel');
|
||||
const Session = require('../sessions/sessionModel');
|
||||
const AppError = require('../../utils/AppError');
|
||||
const { calculateMeta, escapeRegex, getSearchTerm } = require('../../utils/pagination');
|
||||
const { sendClassRegisteredSms } = require('../../utils/senders/smsMessages');
|
||||
const { buildClassScheduleContext } = require('../../utils/classSchedule');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
const getAll = async (query) => {
|
||||
@@ -77,12 +79,20 @@ const registerUsers = async (classId, userIds) => {
|
||||
await cls.save();
|
||||
|
||||
const classLabel = cls.name || cls.course?.title || 'کلاس';
|
||||
const sessions = await Session.find({ class: classId })
|
||||
.select('day startTime endTime')
|
||||
.sort({ day: 1 })
|
||||
.lean();
|
||||
const schedule = buildClassScheduleContext(cls, sessions);
|
||||
const users = await User.find({ _id: { $in: toAdd } }).select('phoneNumber').lean();
|
||||
await Promise.all(
|
||||
users.map(async (user) => {
|
||||
if (!user.phoneNumber) return;
|
||||
try {
|
||||
await sendClassRegisteredSms(user.phoneNumber, classLabel, user._id);
|
||||
await sendClassRegisteredSms(user.phoneNumber, classLabel, user._id, {
|
||||
courseName: cls.course?.title || classLabel,
|
||||
...schedule
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(`[registerUsers] SMS failed for ${user.phoneNumber}: ${err.message}`);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ const { calculateMeta, escapeRegex, getSearchTerm } = require('../../utils/pagin
|
||||
const User = require('../users/userModel');
|
||||
const Course = require('../courses/courseModel');
|
||||
const Class = require('../classes/classModel');
|
||||
const Session = require('../sessions/sessionModel');
|
||||
const { sendInvoiceCreatedSms } = require('../../utils/senders/smsMessages');
|
||||
const { buildClassScheduleContext } = require('../../utils/classSchedule');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
const getAllPayments = async (query) => {
|
||||
@@ -79,22 +81,34 @@ const createPayment = async (body, actorId = null) => {
|
||||
const user = await User.findById(payment.user).select('name phoneNumber').lean();
|
||||
if (user?.phoneNumber) {
|
||||
let courseName = '';
|
||||
let schedule = { classStartDate: '', classDays: '', courseTime: '' };
|
||||
if (payment.course) {
|
||||
const course = await Course.findById(payment.course).select('title').lean();
|
||||
if (course?.title) courseName = course.title;
|
||||
} else if (payment.classes && payment.classes.length > 0) {
|
||||
}
|
||||
if (payment.classes && payment.classes.length > 0) {
|
||||
const cls = await Class.findById(payment.classes[0]).populate('course', 'title').lean();
|
||||
if (cls?.course?.title) {
|
||||
courseName = cls.course.title;
|
||||
} else if (cls?.name) {
|
||||
courseName = cls.name;
|
||||
if (!courseName) {
|
||||
if (cls?.course?.title) {
|
||||
courseName = cls.course.title;
|
||||
} else if (cls?.name) {
|
||||
courseName = cls.name;
|
||||
}
|
||||
}
|
||||
if (cls) {
|
||||
const sessions = await Session.find({ class: cls._id })
|
||||
.select('day startTime endTime')
|
||||
.sort({ day: 1 })
|
||||
.lean();
|
||||
schedule = buildClassScheduleContext(cls, sessions);
|
||||
}
|
||||
}
|
||||
|
||||
await sendInvoiceCreatedSms(user.phoneNumber, {
|
||||
fullName: user.name || '',
|
||||
amount: payment.amount,
|
||||
course: courseName || '-'
|
||||
course: courseName || '-',
|
||||
...schedule
|
||||
}, user._id);
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -20,7 +20,10 @@ const SMS_TEMPLATE_DEFS = [
|
||||
{ key: 'className', label: 'نام کلاس', defaultName: 'className' },
|
||||
{ key: 'courseName', label: 'نام دوره', defaultName: 'courseName' },
|
||||
{ key: 'fullName', label: 'نام کارآموز', defaultName: 'fullName' },
|
||||
{ key: 'phoneNumber', label: 'شماره همراه', defaultName: 'mobile' }
|
||||
{ key: 'phoneNumber', label: 'شماره همراه', defaultName: 'mobile' },
|
||||
{ key: 'classStartDate', label: 'تاریخ شروع کلاس', defaultName: 'classStartDate' },
|
||||
{ key: 'classDays', label: 'روزهای برگزاری', defaultName: 'classDays' },
|
||||
{ key: 'courseTime', label: 'ساعت دوره', defaultName: 'courseTime' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -33,7 +36,10 @@ const SMS_TEMPLATE_DEFS = [
|
||||
{ key: 'time', label: 'ساعت', defaultName: 'time' },
|
||||
{ key: 'place', label: 'مکان', defaultName: 'place' },
|
||||
{ key: 'fullName', label: 'نام کاربر', defaultName: 'fullName' },
|
||||
{ key: 'phoneNumber', label: 'شماره همراه', defaultName: 'mobile' }
|
||||
{ key: 'phoneNumber', label: 'شماره همراه', defaultName: 'mobile' },
|
||||
{ key: 'classStartDate', label: 'تاریخ شروع کلاس', defaultName: 'classStartDate' },
|
||||
{ key: 'classDays', label: 'روزهای برگزاری', defaultName: 'classDays' },
|
||||
{ key: 'courseTime', label: 'ساعت دوره', defaultName: 'courseTime' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -44,7 +50,10 @@ const SMS_TEMPLATE_DEFS = [
|
||||
{ key: 'fullName', label: 'نام کارآموز', defaultName: 'FULLNAME' },
|
||||
{ key: 'amount', label: 'مبلغ', defaultName: 'PAYMENT_PRICE' },
|
||||
{ key: 'course', label: 'نام دوره', defaultName: 'COURSE' },
|
||||
{ key: 'phoneNumber', label: 'شماره همراه', defaultName: 'MOBILE' }
|
||||
{ key: 'phoneNumber', label: 'شماره همراه', defaultName: 'MOBILE' },
|
||||
{ key: 'classStartDate', label: 'تاریخ شروع کلاس', defaultName: 'classStartDate' },
|
||||
{ key: 'classDays', label: 'روزهای برگزاری', defaultName: 'classDays' },
|
||||
{ key: 'courseTime', label: 'ساعت دوره', defaultName: 'courseTime' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -58,4 +58,15 @@ describe('SMS template variables', () => {
|
||||
assert.deepEqual(merged.variables.map((item) => item.slot), ['amount', 'fullName', 'course']);
|
||||
assert.deepEqual(merged.variables.map((item) => item.name), ['PAYMENT_PRICE', 'FULLNAME', 'COURSE']);
|
||||
});
|
||||
|
||||
it('exposes class start date, class days, and course time slots', () => {
|
||||
const expected = ['classStartDate', 'classDays', 'courseTime'];
|
||||
for (const key of ['classRegistered', 'classReminder', 'invoiceCreated']) {
|
||||
const def = SMS_TEMPLATE_DEFS.find((item) => item.key === key);
|
||||
const slots = (def?.slots || []).map((slot) => slot.key);
|
||||
for (const slot of expected) {
|
||||
assert.ok(slots.includes(slot), `${key} is missing slot ${slot}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user