- Исправлена потеря документов при обновлении черновика (SQL объединяет вместо перезаписи) - Исправлено определение типа документа (приоритет field_label над field_name) - Исправлены дубликаты в documents_meta и documents_uploaded - Добавлена передача group_index с фронтенда для правильного field_name - Исправлены все документы в таблице clpr_claim_documents с правильными field_name - Обновлены SQL запросы: claimsave и claimsave_final для нового флоу - Добавлена поддержка multi-file upload для одного документа - Исправлены дубликаты в списке загруженных документов на фронтенде Файлы: - SQL: SQL_CLAIMSAVE_FIXED_NEW_FLOW.sql, SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED.sql - n8n: N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js (поддержка group_index) - Backend: documents.py (передача group_index в n8n) - Frontend: StepWizardPlan.tsx (передача group_index, исправление дубликатов) - Скрипты: fix_claim_documents_field_names.py, fix_documents_meta_duplicates.py Результат: документы больше не теряются, имеют правильные типы и field_name
215 lines
8.7 KiB
JavaScript
215 lines
8.7 KiB
JavaScript
// ========================================
|
||
// Code Node: Мерж данных проекта в сессию
|
||
// v2.0 - с расширенным логированием для отладки
|
||
// ========================================
|
||
|
||
// 1. Берём первый item
|
||
const inputItem = $input.all()[0];
|
||
|
||
if (!inputItem || !inputItem.json) {
|
||
throw new Error('Пустой input в Code Node (нет json)');
|
||
}
|
||
|
||
// root — то, что реально пришло в эту ноду
|
||
const root = inputItem.json;
|
||
|
||
// ✅ ОТЛАДКА: смотрим что пришло
|
||
console.log('🔍 DEBUG: root keys:', Object.keys(root));
|
||
console.log('🔍 DEBUG: root.body exists:', !!root.body);
|
||
console.log('🔍 DEBUG: root.other exists:', !!root.other);
|
||
|
||
// 2. Универсально получаем body
|
||
// - если нода стоит сразу после Webhook → данные лежат в root.body
|
||
// - если кто-то выше уже отдал только body → root и есть body
|
||
const body = root.body || root;
|
||
|
||
console.log('🔍 DEBUG: body keys:', Object.keys(body));
|
||
console.log('🔍 DEBUG: body.other exists:', !!body.other);
|
||
console.log('🔍 DEBUG: body.other type:', typeof body.other);
|
||
|
||
// 3. Парсим body.other (если есть) как сессию
|
||
// ✅ ВАЖНО: Также проверяем root.other напрямую (если данные пришли не через body)
|
||
let sessionData = {};
|
||
let rawOther = body.other || root.other;
|
||
|
||
// ✅ Пробуем также достать other из Webhook напрямую
|
||
if (!rawOther) {
|
||
try {
|
||
const webhookJson = $('Webhook').first()?.json;
|
||
if (webhookJson?.body?.other) {
|
||
rawOther = webhookJson.body.other;
|
||
console.log('✅ Взяли other напрямую из Webhook');
|
||
}
|
||
} catch (e) {
|
||
console.log('⚠️ Не удалось достать other из Webhook:', e.message);
|
||
}
|
||
}
|
||
|
||
console.log('🔍 DEBUG: rawOther exists:', !!rawOther);
|
||
console.log('🔍 DEBUG: rawOther type:', typeof rawOther);
|
||
if (rawOther) {
|
||
console.log('🔍 DEBUG: rawOther preview:', typeof rawOther === 'string' ? rawOther.substring(0, 200) : JSON.stringify(rawOther).substring(0, 200));
|
||
}
|
||
|
||
if (rawOther) {
|
||
if (typeof rawOther === 'string') {
|
||
try {
|
||
sessionData = JSON.parse(rawOther);
|
||
console.log('✅ Распарсили other как JSON. Ключи:', Object.keys(sessionData));
|
||
console.log('✅ sessionData.session_id:', sessionData.session_id);
|
||
console.log('✅ sessionData.phone:', sessionData.phone);
|
||
console.log('✅ sessionData.firstname:', sessionData.firstname);
|
||
} catch (e) {
|
||
throw new Error('Не смог распарсить other как JSON: ' + e.message + '. rawOther: ' + rawOther.substring(0, 500));
|
||
}
|
||
} else if (typeof rawOther === 'object') {
|
||
sessionData = rawOther;
|
||
console.log('✅ other уже объект. Ключи:', Object.keys(sessionData));
|
||
}
|
||
} else {
|
||
console.log('⚠️ other отсутствует или пустой. Проверьте структуру данных!');
|
||
console.log('⚠️ root:', JSON.stringify(root).substring(0, 500));
|
||
}
|
||
|
||
// 4. Определяем claimId (основной путь)
|
||
let claimId = body.claim_id || sessionData.claim_id || null;
|
||
|
||
// 5. Fallback: пробуем достать claim_id напрямую из Webhook, если его до сих пор нет
|
||
if (!claimId) {
|
||
try {
|
||
const webhookNodeJson = $('Webhook').first()?.json;
|
||
if (webhookNodeJson?.body?.claim_id) {
|
||
claimId = webhookNodeJson.body.claim_id;
|
||
}
|
||
} catch (e) {
|
||
// молча игнорируем, просто не удалось взять из Webhook
|
||
}
|
||
}
|
||
|
||
// 6. Если всё ещё нет claimId — это реально критичная ситуация
|
||
if (!claimId) {
|
||
throw new Error(
|
||
'Нет claim_id ни в body, ни в sessionData, ни в Webhook. ' +
|
||
'body: ' + JSON.stringify(body) +
|
||
', sessionData: ' + JSON.stringify(sessionData)
|
||
);
|
||
}
|
||
|
||
// 7. Забираем результат ноды CreateClientProject (или CreateWebPorject, если опечатка в названии ноды)
|
||
let projectNode = null;
|
||
let projectNodeName = null;
|
||
|
||
// Пробуем найти ноду безопасно
|
||
try {
|
||
projectNode = $node["CreateClientProject"];
|
||
if (projectNode && projectNode.json) {
|
||
projectNodeName = "CreateClientProject";
|
||
}
|
||
} catch (e) {
|
||
// Нода CreateClientProject не найдена, пробуем альтернативное название
|
||
}
|
||
|
||
if (!projectNode || !projectNode.json) {
|
||
try {
|
||
projectNode = $node["CreateWebPorject"];
|
||
if (projectNode && projectNode.json) {
|
||
projectNodeName = "CreateWebPorject";
|
||
}
|
||
} catch (e) {
|
||
// Нода CreateWebPorject тоже не найдена
|
||
}
|
||
}
|
||
|
||
if (!projectNode || !projectNode.json) {
|
||
throw new Error('Нет данных от ноды CreateClientProject/CreateWebPorject. Убедитесь, что нода существует и выполнена.');
|
||
}
|
||
|
||
const projectResult = projectNode.json.result;
|
||
// Ожидаем что-то типа: { "project_id": "398095", "project_name": "Иванов_КлиентПрав", "is_new": false }
|
||
|
||
if (!projectResult || !projectResult.project_id) {
|
||
throw new Error('Нет projectResult.project_id. result: ' + JSON.stringify(projectNode.json));
|
||
}
|
||
|
||
// 8. Собираем обновлённую сессию
|
||
// ✅ Используем spread оператор, но с фильтрацией undefined значений
|
||
// Сначала создаём базовый объект из sessionData, фильтруя undefined
|
||
const baseSession = Object.keys(sessionData).reduce((acc, key) => {
|
||
if (sessionData[key] !== undefined && sessionData[key] !== null) {
|
||
acc[key] = sessionData[key];
|
||
}
|
||
return acc;
|
||
}, {});
|
||
|
||
console.log('📦 baseSession после фильтрации:', Object.keys(baseSession));
|
||
console.log('📦 baseSession sample:', {
|
||
session_id: baseSession.session_id,
|
||
phone: baseSession.phone,
|
||
unified_id: baseSession.unified_id,
|
||
contact_id: baseSession.contact_id,
|
||
firstname: baseSession.firstname,
|
||
lastname: baseSession.lastname,
|
||
});
|
||
|
||
const updatedSession = {
|
||
// ✅ Шаг 1: Все данные из sessionData (body.other) - базовая сессия
|
||
...baseSession,
|
||
|
||
// ✅ Шаг 2: Дополняем данными из body (если их нет в sessionData)
|
||
...(body.phone && !baseSession.phone ? { phone: body.phone } : {}),
|
||
...(body.unified_id && !baseSession.unified_id ? { unified_id: body.unified_id } : {}),
|
||
...(body.contact_id && !baseSession.contact_id ? { contact_id: body.contact_id } : {}),
|
||
...(body.email && !baseSession.email ? { email: body.email } : {}),
|
||
|
||
// ✅ Шаг 3: Данные проекта (новые, всегда перезаписываем)
|
||
claim_id: claimId, // актуальный claim_id (перезаписываем null из sessionData)
|
||
project_id: projectResult.project_id, // id проекта из CRM
|
||
project_name: projectResult.project_name || null, // название проекта из CRM
|
||
is_new_project: projectResult.is_new, // флаг новый/старый
|
||
current_step: 2, // двигаем визард на шаг 2
|
||
|
||
// ✅ Шаг 4: Данные анализа из body (приоритет body)
|
||
problem: body.problem || baseSession.problem || null,
|
||
last_analysis_output: body.output || baseSession.last_analysis_output || null,
|
||
|
||
// ✅ Шаг 5: Метаданные (всегда обновляем)
|
||
updated_at: new Date().toISOString(),
|
||
};
|
||
|
||
// ✅ Логируем результат для отладки
|
||
console.log('📦 sessionData keys:', Object.keys(sessionData));
|
||
console.log('📦 sessionData sample:', {
|
||
session_id: sessionData.session_id,
|
||
phone: sessionData.phone,
|
||
unified_id: sessionData.unified_id,
|
||
contact_id: sessionData.contact_id,
|
||
firstname: sessionData.firstname,
|
||
lastname: sessionData.lastname,
|
||
middle_name: sessionData.middle_name,
|
||
});
|
||
console.log('📦 updatedSession keys:', Object.keys(updatedSession));
|
||
console.log('📦 updatedSession sample:', {
|
||
session_id: updatedSession.session_id,
|
||
phone: updatedSession.phone,
|
||
unified_id: updatedSession.unified_id,
|
||
contact_id: updatedSession.contact_id,
|
||
firstname: updatedSession.firstname,
|
||
lastname: updatedSession.lastname,
|
||
middle_name: updatedSession.middle_name,
|
||
claim_id: updatedSession.claim_id,
|
||
project_id: updatedSession.project_id,
|
||
});
|
||
console.log('📦 updatedSession FULL:', JSON.stringify(updatedSession, null, 2));
|
||
|
||
// 9. Возвращаем один item для Redis SET
|
||
return [
|
||
{
|
||
json: {
|
||
redis_key: `claim:${claimId}`,
|
||
redis_value: JSON.stringify(updatedSession),
|
||
ttl: 604800, // 7 дней
|
||
},
|
||
},
|
||
];
|
||
|