// Code node: Mini-app "Подтверждение данных" — фикс без вложенных ${} в шаблоне // --- ВХОД --- const raw = $input.all()?.[0]?.json ?? {}; // Извлекаем SMS данные из входящих данных n8n const smsInputData = { prefix: raw.prefix || '', session_token: raw.session_token || '', telegram_id: raw.telegram_id || '', claim_id: raw.claim_id || '', unified_id: (raw.propertyName && raw.propertyName.meta && raw.propertyName.meta.unified_id) || '', user_id: (raw.propertyName && raw.propertyName.meta && raw.propertyName.meta.user_id) || '', status: (raw.propertyName && raw.propertyName.meta && raw.propertyName.meta.status) || '' }; console.log('SMS input data from n8n:', smsInputData); // --- Утилиты --- function safeGet(...keys) { for (const k of keys) { if (k === undefined || k === null) continue; if (typeof k === 'object') { if (Object.keys(k).length) return k; continue; } const s = String(k).trim(); if (s !== '') return k; } return ''; } function tryParseJSON(x) { try { return typeof x === 'string' ? JSON.parse(x) : x; } catch { return null; } } function escapeHtml(str){ if (str === undefined || str === null) return ''; return String(str).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); } // Нормализация денежной суммы из любого формата в число function normalizeMoney(rawValue) { if (!rawValue) return null; console.log('normalizeMoney: входящее значение:', rawValue, 'тип:', typeof rawValue); var s = String(rawValue); // Удаляем все лишнее: пробелы, слова "рублей", "руб", валютные знаки s = s.replace(/\s+/g, ''); // убираем пробелы s = s.replace(/руб(лей|ль|\.)?/gi, ''); // убираем "рублей", "руб", "рубль" s = s.replace(/₽|р\.|р$/gi, ''); // убираем символы валют s = s.replace(/[^0-9,.-]/g, ''); // оставляем только цифры, запятые, точки, минусы // Заменяем запятую на точку для десятичной части s = s.replace(',', '.'); console.log('normalizeMoney: после очистки:', s); // Проверяем, что получилось валидное число if (!/^-?[0-9]+(\.[0-9]{1,2})?$/.test(s)) { console.log('normalizeMoney: невалидный формат после очистки'); return null; } var result = parseFloat(s); console.log('normalizeMoney: результат:', result); return result > 0 ? result : null; } // --- Достаём объект кейса из «типичных» мест --- let dataCandidate = null; if (!dataCandidate && raw.propertyName !== undefined) { // Если propertyName - это объект (как в вашем случае), берем его напрямую if (typeof raw.propertyName === 'object' && raw.propertyName !== null) { dataCandidate = raw.propertyName; } else if (typeof raw.propertyName === 'string') { dataCandidate = tryParseJSON(raw.propertyName); } } if (!dataCandidate && raw.value !== undefined) dataCandidate = tryParseJSON(raw.value); if (!dataCandidate && (raw.user || raw.project || raw.offenders || raw.meta)) dataCandidate = raw; if (!dataCandidate && raw.data) dataCandidate = raw.data; if (!dataCandidate && raw.output) dataCandidate = tryParseJSON(raw.output) || raw.output; dataCandidate = dataCandidate || {}; console.log('Raw dataCandidate:', dataCandidate); console.log('Type of dataCandidate:', typeof dataCandidate); console.log('Keys of dataCandidate:', Object.keys(dataCandidate || {})); // --- Базовые схемы --- const baseUser = { firstname:null, secondname:null, lastname:null, mobile:null, email:null, tgid:null, birthday:null, birthplace:null, mailingstreet:null, inn:null }; const baseProject = { category:null, direction:null, agrprice:null, subject:null, agrdate:null, startdate:null, finishdate:null, period_text:null, description:null, reason:null }; const baseOffender = { accountname:null, address:null, email:null, website:null, phone:null, inn:null, ogrn:null }; // --- Нормализуем данные под новую структуру --- function normalizeData(data) { console.log('=== НОРМАЛИЗАЦИЯ ДАННЫХ ==='); console.log('Input data:', data); console.log('Has propertyName:', !!data.propertyName); console.log('Has applicant in propertyName:', !!(data.propertyName && data.propertyName.applicant)); // Если данные приходят в новом формате (с propertyName) if (data.propertyName && data.propertyName.applicant) { console.log('Using NEW format with propertyName'); var props = data.propertyName; var applicant = props.applicant || {}; var caseData = props.case || {}; var contract = props.contract_or_service || {}; var offenders = props.offenders || []; var claim = props.claim || {}; var meta = props.meta || {}; console.log('=== ОТЛАДКА КОНТРАКТА ==='); console.log('contract_or_service:', contract); console.log('subject:', contract.subject); console.log('agreement_date_fmt:', contract.agreement_date_fmt); console.log('agreement_date:', contract.agreement_date); console.log('period_start_fmt:', contract.period_start_fmt); console.log('period_end_fmt:', contract.period_end_fmt); // Получаем список приложенных документов var attachments = props.attachments_names || []; console.log('=== ОТЛАДКА ПРИЛОЖЕНИЙ ==='); console.log('attachments_names:', attachments); return { user: { firstname: applicant.first_name || null, secondname: applicant.middle_name || null, lastname: applicant.last_name || null, mobile: applicant.phone || null, email: applicant.email || null, birthday: applicant.birth_date_fmt || applicant.birth_date || null, birthplace: applicant.birth_place || null, mailingstreet: applicant.address || null, inn: applicant.inn || null, tgid: null }, project: { category: caseData.category || null, // Тема обращения - общая категория (только для чтения) direction: caseData.direction || null, agrprice: normalizeMoney(contract.amount_paid_fmt || contract.amount_paid) || null, subject: contract.subject || null, agrdate: contract.agreement_date_fmt || contract.agreement_date || null, startdate: contract.period_start_fmt || contract.period_start || null, finishdate: contract.period_end_fmt || contract.period_end || null, period_text: contract.period_text || null, description: claim.description || null, reason: claim.reason || caseData.category || null // Копируется из категории, но редактируемая }, attachments: attachments, // Список приложенных документов offenders: offenders.map(function(o) { return { accountname: o.name || null, address: o.address || null, email: o.email || null, website: o.website || null, phone: o.phone || null, inn: o.inn || null, ogrn: o.ogrn || null }; }), meta: Object.assign({}, meta, { // Добавляем SMS данные из корня элемента массива session_token: data.session_token || meta.claim_id || null, prefix: data.prefix || null, telegram_id: data.telegram_id || null, claim_id: data.claim_id || meta.claim_id || null, unified_id: meta.unified_id || null, user_id: meta.user_id || null }) }; } // Если данные приходят в старом формате (прямо applicant, case, etc) if (data.applicant || data.case || data.contract_or_service) { var applicant = data.applicant || {}; var caseData = data.case || {}; var contract = data.contract_or_service || {}; var offenders = data.offenders || []; var claim = data.claim || {}; console.log('=== ОТЛАДКА КОНТРАКТА (старый формат) ==='); console.log('contract_or_service:', contract); console.log('subject:', contract.subject); console.log('agreement_date_fmt:', contract.agreement_date_fmt); return { user: { firstname: applicant.first_name || null, secondname: applicant.middle_name || null, lastname: applicant.last_name || null, mobile: applicant.phone || null, email: applicant.email || null, birthday: applicant.birth_date_fmt || applicant.birth_date || null, birthplace: applicant.birth_place || null, mailingstreet: applicant.address || null, inn: applicant.inn || null, tgid: null }, project: { category: caseData.category || null, direction: caseData.direction || null, agrprice: normalizeMoney(contract.amount_paid_fmt || contract.amount_paid) || null, subject: contract.subject || null, agrdate: contract.agreement_date_fmt || contract.agreement_date || null, startdate: contract.period_start_fmt || contract.period_start || null, finishdate: contract.period_end_fmt || contract.period_end || null, period_text: contract.period_text || null, description: claim.description || null, reason: claim.reason || caseData.category || null }, attachments: data.attachments_names || [], // Список приложенных документов (старый формат) offenders: offenders.map(function(o) { return { accountname: o.name || null, address: o.address || null, email: o.email || null, website: o.website || null, phone: o.phone || null, inn: o.inn || null, ogrn: o.ogrn || null }; }), meta: data.meta || {} }; } // Старый формат (обратная совместимость) return { user: Object.assign({}, baseUser, tryParseJSON(data.user) || data.user || {}), project: Object.assign({}, baseProject, tryParseJSON(data.project) || data.project || {}), offenders: Array.isArray(data.offenders) ? data.offenders.map(function(o) { return Object.assign({}, baseOffender, o || {}); }) : [], meta: Object.assign({}, data.meta || {}) }; } // Если dataCandidate - массив, берем первый элемент var dataToNormalize = Array.isArray(dataCandidate) ? dataCandidate[0] : dataCandidate; console.log('Data to normalize:', dataToNormalize); const caseObj = normalizeData(dataToNormalize); if (!caseObj.offenders.length) caseObj.offenders = [ Object.assign({}, baseOffender) ]; console.log('Normalized caseObj:', caseObj); // --- Куда постить подтверждение --- const webhookUrl = String(safeGet( raw.webhook_url, raw.confirm_url, raw.CONFIRM_URL, raw.WIZARD_POST_URL, raw.headers?.['x-webhook-url'], raw.headers?.['x-confirm-url'], '' )) || 'https://n8n.clientright.pro/webhook/miniapp/confirm'; // --- Сервисные поля (для отправки вместе с формой) --- const sessionToken = String(safeGet(caseObj.meta?.session_token, raw.session_token, raw.query?.session_token, '')); const telegramId = String(safeGet(caseObj.user?.tgid, raw.telegram_id, raw.query?.telegram_id, raw.tg_id, raw.chat_id, '')); // Дополнительные поля для SMS - берем из входящих данных n8n const smsMetaData = { session_token: String(safeGet(smsInputData.session_token, sessionToken, '')), prefix: String(safeGet(smsInputData.prefix, '')), telegram_id: String(safeGet(smsInputData.telegram_id, telegramId, '')), claim_id: String(safeGet(smsInputData.claim_id, '')), unified_id: String(safeGet(smsInputData.unified_id, '')), user_id: String(safeGet(smsInputData.user_id, '')) }; console.log('dataToNormalize keys:', Object.keys(dataToNormalize || {})); console.log('dataToNormalize.prefix:', dataToNormalize.prefix); console.log('dataToNormalize.session_token:', dataToNormalize.session_token); // Читаем дополнительные данные из URL параметров try { const urlParams = new URLSearchParams(window.location.search); var urlMetaData = { session_token: urlParams.get('session_token') || '', prefix: urlParams.get('prefix') || '', telegram_id: urlParams.get('telegram_id') || urlParams.get('tg_id') || '', claim_id: urlParams.get('claim_id') || '' }; console.log('URL search string:', window.location.search); console.log('URL meta data:', urlMetaData); } catch (e) { console.error('Error reading URL params:', e); var urlMetaData = {}; } console.log('SMS meta data extracted:', smsMetaData); // --- Безопасно встраиваем данные в HTML --- let caseJson = JSON.stringify({ case: caseObj, session_token: sessionToken, telegram_id: telegramId, token: raw.token || '', // Добавляем token для отправки формы sms_meta: Object.assign({}, smsMetaData, urlMetaData) // Объединяем данные из JSON и URL }); caseJson = caseJson.replace(/ // --- HTML (без вложенных ${} в скрипте) --- const html = ` Подтверждение данных

📋 Редактирование заявления

Проверьте и при необходимости отредактируйте все поля

Загрузка…
`; // Отдаём HTML одной нодой return [{ json: { html } }];