feat: let SuperAdmin rename SMS template variable names

Store custom sms.ir parameter names per template slot so outbound messages match the panel placeholders.
This commit is contained in:
2026-08-15 18:43:24 +03:30
parent 3160f07873
commit 7ebf9409f0
3 changed files with 203 additions and 52 deletions
+92 -9
View File
@@ -4,26 +4,37 @@ const SMS_TEMPLATE_DEFS = [
{
key: 'accountCreated',
label: 'ایجاد حساب کاربری',
variables: ['user', 'password'],
envKey: 'SMS_TEMPLATE_ACCOUNT_CREATED'
envKey: 'SMS_TEMPLATE_ACCOUNT_CREATED',
slots: [
{ key: 'username', label: 'نام کاربری', defaultName: 'user' },
{ key: 'password', label: 'رمز عبور', defaultName: 'password' }
]
},
{
key: 'classRegistered',
label: 'ثبت‌نام در کلاس',
variables: ['className'],
envKey: 'SMS_TEMPLATE_CLASS_REGISTERED'
envKey: 'SMS_TEMPLATE_CLASS_REGISTERED',
slots: [
{ key: 'className', label: 'نام کلاس', defaultName: 'className' }
]
},
{
key: 'classReminder',
label: 'یادآوری کلاس',
variables: ['className', 'time', 'place'],
envKey: 'SMS_TEMPLATE_CLASS_REMINDER'
envKey: 'SMS_TEMPLATE_CLASS_REMINDER',
slots: [
{ key: 'className', label: 'نام کلاس', defaultName: 'className' },
{ key: 'time', label: 'ساعت', defaultName: 'time' },
{ key: 'place', label: 'مکان', defaultName: 'place' }
]
},
{
key: 'invoiceCreated',
label: 'ایجاد صورتحساب',
variables: ['amount'],
envKey: 'SMS_TEMPLATE_INVOICE_CREATED'
envKey: 'SMS_TEMPLATE_INVOICE_CREATED',
slots: [
{ key: 'amount', label: 'مبلغ', defaultName: 'amount' }
]
}
];
@@ -42,9 +53,81 @@ const sanitizeTemplateId = (value) => {
return digits;
};
const sanitizeVariableName = (value) => {
if (value == null) return '';
const name = String(value).trim().replace(/^#+|#+$/g, '');
if (!name) return '';
if (!/^[A-Za-z][A-Za-z0-9_]{0,49}$/.test(name)) return null;
return name;
};
const defaultVariableMap = (def) => {
const map = {};
(def.slots || []).forEach((slot) => {
map[slot.key] = slot.defaultName;
});
return map;
};
const emptyTemplateEntry = (def) => ({
templateId: '',
variables: defaultVariableMap(def)
});
const normalizeStoredEntry = (raw) => {
if (raw == null || raw === '') {
return { templateId: '', variables: {} };
}
if (typeof raw === 'string' || typeof raw === 'number') {
return { templateId: String(raw), variables: {} };
}
if (typeof raw !== 'object') {
return { templateId: '', variables: {} };
}
let variables = {};
if (raw.variables && typeof raw.variables === 'object' && !Array.isArray(raw.variables)) {
variables = { ...raw.variables };
} else if (Array.isArray(raw.variables)) {
raw.variables.forEach((item) => {
if (item?.slot) variables[item.slot] = item.name;
});
}
return {
templateId: raw.templateId != null ? String(raw.templateId) : '',
variables
};
};
const resolveVariableMap = (def, storedVariables = {}) => {
const map = {};
(def.slots || []).forEach((slot) => {
const sanitized = sanitizeVariableName(storedVariables[slot.key]);
map[slot.key] = sanitized || slot.defaultName;
});
return map;
};
const buildSmsParameters = (variableMap, valuesBySlot) => {
return Object.entries(valuesBySlot)
.map(([slot, value]) => {
const name = String(variableMap?.[slot] || '').trim();
if (!name) return null;
return { name, value: String(value ?? '') };
})
.filter(Boolean);
};
module.exports = {
SMS_TEMPLATE_DEFS,
SMS_TEMPLATE_KEYS,
envFallbackFor,
sanitizeTemplateId
sanitizeTemplateId,
sanitizeVariableName,
defaultVariableMap,
emptyTemplateEntry,
normalizeStoredEntry,
resolveVariableMap,
buildSmsParameters
};