# Инструкция: Добавление обработки формы БЕЗ файлов в workflow form_get **Дата:** 2025-11-21 **Workflow ID:** `8ZVMTsuH7Cmw7snw` **Workflow Name:** `form_get` --- ## 🎯 Цель Добавить ветку обработки для случая, когда форма отправляется **без файлов** (`has_files === false`). --- ## 📍 Где добавлять В workflow `form_get` есть IF-нода **"проверка наличия файлов"** (ID: `b7497f29-dab3-41cd-aaa3-a43ee83e607c`): - **TRUE ветка** (index 0) — файлов НЕТ → **ПУСТАЯ** ❌ - **FALSE ветка** (index 1) — файлы ЕСТЬ → существующий flow ✅ **Задача:** Добавить ноды в TRUE ветку. --- ## 📝 Пошаговая инструкция ### Шаг 1: Открыть workflow 1. Перейти в n8n: https://n8n.clientright.pro 2. Открыть workflow **"form_get"** 3. Найти ноду **"проверка наличия файлов"** (IF) --- ### Шаг 2: Добавить ноду #1 - Extract Data **Название:** `extract_webhook_data_no_files` **Тип:** `Edit Fields` (Set) **Позиция:** справа от IF-ноды, выше основного flow **Параметры:** | Field Name | Type | Value | |------------|------|-------| | `session_id` | String | `={{ $('Webhook').item.json.body.session_id }}` | | `claim_id` | String | `={{ $('Webhook').item.json.body.claim_id }}` | | `unified_id` | String | `={{ $('Webhook').item.json.body.unified_id }}` | | `contact_id` | String | `={{ $('Webhook').item.json.body.contact_id }}` | | `phone` | String | `={{ $('Webhook').item.json.body.phone }}` | | `wizard_plan` | Object | `={{ $('Webhook').item.json.body.wizard_plan }}` | | `wizard_answers` | Object | `={{ $('Webhook').item.json.body.wizard_answers }}` | | `type_code` | String | `={{ $('Webhook').item.json.body.type_code || 'consumer' }}` | **Подключение:** - Из ноды **"проверка наличия файлов"** → TRUE (верхний выход) - К ноде **"extract_webhook_data_no_files"** --- ### Шаг 3: Добавить ноду #2 - Prepare Payload **Название:** `prepare_payload_no_files` **Тип:** `Edit Fields` (Set) **Параметры:** | Field Name | Type | Value | |------------|------|-------| | `payload_partial_json` | Object | См. ниже ⬇️ | | `claim_id` | String | `={{ $json.claim_id }}` | **Значение для `payload_partial_json`:** ```javascript ={{ { session_id: $json.session_id, unified_id: $json.unified_id, contact_id: $json.contact_id, phone: $json.phone, type_code: $json.type_code, wizard_plan: $json.wizard_plan, wizard_answers: $json.wizard_answers, documents_meta: [] } }} ``` **Подключение:** - Из ноды **"extract_webhook_data_no_files"** - К ноде **"prepare_payload_no_files"** --- ### Шаг 4: Добавить ноду #3 - Save to PostgreSQL **Название:** `save_claim_no_files` **Тип:** `Postgres` **Operation:** `Execute Query` **Credentials:** `timeweb_bd` (существующие) **Query:** ```sql WITH partial AS ( SELECT $1::jsonb AS p, $2::text AS claim_id_str ), wizard_answers_parsed AS ( SELECT CASE WHEN partial.p->>'wizard_answers' IS NOT NULL THEN (partial.p->>'wizard_answers')::jsonb WHEN partial.p->'wizard_answers' IS NOT NULL AND jsonb_typeof(partial.p->'wizard_answers') = 'object' THEN partial.p->'wizard_answers' ELSE '{}'::jsonb END AS answers FROM partial ), 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' ELSE NULL END AS wizard_plan FROM partial ), claim_upsert AS ( INSERT INTO clpr_claims ( id, session_token, unified_id, contact_id, phone, channel, type_code, status_code, payload, created_at, updated_at, expires_at ) SELECT partial.claim_id_str::uuid, COALESCE(partial.p->>'session_id', 'sess-unknown'), partial.p->>'unified_id', partial.p->>'contact_id', partial.p->>'phone', 'web_form', COALESCE(partial.p->>'type_code', 'consumer'), 'draft', jsonb_build_object( 'claim_id', partial.claim_id_str, 'answers', (SELECT answers FROM wizard_answers_parsed), 'documents_meta', '[]'::jsonb, 'wizard_plan', (SELECT wizard_plan FROM wizard_plan_parsed) ), COALESCE( (SELECT created_at FROM clpr_claims WHERE id = partial.claim_id_str::uuid), now() ), now(), now() + interval '14 days' FROM partial ON CONFLICT (id) DO UPDATE SET session_token = EXCLUDED.session_token, unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id), contact_id = COALESCE(EXCLUDED.contact_id, clpr_claims.contact_id), phone = COALESCE(EXCLUDED.phone, clpr_claims.phone), status_code = 'draft', payload = ( clpr_claims.payload - 'answers' - 'documents_meta' - 'wizard_plan' - 'claim_id' ) || EXCLUDED.payload, updated_at = now(), expires_at = now() + interval '14 days' RETURNING id, status_code, payload, unified_id, contact_id, phone, session_token ) SELECT (SELECT jsonb_build_object( 'claim_id', cu.id::text, 'claim_id_str', (cu.payload->>'claim_id'), 'status_code', cu.status_code, 'unified_id', cu.unified_id, 'contact_id', cu.contact_id, 'phone', cu.phone, 'session_token', cu.session_token, 'payload', cu.payload ) FROM claim_upsert cu) AS claim; ``` **Query Replacement:** `={{ $json.payload_partial_json }}, {{ $json.claim_id }}` **Подключение:** - Из ноды **"prepare_payload_no_files"** - К ноде **"save_claim_no_files"** --- ### Шаг 5: Добавить ноду #4 - Prepare Redis Event **Название:** `prepare_redis_event_no_files` **Тип:** `Edit Fields` (Set) **Параметры:** | Field Name | Type | Value | |------------|------|-------| | `redis_key` | String | `=ocr_events:{{ $('extract_webhook_data_no_files').item.json.session_id }}` | | `redis_value` | Object | См. ниже ⬇️ | **Значение для `redis_value`:** ```javascript ={{ { event_type: 'form_saved_no_files', claim_id: $json.claim.claim_id, status_code: $json.claim.status_code, unified_id: $json.claim.unified_id, contact_id: $json.claim.contact_id, phone: $json.claim.phone, session_token: $json.claim.session_token, has_files: false, timestamp: new Date().toISOString() } }} ``` **Подключение:** - Из ноды **"save_claim_no_files"** - К ноде **"prepare_redis_event_no_files"** --- ### Шаг 6: Добавить ноду #5 - Publish to Redis **Название:** `publish_to_redis_no_files` **Тип:** `Redis` **Operation:** `Publish` **Credentials:** `Local Redis` (существующие) **Параметры:** | Parameter | Value | |-----------|-------| | Channel | `={{ $json.redis_key }}` | | Value | `={{ JSON.stringify($json.redis_value) }}` | **Подключение:** - Из ноды **"prepare_redis_event_no_files"** - К ноде **"publish_to_redis_no_files"** --- ### Шаг 7: Добавить ноду #6 - Respond to Webhook **Название:** `respond_no_files` **Тип:** `Respond to Webhook` **Response Code:** `200` **Response Body:** ```javascript ={{ { success: true, claim_id: $('save_claim_no_files').item.json.claim.claim_id, status_code: $('save_claim_no_files').item.json.claim.status_code, has_files: false, message: 'Заявка сохранена без файлов' } }} ``` **Подключение:** - Из ноды **"publish_to_redis_no_files"** - К ноде **"respond_no_files"** (финальная нода) --- ## 🔄 Финальная структура workflow ``` Webhook → Code17 (парсинг файлов) ↓ IF "проверка наличия файлов" │ ├─ TRUE (файлов НЕТ) → [НОВАЯ ВЕТКА] │ ↓ │ extract_webhook_data_no_files │ ↓ │ prepare_payload_no_files │ ↓ │ save_claim_no_files (PostgreSQL) │ ↓ │ prepare_redis_event_no_files │ ↓ │ publish_to_redis_no_files │ ↓ │ respond_no_files │ └─ FALSE (файлы ЕСТЬ) → существующий flow ↓ set_token1 → get_data1 → Upload → ... ``` --- ## ✅ Проверка После добавления нод: 1. **Сохранить workflow** (Ctrl+S) 2. **Активировать workflow** (если не активен) 3. **Протестировать:** - Отправить форму БЕЗ файлов - Проверить, что заявка сохранилась в PostgreSQL - Проверить событие в Redis: `redis-cli GET "ocr_events:sess-xxx"` - Проверить ответ webhook: `success: true, has_files: false` --- ## 📊 Ожидаемый результат **Вход (webhook body):** ```json { "session_id": "sess_xxx", "claim_id": "uuid", "unified_id": "usr_xxx", "contact_id": "12345", "phone": "79262306381", "wizard_answers": {"q1": "answer1"}, "wizard_plan": null } ``` **Выход (claim в PostgreSQL):** ```json { "claim": { "claim_id": "uuid", "status_code": "draft", "unified_id": "usr_xxx", "contact_id": "12345", "phone": "79262306381", "session_token": "sess_xxx", "payload": { "claim_id": "uuid", "answers": {"q1": "answer1"}, "documents_meta": [], "wizard_plan": null } } } ``` **Redis событие:** ```json { "event_type": "form_saved_no_files", "claim_id": "uuid", "status_code": "draft", "has_files": false, "timestamp": "2025-11-21T15:00:00.000Z" } ``` --- ## 🛠️ Troubleshooting ### Проблема 1: "session_token": "sess-unknown" **Причина:** В payload не передан `session_id` **Решение:** Проверить, что фронтенд отправляет `session_id` в body ### Проблема 2: "contact_id": null **Причина:** Поле не извлекается из webhook **Решение:** Проверить путь в expression: `$('Webhook').item.json.body.contact_id` ### Проблема 3: Ошибка PostgreSQL **Причина:** Неправильный формат данных **Решение:** Проверить логи n8n и формат `payload_partial_json` --- **Автор:** AI Assistant **Дата:** 2025-11-21 **Статус:** Готово к внедрению ✅