Allow enabling/disabling individual SMS template types from dashboard with sync to notification settings
This commit is contained in:
@@ -49,13 +49,19 @@ const resolveTemplateId = (entry, def) => {
|
||||
return envFallbackFor(def);
|
||||
};
|
||||
|
||||
const toPublicTemplates = (storedMap) => {
|
||||
const toPublicTemplates = (storedMap, notificationMap = {}) => {
|
||||
return SMS_TEMPLATE_DEFS.map((def) => {
|
||||
const entry = normalizeStoredEntry(storedMap[def.key], def);
|
||||
const variables = resolveVariablesList(def, entry.variables);
|
||||
const notifyAction = notificationMap[def.key];
|
||||
const isEnabled = notifyAction && notifyAction.sms !== undefined
|
||||
? Boolean(notifyAction.sms)
|
||||
: entry.enabled;
|
||||
|
||||
return {
|
||||
key: def.key,
|
||||
label: def.label,
|
||||
enabled: isEnabled,
|
||||
templateId: resolveTemplateId(entry, def),
|
||||
availableSlots: (def.slots || []).map((slot) => ({
|
||||
slot: slot.key,
|
||||
@@ -98,8 +104,9 @@ const getSettings = async () => {
|
||||
}
|
||||
|
||||
const storedMap = readStoredMap(doc);
|
||||
const notificationMap = readNotificationStoredMap(doc);
|
||||
return {
|
||||
smsTemplates: toPublicTemplates(storedMap),
|
||||
smsTemplates: toPublicTemplates(storedMap, notificationMap),
|
||||
messaging: getPublicMessaging(doc),
|
||||
notificationSettings: await getPublicNotificationSettings(doc)
|
||||
};
|
||||
@@ -108,11 +115,18 @@ const getSettings = async () => {
|
||||
const getSmsTemplate = async (key) => {
|
||||
const def = SMS_TEMPLATE_DEFS.find((item) => item.key === key);
|
||||
if (!def) {
|
||||
return { templateId: '', variables: [] };
|
||||
return { templateId: '', variables: [], enabled: true };
|
||||
}
|
||||
const doc = await Setting.findOne({ key: SETTINGS_KEY }).lean();
|
||||
const entry = normalizeStoredEntry(readStoredMap(doc)[key], def);
|
||||
const notificationStored = readNotificationStoredMap(doc);
|
||||
const notifyAction = notificationStored[key];
|
||||
const isEnabled = notifyAction && notifyAction.sms !== undefined
|
||||
? Boolean(notifyAction.sms)
|
||||
: entry.enabled;
|
||||
|
||||
return {
|
||||
enabled: isEnabled,
|
||||
templateId: resolveTemplateId(entry, def),
|
||||
variables: resolveVariablesList(def, entry.variables)
|
||||
};
|
||||
@@ -126,10 +140,11 @@ const getSmsTemplateId = async (key) => {
|
||||
const parseIncomingEntry = (raw) => {
|
||||
if (raw == null) return null;
|
||||
if (typeof raw === 'string' || typeof raw === 'number') {
|
||||
return { templateId: raw, variables: undefined };
|
||||
return { templateId: raw, variables: undefined, enabled: undefined };
|
||||
}
|
||||
if (typeof raw !== 'object') return null;
|
||||
return {
|
||||
enabled: raw.enabled !== undefined ? Boolean(raw.enabled) : undefined,
|
||||
templateId: raw.templateId,
|
||||
variables: raw.variables
|
||||
};
|
||||
@@ -172,6 +187,23 @@ const saveSettings = async (body = {}) => {
|
||||
|
||||
doc.set('smsTemplates', JSON.parse(JSON.stringify(nextMap)));
|
||||
doc.markModified('smsTemplates');
|
||||
|
||||
// Synchronize smsEnabled to notificationSettings
|
||||
const storedNotificationMap = existing
|
||||
? readNotificationStoredMap(existing.toObject ? existing.toObject() : existing)
|
||||
: emptyNotificationSettingsMap();
|
||||
const updatedNotificationMap = { ...storedNotificationMap };
|
||||
for (const def of SMS_TEMPLATE_DEFS) {
|
||||
if (nextMap[def.key] && nextMap[def.key].enabled !== undefined) {
|
||||
const currentAction = updatedNotificationMap[def.key] || { sms: true, email: false, bot: false };
|
||||
updatedNotificationMap[def.key] = {
|
||||
...currentAction,
|
||||
sms: Boolean(nextMap[def.key].enabled)
|
||||
};
|
||||
}
|
||||
}
|
||||
doc.set('notificationSettings', JSON.parse(JSON.stringify(mergeNotificationSettings(updatedNotificationMap))));
|
||||
doc.markModified('notificationSettings');
|
||||
} else if (!existing) {
|
||||
doc.set('smsTemplates', emptyTemplateMap());
|
||||
}
|
||||
@@ -191,7 +223,24 @@ const saveSettings = async (body = {}) => {
|
||||
const nextNotificationMap = mergeNotificationSettings(storedNotificationMap, incomingNotificationSettings);
|
||||
doc.set('notificationSettings', JSON.parse(JSON.stringify(nextNotificationMap)));
|
||||
doc.markModified('notificationSettings');
|
||||
} else if (!existing) {
|
||||
|
||||
// Synchronize enabled flag in smsTemplates
|
||||
const storedSmsMap = existing
|
||||
? readStoredMap(existing.toObject ? existing.toObject() : existing)
|
||||
: emptyTemplateMap();
|
||||
const updatedSmsMap = { ...storedSmsMap };
|
||||
for (const def of SMS_TEMPLATE_DEFS) {
|
||||
if (nextNotificationMap[def.key] && nextNotificationMap[def.key].sms !== undefined) {
|
||||
const currentTemplate = normalizeStoredEntry(updatedSmsMap[def.key], def);
|
||||
updatedSmsMap[def.key] = {
|
||||
...currentTemplate,
|
||||
enabled: Boolean(nextNotificationMap[def.key].sms)
|
||||
};
|
||||
}
|
||||
}
|
||||
doc.set('smsTemplates', JSON.parse(JSON.stringify(updatedSmsMap)));
|
||||
doc.markModified('smsTemplates');
|
||||
} else if (!existing && !hasTemplatePayload) {
|
||||
doc.set('notificationSettings', emptyNotificationSettingsMap());
|
||||
}
|
||||
|
||||
@@ -200,8 +249,9 @@ const saveSettings = async (body = {}) => {
|
||||
invalidateNotificationSettingsCache();
|
||||
|
||||
const saved = await Setting.findOne({ key: SETTINGS_KEY }).lean();
|
||||
const finalNotificationMap = readNotificationStoredMap(saved);
|
||||
return {
|
||||
smsTemplates: toPublicTemplates(readStoredMap(saved)),
|
||||
smsTemplates: toPublicTemplates(readStoredMap(saved), finalNotificationMap),
|
||||
messaging: getPublicMessaging(saved),
|
||||
notificationSettings: await getPublicNotificationSettings(saved)
|
||||
};
|
||||
|
||||
@@ -148,6 +148,7 @@ const SMS_TEMPLATE_DEFS = [
|
||||
key: 'classRequestApproved',
|
||||
label: 'تأیید درخواست تشکیل کلاس',
|
||||
envKey: 'SMS_TEMPLATE_CLASS_REQUEST_APPROVED',
|
||||
defaultEnabled: false,
|
||||
slots: [
|
||||
{ key: 'fullName', label: 'نام کارآموز', defaultName: 'fullName' },
|
||||
{ key: 'courseName', label: 'نام دوره', defaultName: 'courseName' },
|
||||
@@ -159,6 +160,7 @@ const SMS_TEMPLATE_DEFS = [
|
||||
key: 'classRequestRejected',
|
||||
label: 'رد درخواست ثبتنام',
|
||||
envKey: 'SMS_TEMPLATE_CLASS_REQUEST_REJECTED',
|
||||
defaultEnabled: false,
|
||||
slots: [
|
||||
{ key: 'fullName', label: 'نام کارآموز', defaultName: 'fullName' },
|
||||
{ key: 'courseName', label: 'نام دوره', defaultName: 'courseName' },
|
||||
@@ -171,6 +173,7 @@ const SMS_TEMPLATE_DEFS = [
|
||||
key: 'pendingRegistration',
|
||||
label: 'دریافت درخواست ثبتنام',
|
||||
envKey: 'SMS_TEMPLATE_PENDING_REGISTRATION',
|
||||
defaultEnabled: false,
|
||||
slots: [
|
||||
{ key: 'fullName', label: 'نام کارآموز', defaultName: 'fullName' },
|
||||
{ key: 'className', label: 'نام کلاس', defaultName: 'className' },
|
||||
@@ -225,6 +228,7 @@ const defaultVariablesList = (def) => {
|
||||
};
|
||||
|
||||
const emptyTemplateEntry = (def) => ({
|
||||
enabled: def ? (def.defaultEnabled !== false) : true,
|
||||
templateId: '',
|
||||
variables: defaultVariablesList(def)
|
||||
});
|
||||
@@ -266,20 +270,24 @@ const normalizeVariablesInput = (rawVariables, def) => {
|
||||
};
|
||||
|
||||
const normalizeStoredEntry = (raw, def) => {
|
||||
const defaultEnabled = def ? (def.defaultEnabled !== false) : true;
|
||||
if (raw == null || raw === '') {
|
||||
return {
|
||||
enabled: defaultEnabled,
|
||||
templateId: '',
|
||||
variables: def ? defaultVariablesList(def) : []
|
||||
};
|
||||
}
|
||||
if (typeof raw === 'string' || typeof raw === 'number') {
|
||||
return {
|
||||
enabled: true,
|
||||
templateId: String(raw),
|
||||
variables: def ? defaultVariablesList(def) : []
|
||||
};
|
||||
}
|
||||
if (typeof raw !== 'object') {
|
||||
return {
|
||||
enabled: defaultEnabled,
|
||||
templateId: '',
|
||||
variables: def ? defaultVariablesList(def) : []
|
||||
};
|
||||
@@ -289,7 +297,12 @@ const normalizeStoredEntry = (raw, def) => {
|
||||
? normalizeVariablesInput(raw.variables, def)
|
||||
: (def ? defaultVariablesList(def) : []);
|
||||
|
||||
const enabled = raw.enabled !== undefined
|
||||
? Boolean(raw.enabled)
|
||||
: defaultEnabled;
|
||||
|
||||
return {
|
||||
enabled,
|
||||
templateId: raw.templateId != null ? String(raw.templateId) : '',
|
||||
variables
|
||||
};
|
||||
@@ -304,8 +317,13 @@ const mergeTemplateEntry = (def, storedRaw, incomingRaw = null) => {
|
||||
const storedEntry = normalizeStoredEntry(storedRaw, def);
|
||||
let templateId = storedEntry.templateId || envFallbackFor(def);
|
||||
let variables = storedEntry.variables;
|
||||
let enabled = storedEntry.enabled;
|
||||
|
||||
if (incomingRaw) {
|
||||
if (incomingRaw.enabled !== undefined) {
|
||||
enabled = Boolean(incomingRaw.enabled);
|
||||
}
|
||||
|
||||
if (incomingRaw.templateId !== undefined) {
|
||||
const sanitized = sanitizeTemplateId(incomingRaw.templateId);
|
||||
if (sanitized === null) {
|
||||
@@ -336,6 +354,7 @@ const mergeTemplateEntry = (def, storedRaw, incomingRaw = null) => {
|
||||
}
|
||||
|
||||
return {
|
||||
enabled,
|
||||
templateId,
|
||||
variables: resolveVariablesList(def, variables)
|
||||
};
|
||||
|
||||
@@ -92,4 +92,27 @@ describe('SMS template variables', () => {
|
||||
assert.ok(slots.includes(slot), `sessionHolding is missing slot ${slot}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('normalizes and merges enabled property for SMS templates', () => {
|
||||
const stored = normalizeStoredEntry({
|
||||
enabled: false,
|
||||
templateId: '123',
|
||||
variables: threeInvoiceVars
|
||||
}, invoiceDef);
|
||||
assert.equal(stored.enabled, false);
|
||||
|
||||
const mergedToTrue = mergeTemplateEntry(
|
||||
invoiceDef,
|
||||
stored,
|
||||
{ enabled: true }
|
||||
);
|
||||
assert.equal(mergedToTrue.enabled, true);
|
||||
|
||||
const mergedToFalse = mergeTemplateEntry(
|
||||
invoiceDef,
|
||||
mergedToTrue,
|
||||
{ enabled: false }
|
||||
);
|
||||
assert.equal(mergedToFalse.enabled, false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user