diff --git a/docs/SQL_CLAIMSAVE_UPSERT_SIMPLE.sql b/docs/SQL_CLAIMSAVE_UPSERT_SIMPLE.sql index 71b859f..74022ca 100644 --- a/docs/SQL_CLAIMSAVE_UPSERT_SIMPLE.sql +++ b/docs/SQL_CLAIMSAVE_UPSERT_SIMPLE.sql @@ -1,7 +1,8 @@ -- Упрощённый UPSERT для сохранения claim с известным claim_id -- Используется в n8n workflow: form_get (нода claimsave) --- Дата: 2025-11-21 +-- Дата: 2025-11-21 (обновлено: сохранение существующих полей) -- Описание: Простой INSERT/UPDATE для claim, т.к. claim_id уже известен +-- ВАЖНО: Сохраняет wizard_plan и другие поля из БД, если не пришли новые -- Входные параметры: -- $1: payload_partial_json (jsonb) - данные формы с wizard_answers, wizard_plan, documents_meta @@ -13,6 +14,16 @@ WITH partial AS ( $2::text AS claim_id_str ), +-- Получаем существующий payload из БД (если запись есть) +existing_claim AS ( + SELECT + id, + payload + FROM clpr_claims + WHERE id = (SELECT claim_id_str::uuid FROM partial) + LIMIT 1 +), + -- Парсим wizard_answers wizard_answers_parsed AS ( SELECT @@ -27,20 +38,94 @@ wizard_answers_parsed AS ( FROM partial ), --- Парсим wizard_plan +-- Парсим wizard_plan (или берём из существующей записи) wizard_plan_parsed AS ( SELECT CASE + -- Сначала пытаемся взять из входящих данных WHEN partial.p->>'wizard_plan' IS NOT NULL THEN (partial.p->>'wizard_plan')::jsonb WHEN partial.p->'wizard_plan' IS NOT NULL AND jsonb_typeof(partial.p->'wizard_plan') = 'object' THEN partial.p->'wizard_plan' + -- Если нет в payload - берём из существующей записи в БД + WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'wizard_plan' IS NOT NULL) + THEN (SELECT payload->'wizard_plan' FROM existing_claim) ELSE NULL END AS wizard_plan FROM partial ), +-- Парсим answers_prefill (или берём из БД) +answers_prefill_parsed AS ( + SELECT + CASE + WHEN partial.p->'answers_prefill' IS NOT NULL + AND jsonb_typeof(partial.p->'answers_prefill') = 'array' + THEN partial.p->'answers_prefill' + WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'answers_prefill' IS NOT NULL) + THEN (SELECT payload->'answers_prefill' FROM existing_claim) + ELSE '[]'::jsonb + END AS answers_prefill + FROM partial +), + +-- Парсим coverage_report (или берём из БД) +coverage_report_parsed AS ( + SELECT + CASE + WHEN partial.p->'coverage_report' IS NOT NULL + AND jsonb_typeof(partial.p->'coverage_report') = 'object' + THEN partial.p->'coverage_report' + WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'coverage_report' IS NOT NULL) + THEN (SELECT payload->'coverage_report' FROM existing_claim) + ELSE NULL + END AS coverage_report + FROM partial +), + +-- Парсим ai_agent1_facts (или берём из БД) +ai_agent1_facts_parsed AS ( + SELECT + CASE + WHEN partial.p->'ai_agent1_facts' IS NOT NULL + AND jsonb_typeof(partial.p->'ai_agent1_facts') = 'object' + THEN partial.p->'ai_agent1_facts' + WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'ai_agent1_facts' IS NOT NULL) + THEN (SELECT payload->'ai_agent1_facts' FROM existing_claim) + ELSE NULL + END AS ai_agent1_facts + FROM partial +), + +-- Парсим ai_agent13_rag (или берём из БД) +ai_agent13_rag_parsed AS ( + SELECT + CASE + WHEN partial.p->'ai_agent13_rag' IS NOT NULL + THEN partial.p->'ai_agent13_rag' + WHEN partial.p->>'ai_agent13_rag' IS NOT NULL + THEN to_jsonb(partial.p->>'ai_agent13_rag') + WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'ai_agent13_rag' IS NOT NULL) + THEN (SELECT payload->'ai_agent13_rag' FROM existing_claim) + ELSE NULL + END AS ai_agent13_rag + FROM partial +), + +-- Парсим problem_description (или берём из БД) +problem_description_parsed AS ( + SELECT + CASE + WHEN partial.p->>'problem_description' IS NOT NULL + THEN partial.p->>'problem_description' + WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->>'problem_description' IS NOT NULL) + THEN (SELECT payload->>'problem_description' FROM existing_claim) + ELSE NULL + END AS problem_description + FROM partial +), + -- UPSERT claim claim_upsert AS ( INSERT INTO clpr_claims ( @@ -72,9 +157,16 @@ claim_upsert AS ( END, jsonb_build_object( 'claim_id', partial.claim_id_str, + 'problem_description', (SELECT problem_description FROM problem_description_parsed), 'answers', (SELECT answers FROM wizard_answers_parsed), 'documents_meta', COALESCE(partial.p->'documents_meta', '[]'::jsonb), - 'wizard_plan', (SELECT wizard_plan FROM wizard_plan_parsed) + 'wizard_plan', (SELECT wizard_plan FROM wizard_plan_parsed), + 'answers_prefill', (SELECT answers_prefill FROM answers_prefill_parsed), + 'coverage_report', (SELECT coverage_report FROM coverage_report_parsed), + 'ai_agent1_facts', (SELECT ai_agent1_facts FROM ai_agent1_facts_parsed), + 'ai_agent13_rag', (SELECT ai_agent13_rag FROM ai_agent13_rag_parsed), + 'phone', COALESCE(partial.p->>'phone', (SELECT payload->>'phone' FROM existing_claim)), + 'email', COALESCE(partial.p->>'email', (SELECT payload->>'email' FROM existing_claim)) ), COALESCE( (SELECT created_at FROM clpr_claims WHERE id = partial.claim_id_str::uuid), @@ -89,14 +181,7 @@ claim_upsert AS ( contact_id = COALESCE(EXCLUDED.contact_id, clpr_claims.contact_id), phone = COALESCE(EXCLUDED.phone, clpr_claims.phone), status_code = EXCLUDED.status_code, - payload = ( - -- Сохраняем старые поля, которых нет в новом payload - clpr_claims.payload - - 'answers' - - 'documents_meta' - - 'wizard_plan' - - 'claim_id' - ) || EXCLUDED.payload, + payload = EXCLUDED.payload, updated_at = now(), expires_at = now() + interval '14 days' RETURNING id, status_code, payload, unified_id, contact_id, phone, session_token @@ -161,9 +246,21 @@ SELECT )) FROM docs_upsert) AS documents; /* -ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ: +============================================================================ +КЛЮЧЕВЫЕ ИЗМЕНЕНИЯ (2025-11-21): +============================================================================ -1. Вызов с wizard_answers и wizard_plan: +1. Добавлена CTE "existing_claim" - читает существующий payload из БД +2. Все парсеры (wizard_plan, answers_prefill, coverage_report и т.д.) + теперь проверяют БД, если поле не пришло в payload_partial_json +3. Это критично для workflow form_get, где wizard_plan создаётся на Step 2, + а файлы загружаются на Step 3 (без повторной отправки wizard_plan) + +============================================================================ +ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ: +============================================================================ + +1. Первое сохранение (после Step 2 - описание проблемы): SELECT * FROM ... WHERE ... = ( '{ @@ -171,8 +268,27 @@ SELECT * FROM ... WHERE ... = ( "unified_id": "usr_xxx", "contact_id": "12345", "phone": "79262306381", - "wizard_answers": "{\\"q1\\": \\"answer1\\"}", - "wizard_plan": "{\\"questions\\": [...]}", + "problem_description": "Не вернули деньги...", + "wizard_plan": {...полный wizard_plan...}, + "ai_agent1_facts": {...}, + "ai_agent13_rag": "...", + "answers_prefill": [...], + "coverage_report": {...} + }'::jsonb, + 'uuid-here'::text +); + +Результат: Создаётся claim с wizard_plan ✅ + +--- + +2. Обновление с файлами (Step 3 - загрузка документов): + +SELECT * FROM ... WHERE ... = ( + '{ + "session_id": "sess_xxx", + "unified_id": "usr_xxx", + "wizard_answers": {"q1": "answer1"}, "documents_meta": [ { "field_name": "uploads[0][0]", @@ -183,25 +299,35 @@ SELECT * FROM ... WHERE ... = ( } ] }'::jsonb, - 'uuid-here'::text + 'СУЩЕСТВУЮЩИЙ-uuid'::text ); -2. Вызов БЕЗ файлов (только answers): +Результат: +- Обновляется answers ✅ +- Добавляются documents_meta ✅ +- wizard_plan СОХРАНЯЕТСЯ из БД ✅ (не затирается!) + +--- + +3. Сохранение БЕЗ файлов (только answers): SELECT * FROM ... WHERE ... = ( '{ "session_id": "sess_xxx", - "unified_id": "usr_xxx", - "contact_id": "12345", - "phone": "79262306381", - "wizard_answers": "{\\"q1\\": \\"answer1\\"}", - "wizard_plan": null, + "wizard_answers": {"q1": "answer1"}, "documents_meta": [] }'::jsonb, 'uuid-here'::text ); -РЕЗУЛЬТАТ: +Результат: +- status_code = 'draft' (т.к. docs_exist != true) +- wizard_plan сохраняется из БД ✅ + +============================================================================ +ОЖИДАЕМЫЙ РЕЗУЛЬТАТ: +============================================================================ + { "claim": { "claim_id": "uuid", @@ -211,17 +337,32 @@ SELECT * FROM ... WHERE ... = ( "contact_id": "12345", "phone": "79262306381", "session_token": "sess_xxx", - "payload": {...} - }, - "documents": [ - { - "id": "uuid", - "field_name": "uploads[0][0]", - "file_id": "clientright/0/file.pdf", - "file_name": "file.pdf", - "original_file_name": "original.pdf" + "payload": { + "claim_id": "uuid", + "problem_description": "...", + "answers": {...}, + "documents_meta": [...], + "wizard_plan": {...}, // ← СОХРАНЯЕТСЯ из БД! + "answers_prefill": [...], + "coverage_report": {...}, + "ai_agent1_facts": {...}, + "ai_agent13_rag": "..." } - ] + }, + "documents": [...] } -*/ +============================================================================ +TROUBLESHOOTING: +============================================================================ + +Проблема: wizard_plan всё равно NULL после загрузки файлов +Причина: В n8n workflow form_get не передаётся claim_id в payload +Решение: Убедиться, что в set_token1 извлекается claim_id из webhook: + "claim_id": "={{ $('Webhook').item.json.body.claim_id }}" + +Проблема: Затираются ai_agent1_facts после Step 3 +Причина: Не включены в payload при отправке +Решение: SQL сохраняет их из БД автоматически ✅ + +*/