Files
aiform_prod/docs/N8N_FORM_GET_NO_FILES_INSTRUCTIONS.md
AI Assistant 0978e485dc feat: Add claim plan confirmation flow via Redis SSE
Problem:
- After wizard form submission, need to wait for claim data from n8n
- Claim data comes via Redis channel claim:plan:{session_token}
- Need to display confirmation form with claim data

Solution:
1. Backend: Added SSE endpoint /api/v1/claim-plan/{session_token}
   - Subscribes to Redis channel claim:plan:{session_token}
   - Streams claim data from n8n to frontend
   - Handles timeouts and errors gracefully

2. Frontend: Added subscription to claim:plan channel
   - StepWizardPlan: After form submission, subscribes to SSE
   - Waits for claim_plan_ready event
   - Shows loading message while waiting
   - On success: saves claimPlanData and shows confirmation step

3. New component: StepClaimConfirmation
   - Displays claim confirmation form in iframe
   - Receives claimPlanData from parent
   - Generates HTML form (placeholder - should call n8n for real HTML)
   - Handles confirmation/cancellation via postMessage

4. ClaimForm: Added conditional step for confirmation
   - Shows StepClaimConfirmation when showClaimConfirmation=true
   - Step appears after StepWizardPlan
   - Only visible when claimPlanData is available

Flow:
1. User fills wizard form → submits
2. Form data sent to n8n via /api/v1/claims/wizard
3. Frontend subscribes to SSE /api/v1/claim-plan/{session_token}
4. n8n processes data → publishes to Redis claim:plan:{session_token}
5. Backend receives → streams to frontend via SSE
6. Frontend receives → shows StepClaimConfirmation
7. User confirms → proceeds to next step

Files:
- backend/app/api/events.py: Added stream_claim_plan endpoint
- frontend/src/components/form/StepWizardPlan.tsx: Added subscribeToClaimPlan
- frontend/src/components/form/StepClaimConfirmation.tsx: New component
- frontend/src/pages/ClaimForm.tsx: Added confirmation step to steps array
2025-11-24 13:36:14 +03:00

11 KiB
Raw Blame History

Инструкция: Добавление обработки формы БЕЗ файлов в 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

Подключение:

  • Из ноды "проверка наличия файлов" → 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:

={{ {
  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:

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:

={{ {
  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:

={{ {
  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):

{
  "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):

{
  "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 событие:

{
  "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
Статус: Готово к внедрению