'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: '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 };