261 lines
9.2 KiB
JavaScript
261 lines
9.2 KiB
JavaScript
'use strict';
|
|
|
|
const { CHANNELS } = require('../../utils/messagingChannels');
|
|
|
|
const NOTIFICATION_CATEGORIES = [
|
|
{ key: 'users', label: 'کاربران' },
|
|
{ key: 'classes', label: 'کلاسها و جلسات' },
|
|
{ key: 'payments', label: 'پرداختها' },
|
|
{ key: 'registrations', label: 'ثبتنام و درخواستها' },
|
|
{ key: 'certificates', label: 'گواهینامهها' }
|
|
];
|
|
|
|
const NOTIFICATION_ACTION_DEFS = [
|
|
{
|
|
key: 'accountCreated',
|
|
label: 'ایجاد حساب کاربری',
|
|
description: 'ارسال نام کاربری و رمز عبور پس از ایجاد حساب',
|
|
category: 'users',
|
|
relatedEvent: 'user.created',
|
|
smsTemplateKey: 'accountCreated',
|
|
defaultChannels: { sms: true, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'passwordReset',
|
|
label: 'بازنشانی رمز عبور',
|
|
description: 'ارسال رمز عبور جدید پس از بازنشانی توسط مدیر',
|
|
category: 'users',
|
|
relatedEvent: 'user.password_reset',
|
|
smsTemplateKey: 'passwordReset',
|
|
defaultChannels: { sms: true, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'classRegistered',
|
|
label: 'ثبتنام در کلاس',
|
|
description: 'اطلاعرسانی پس از ثبتنام کارآموز در کلاس',
|
|
category: 'classes',
|
|
relatedEvent: 'user.enrolled',
|
|
smsTemplateKey: 'classRegistered',
|
|
defaultChannels: { sms: true, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'classReminder',
|
|
label: 'یادآوری کلاس',
|
|
description: 'یادآوری خودکار حدود ۳۰ دقیقه قبل از شروع جلسه',
|
|
category: 'classes',
|
|
relatedEvent: 'session.reminder',
|
|
smsTemplateKey: 'classReminder',
|
|
defaultChannels: { sms: true, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'sessionCancelled',
|
|
label: 'لغو جلسه',
|
|
description: 'اطلاعرسانی لغو جلسه به کارآموزان ثبتنامشده',
|
|
category: 'classes',
|
|
relatedEvent: 'session.cancelled',
|
|
smsTemplateKey: 'sessionCancelled',
|
|
defaultChannels: { sms: true, email: true, bot: false }
|
|
},
|
|
{
|
|
key: 'sessionHolding',
|
|
label: 'برگزاری جلسه طبق برنامه',
|
|
description: 'اطلاعرسانی برگزاری جلسه طبق برنامه به کارآموزان',
|
|
category: 'classes',
|
|
relatedEvent: 'session.holding',
|
|
smsTemplateKey: 'sessionHolding',
|
|
defaultChannels: { sms: true, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'classPlanProfessor',
|
|
label: 'ارسال برنامه کلاس به استاد',
|
|
description: 'ارسال جزئیات و زمانبندی برگزاری کلاس به استاد',
|
|
category: 'classes',
|
|
relatedEvent: 'class.plan_to_professor',
|
|
smsTemplateKey: 'classPlanProfessor',
|
|
defaultChannels: { sms: true, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'invoiceCreated',
|
|
label: 'ایجاد صورتحساب',
|
|
description: 'اطلاعرسانی صدور صورتحساب جدید',
|
|
category: 'payments',
|
|
relatedEvent: 'payment.created',
|
|
smsTemplateKey: 'invoiceCreated',
|
|
defaultChannels: { sms: true, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'paymentStatusChanged',
|
|
label: 'تغییر وضعیت پرداخت',
|
|
description: 'اطلاعرسانی تغییر وضعیت صورتحساب (پرداختشده، معوق و …)',
|
|
category: 'payments',
|
|
relatedEvent: 'payment.status_changed',
|
|
smsTemplateKey: 'paymentStatusChanged',
|
|
defaultChannels: { sms: true, email: true, bot: false }
|
|
},
|
|
{
|
|
key: 'paymentReminder',
|
|
label: 'یادآوری سررسید پرداخت',
|
|
description: 'یادآوری خودکار پرداختهای نزدیک به سررسید',
|
|
category: 'payments',
|
|
relatedEvent: 'payment.reminder_due',
|
|
smsTemplateKey: 'paymentReminder',
|
|
defaultChannels: { sms: true, email: true, bot: false }
|
|
},
|
|
{
|
|
key: 'transactionRecorded',
|
|
label: 'ثبت تراکنش',
|
|
description: 'اطلاعرسانی ثبت یا تأیید تراکنش پرداخت',
|
|
category: 'payments',
|
|
relatedEvent: 'payment.transaction_added',
|
|
smsTemplateKey: 'transactionRecorded',
|
|
defaultChannels: { sms: true, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'pendingRegistration',
|
|
label: 'دریافت درخواست ثبتنام',
|
|
description: 'تأیید دریافت درخواست ثبتنام آنلاین توسط کارآموز',
|
|
category: 'registrations',
|
|
relatedEvent: 'pending.registration_received',
|
|
smsTemplateKey: 'pendingRegistration',
|
|
defaultChannels: { sms: false, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'classRequestApproved',
|
|
label: 'تأیید درخواست تشکیل کلاس',
|
|
description: 'اطلاعرسانی تأیید درخواست تشکیل کلاس جدید',
|
|
category: 'registrations',
|
|
relatedEvent: 'class_request.approved',
|
|
smsTemplateKey: 'classRequestApproved',
|
|
defaultChannels: { sms: false, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'classRequestRejected',
|
|
label: 'رد درخواست ثبتنام',
|
|
description: 'اطلاعرسانی رد درخواست ثبتنام یا تشکیل کلاس',
|
|
category: 'registrations',
|
|
relatedEvent: 'class_request.rejected',
|
|
smsTemplateKey: 'classRequestRejected',
|
|
defaultChannels: { sms: false, email: false, bot: false }
|
|
},
|
|
{
|
|
key: 'certificateIssued',
|
|
label: 'صدور گواهینامه',
|
|
description: 'اطلاعرسانی بارگذاری یا صدور گواهینامه',
|
|
category: 'certificates',
|
|
relatedEvent: 'certificate.issued',
|
|
smsTemplateKey: 'certificateIssued',
|
|
defaultChannels: { sms: true, email: true, bot: false }
|
|
}
|
|
];
|
|
|
|
const NOTIFICATION_ACTION_KEYS = NOTIFICATION_ACTION_DEFS.map((def) => def.key);
|
|
|
|
const defaultActionChannels = (def) => ({
|
|
sms: def?.defaultChannels?.sms !== false,
|
|
email: def?.defaultChannels?.email === true,
|
|
bot: def?.defaultChannels?.bot === true
|
|
});
|
|
|
|
const emptyNotificationSettingsMap = () => {
|
|
const map = {};
|
|
NOTIFICATION_ACTION_DEFS.forEach((def) => {
|
|
map[def.key] = defaultActionChannels(def);
|
|
});
|
|
return map;
|
|
};
|
|
|
|
const normalizeChannelValue = (value, fallback) => {
|
|
if (value === undefined || value === null || value === '') return fallback;
|
|
if (typeof value === 'boolean') return value;
|
|
if (typeof value === 'number') return value !== 0;
|
|
const normalized = String(value).trim().toLowerCase();
|
|
if (['false', '0', 'no', 'off'].includes(normalized)) return false;
|
|
if (['true', '1', 'yes', 'on'].includes(normalized)) return true;
|
|
return fallback;
|
|
};
|
|
|
|
const normalizeStoredActionEntry = (raw, def) => {
|
|
const defaults = defaultActionChannels(def);
|
|
if (raw == null || typeof raw !== 'object') {
|
|
return { ...defaults };
|
|
}
|
|
return {
|
|
sms: normalizeChannelValue(raw.sms, defaults.sms),
|
|
email: normalizeChannelValue(raw.email, defaults.email),
|
|
bot: normalizeChannelValue(raw.bot, defaults.bot)
|
|
};
|
|
};
|
|
|
|
const mergeNotificationSettings = (storedMap = {}, incomingMap = null) => {
|
|
const next = {};
|
|
for (const def of NOTIFICATION_ACTION_DEFS) {
|
|
const stored = normalizeStoredActionEntry(storedMap[def.key], def);
|
|
const incoming = incomingMap && Object.prototype.hasOwnProperty.call(incomingMap, def.key)
|
|
? incomingMap[def.key]
|
|
: null;
|
|
|
|
if (incoming && typeof incoming === 'object') {
|
|
next[def.key] = {
|
|
sms: incoming.sms !== undefined ? normalizeChannelValue(incoming.sms, stored.sms) : stored.sms,
|
|
email: incoming.email !== undefined ? normalizeChannelValue(incoming.email, stored.email) : stored.email,
|
|
bot: incoming.bot !== undefined ? normalizeChannelValue(incoming.bot, stored.bot) : stored.bot
|
|
};
|
|
} else {
|
|
next[def.key] = stored;
|
|
}
|
|
}
|
|
return next;
|
|
};
|
|
|
|
const toPublicNotificationSettings = (storedMap = {}) => {
|
|
return NOTIFICATION_CATEGORIES.map((category) => ({
|
|
key: category.key,
|
|
label: category.label,
|
|
actions: NOTIFICATION_ACTION_DEFS
|
|
.filter((def) => def.category === category.key)
|
|
.map((def) => {
|
|
const channels = normalizeStoredActionEntry(storedMap[def.key], def);
|
|
return {
|
|
key: def.key,
|
|
label: def.label,
|
|
description: def.description,
|
|
relatedEvent: def.relatedEvent,
|
|
smsTemplateKey: def.smsTemplateKey,
|
|
channels: {
|
|
sms: channels.sms,
|
|
email: channels.email,
|
|
bot: channels.bot
|
|
}
|
|
};
|
|
})
|
|
}));
|
|
};
|
|
|
|
const parseIncomingNotificationSettings = (raw) => {
|
|
if (raw === undefined) return undefined;
|
|
if (raw == null) return emptyNotificationSettingsMap();
|
|
|
|
const incomingMap = Array.isArray(raw)
|
|
? Object.fromEntries(
|
|
raw.flatMap((group) => (group?.actions || []).map((action) => [action.key, action.channels || action]))
|
|
)
|
|
: raw;
|
|
|
|
return mergeNotificationSettings({}, incomingMap);
|
|
};
|
|
|
|
const findActionDef = (actionKey) => NOTIFICATION_ACTION_DEFS.find((def) => def.key === actionKey);
|
|
|
|
module.exports = {
|
|
CHANNELS,
|
|
NOTIFICATION_CATEGORIES,
|
|
NOTIFICATION_ACTION_DEFS,
|
|
NOTIFICATION_ACTION_KEYS,
|
|
emptyNotificationSettingsMap,
|
|
mergeNotificationSettings,
|
|
toPublicNotificationSettings,
|
|
parseIncomingNotificationSettings,
|
|
normalizeStoredActionEntry,
|
|
findActionDef
|
|
};
|