fix(settings): persist every SMS template variable on save

Mongoose Mixed updates were dropping extra invoiceCreated variables after a successful save.
This commit is contained in:
2026-08-15 22:55:54 +03:30
parent d3190a8729
commit 366cd41bb7
5 changed files with 176 additions and 93 deletions
+89 -32
View File
@@ -86,6 +86,42 @@ const emptyTemplateEntry = (def) => ({
variables: defaultVariablesList(def)
});
const toVariableItem = (item, fallbackSlot = '') => {
if (item == null) return null;
if (typeof item === 'object') {
const slot = String(item.slot || fallbackSlot || '').trim();
if (!slot) return null;
return {
slot,
name: String(item.name || '').trim()
};
}
const slot = String(fallbackSlot || '').trim();
if (!slot) return null;
return {
slot,
name: String(item).trim()
};
};
const normalizeVariablesInput = (rawVariables, def) => {
if (rawVariables == null) {
return def ? defaultVariablesList(def) : [];
}
if (Array.isArray(rawVariables)) {
return rawVariables.map((item) => toVariableItem(item)).filter(Boolean);
}
if (typeof rawVariables === 'object') {
return Object.entries(rawVariables)
.map(([key, value]) => toVariableItem(value, key))
.filter(Boolean);
}
return def ? defaultVariablesList(def) : [];
};
const normalizeStoredEntry = (raw, def) => {
if (raw == null || raw === '') {
return {
@@ -106,22 +142,9 @@ const normalizeStoredEntry = (raw, def) => {
};
}
let variables = [];
if (Array.isArray(raw.variables)) {
variables = raw.variables
.filter((item) => item && typeof item === 'object' && item.slot)
.map((item) => ({
slot: String(item.slot).trim(),
name: String(item.name || '').trim()
}));
} else if (raw.variables && typeof raw.variables === 'object') {
variables = Object.entries(raw.variables).map(([slot, name]) => ({
slot: String(slot).trim(),
name: String(name || '').trim()
}));
} else if (def) {
variables = defaultVariablesList(def);
}
const variables = Object.prototype.hasOwnProperty.call(raw, 'variables')
? normalizeVariablesInput(raw.variables, def)
: (def ? defaultVariablesList(def) : []);
return {
templateId: raw.templateId != null ? String(raw.templateId) : '',
@@ -129,19 +152,58 @@ const normalizeStoredEntry = (raw, def) => {
};
};
const parseIncomingVariables = (rawVariables, def) => {
if (rawVariables === undefined) return undefined;
return normalizeVariablesInput(rawVariables, def);
};
const mergeTemplateEntry = (def, storedRaw, incomingRaw = null) => {
const storedEntry = normalizeStoredEntry(storedRaw, def);
let templateId = storedEntry.templateId || envFallbackFor(def);
let variables = storedEntry.variables;
if (incomingRaw) {
if (incomingRaw.templateId !== undefined) {
const sanitized = sanitizeTemplateId(incomingRaw.templateId);
if (sanitized === null) {
const error = new Error(`Invalid SMS template ID for ${def?.key || 'template'}`);
error.code = 'INVALID_TEMPLATE_ID';
error.field = def?.key;
throw error;
}
templateId = sanitized;
}
if (incomingRaw.variables !== undefined) {
const parsed = parseIncomingVariables(incomingRaw.variables, def) || [];
for (const item of parsed) {
const sanitized = sanitizeVariableName(item.name);
if (item.name && sanitized === null) {
const error = new Error(`Invalid SMS variable name "${item.name}"`);
error.code = 'INVALID_VARIABLE_NAME';
error.field = `${def?.key}.${item.slot}`;
error.rawName = item.name;
throw error;
}
}
variables = parsed;
}
} else if (!storedEntry.templateId) {
templateId = envFallbackFor(def);
}
return {
templateId,
variables: resolveVariablesList(def, variables)
};
};
const resolveVariablesList = (def, storedVariables) => {
if (!storedVariables) {
return defaultVariablesList(def);
}
let list = [];
if (Array.isArray(storedVariables)) {
list = storedVariables;
} else if (typeof storedVariables === 'object') {
list = Object.entries(storedVariables).map(([slot, name]) => ({ slot, name }));
}
return list
return normalizeVariablesInput(storedVariables, def)
.map((item) => {
const slot = String(item.slot || '').trim();
if (!slot) return null;
@@ -159,14 +221,7 @@ const resolveVariablesList = (def, storedVariables) => {
};
const buildSmsParameters = (variables, valuesBySlot = {}) => {
let list = [];
if (Array.isArray(variables)) {
list = variables;
} else if (variables && typeof variables === 'object') {
list = Object.entries(variables).map(([slot, name]) => ({ slot, name }));
}
return list
return normalizeVariablesInput(variables, null)
.map((item) => {
const name = sanitizeVariableName(item?.name);
if (!name) return null;
@@ -189,6 +244,8 @@ module.exports = {
defaultVariablesList,
emptyTemplateEntry,
normalizeStoredEntry,
parseIncomingVariables,
mergeTemplateEntry,
resolveVariablesList,
buildSmsParameters
};