{ "meta": { "description": "Ноды для обработки формы БЕЗ файлов в workflow form_get", "date": "2025-11-21", "action": "Добавить в TRUE ветку IF-ноды 'проверка наличия файлов'" }, "nodes": [ { "name": "extract_webhook_data_no_files", "description": "Извлекаем данные из webhook для случая без файлов", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [-320, 400], "parameters": { "assignments": { "assignments": [ { "id": "session_id", "name": "session_id", "value": "={{ $('Webhook').item.json.body.session_id }}", "type": "string" }, { "id": "claim_id", "name": "claim_id", "value": "={{ $('Webhook').item.json.body.claim_id }}", "type": "string" }, { "id": "unified_id", "name": "unified_id", "value": "={{ $('Webhook').item.json.body.unified_id }}", "type": "string" }, { "id": "contact_id", "name": "contact_id", "value": "={{ $('Webhook').item.json.body.contact_id }}", "type": "string" }, { "id": "phone", "name": "phone", "value": "={{ $('Webhook').item.json.body.phone }}", "type": "string" }, { "id": "wizard_plan", "name": "wizard_plan", "value": "={{ $('Webhook').item.json.body.wizard_plan }}", "type": "object" }, { "id": "wizard_answers", "name": "wizard_answers", "value": "={{ $('Webhook').item.json.body.wizard_answers }}", "type": "object" }, { "id": "type_code", "name": "type_code", "value": "={{ $('Webhook').item.json.body.type_code || 'consumer' }}", "type": "string" } ] }, "options": {} } }, { "name": "prepare_payload_no_files", "description": "Формируем payload для PostgreSQL", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [-80, 400], "parameters": { "assignments": { "assignments": [ { "id": "payload_partial_json", "name": "payload_partial_json", "value": "={{ {\n session_id: $json.session_id,\n unified_id: $json.unified_id,\n contact_id: $json.contact_id,\n phone: $json.phone,\n type_code: $json.type_code,\n wizard_plan: $json.wizard_plan,\n wizard_answers: $json.wizard_answers,\n documents_meta: []\n} }}", "type": "object" }, { "id": "claim_id", "name": "claim_id", "value": "={{ $json.claim_id }}", "type": "string" } ] }, "options": {} } }, { "name": "save_claim_no_files", "description": "Сохраняем claim без файлов в PostgreSQL", "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [180, 400], "parameters": { "operation": "executeQuery", "query": "WITH partial AS (\n SELECT \n $1::jsonb AS p, \n $2::text AS claim_id_str\n),\n\n-- Парсим wizard_answers\nwizard_answers_parsed AS (\n SELECT \n CASE \n WHEN partial.p->>'wizard_answers' IS NOT NULL \n THEN (partial.p->>'wizard_answers')::jsonb\n WHEN partial.p->'wizard_answers' IS NOT NULL \n AND jsonb_typeof(partial.p->'wizard_answers') = 'object'\n THEN partial.p->'wizard_answers'\n ELSE '{}'::jsonb\n END AS answers\n FROM partial\n),\n\n-- Парсим wizard_plan\nwizard_plan_parsed AS (\n SELECT \n CASE \n WHEN partial.p->>'wizard_plan' IS NOT NULL \n THEN (partial.p->>'wizard_plan')::jsonb\n WHEN partial.p->'wizard_plan' IS NOT NULL \n AND jsonb_typeof(partial.p->'wizard_plan') = 'object'\n THEN partial.p->'wizard_plan'\n ELSE NULL\n END AS wizard_plan\n FROM partial\n),\n\n-- UPSERT claim\nclaim_upsert AS (\n INSERT INTO clpr_claims (\n id,\n session_token,\n unified_id,\n contact_id,\n phone,\n channel,\n type_code,\n status_code,\n payload,\n created_at,\n updated_at,\n expires_at\n )\n SELECT \n partial.claim_id_str::uuid,\n COALESCE(partial.p->>'session_id', 'sess-unknown'),\n partial.p->>'unified_id',\n partial.p->>'contact_id',\n partial.p->>'phone',\n 'web_form',\n COALESCE(partial.p->>'type_code', 'consumer'),\n 'draft',\n jsonb_build_object(\n 'claim_id', partial.claim_id_str,\n 'answers', (SELECT answers FROM wizard_answers_parsed),\n 'documents_meta', '[]'::jsonb,\n 'wizard_plan', (SELECT wizard_plan FROM wizard_plan_parsed)\n ),\n COALESCE(\n (SELECT created_at FROM clpr_claims WHERE id = partial.claim_id_str::uuid),\n now()\n ),\n now(),\n now() + interval '14 days'\n FROM partial\n ON CONFLICT (id) DO UPDATE SET\n session_token = EXCLUDED.session_token,\n unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id),\n contact_id = COALESCE(EXCLUDED.contact_id, clpr_claims.contact_id),\n phone = COALESCE(EXCLUDED.phone, clpr_claims.phone),\n status_code = 'draft',\n payload = (\n clpr_claims.payload \n - 'answers' \n - 'documents_meta' \n - 'wizard_plan' \n - 'claim_id'\n ) || EXCLUDED.payload,\n updated_at = now(),\n expires_at = now() + interval '14 days'\n RETURNING id, status_code, payload, unified_id, contact_id, phone, session_token\n)\n\nSELECT\n (SELECT jsonb_build_object(\n 'claim_id', cu.id::text,\n 'claim_id_str', (cu.payload->>'claim_id'),\n 'status_code', cu.status_code,\n 'unified_id', cu.unified_id,\n 'contact_id', cu.contact_id,\n 'phone', cu.phone,\n 'session_token', cu.session_token,\n 'payload', cu.payload\n ) FROM claim_upsert cu) AS claim;", "options": { "queryReplacement": "={{ $json.payload_partial_json }}, {{ $json.claim_id }}" } }, "credentials": { "postgres": { "id": "sGJ0fJhU8rz88w3k", "name": "timeweb_bd" } } }, { "name": "prepare_redis_event_no_files", "description": "Готовим событие для публикации в Redis", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [440, 400], "parameters": { "assignments": { "assignments": [ { "id": "redis_key", "name": "redis_key", "value": "=ocr_events:{{ $('extract_webhook_data_no_files').item.json.session_id }}", "type": "string" }, { "id": "redis_value", "name": "redis_value", "value": "={{ {\n event_type: 'form_saved',\n claim_id: $json.claim.claim_id,\n status_code: $json.claim.status_code,\n unified_id: $json.claim.unified_id,\n contact_id: $json.claim.contact_id,\n phone: $json.claim.phone,\n session_token: $json.claim.session_token,\n has_files: false,\n timestamp: new Date().toISOString()\n} }}", "type": "object" } ] }, "options": {} } }, { "name": "publish_to_redis_no_files", "description": "Публикуем событие в Redis", "type": "n8n-nodes-base.redis", "typeVersion": 1, "position": [700, 400], "parameters": { "operation": "publish", "channel": "={{ $json.redis_key }}", "value": "={{ JSON.stringify($json.redis_value) }}", "options": {} }, "credentials": { "redis": { "id": "RKICQB2ZaisVK4WS", "name": "Local Redis" } } }, { "name": "respond_no_files", "description": "Возвращаем ответ клиенту", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [960, 400], "parameters": { "options": { "responseCode": 200 }, "respondWith": "json", "responseBody": "={{ {\n success: true,\n claim_id: $('save_claim_no_files').item.json.claim.claim_id,\n status_code: $('save_claim_no_files').item.json.claim.status_code,\n has_files: false,\n message: 'Заявка сохранена без файлов'\n} }}" } } ], "connections": { "проверка наличия файлов": { "main": [ [ { "node": "extract_webhook_data_no_files", "type": "main", "index": 0 } ], [ { "node": "set_token1", "type": "main", "index": 0 } ] ] }, "extract_webhook_data_no_files": { "main": [ [ { "node": "prepare_payload_no_files", "type": "main", "index": 0 } ] ] }, "prepare_payload_no_files": { "main": [ [ { "node": "save_claim_no_files", "type": "main", "index": 0 } ] ] }, "save_claim_no_files": { "main": [ [ { "node": "prepare_redis_event_no_files", "type": "main", "index": 0 } ] ] }, "prepare_redis_event_no_files": { "main": [ [ { "node": "publish_to_redis_no_files", "type": "main", "index": 0 } ] ] }, "publish_to_redis_no_files": { "main": [ [ { "node": "respond_no_files", "type": "main", "index": 0 } ] ] } }, "instructions": { "step1": "Открыть workflow 'form_get' (ID: 8ZVMTsuH7Cmw7snw) в n8n", "step2": "Найти IF-ноду 'проверка наличия файлов' (ID: b7497f29-dab3-41cd-aaa3-a43ee83e607c)", "step3": "TRUE ветка (index 0) сейчас пустая - туда нужно добавить новые ноды", "step4": "Импортировать ноды из этого JSON или создать вручную по схеме", "step5": "Подключить TRUE ветку IF к первой ноде: extract_webhook_data_no_files", "step6": "Сохранить и активировать workflow", "step7": "Протестировать отправку формы БЕЗ файлов" } }