Files
aiform_prod/docs/DATABASE_SCHEMA.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

5.3 KiB
Raw Blame History

Схема базы данных clpr_*

Основные таблицы

1. clpr_users - Основная таблица пользователей

id (integer, PK)
universal_id (uuid)
unified_id (varchar) ← КЛЮЧЕВОЕ ПОЛЕ для связи
phone (varchar)
created_at, updated_at

2. clpr_user_accounts - Связь пользователей с каналами

id (integer, PK)
user_id (integer) → FK на clpr_users.id
channel (text) - 'telegram', 'web_form'
channel_user_id (text) - ID в канале (telegram_id для telegram, phone для web_form)

Связь:

  • clpr_user_accounts.user_idclpr_users.id
  • Уникальность: (channel, channel_user_id) - один пользователь может быть в нескольких каналах

3. clpr_claims - Заявки/черновики

id (uuid, PK)
session_token (varchar)
unified_id (varchar) ← СВЯЗЬ С clpr_users.unified_id (должен заполняться n8n!)
telegram_id (bigint)
channel (text) - 'telegram', 'web_form'
user_id (integer) - возможно FK на clpr_users.id
type_code (text)
status_code (text) - 'draft', 'in_work', etc.
policy_number (text)
payload (jsonb) - содержит phone, claim_id, wizard_plan, answers, documents_meta и т.д.
is_confirmed (boolean)
created_at, updated_at, expires_at

Связь:

  • clpr_claims.unified_idclpr_users.unified_id (логическая связь)
  • clpr_claims.user_idclpr_users.id (возможно, не всегда заполнено)

4. clpr_users_tg - Данные Telegram пользователей

telegram_id (bigint, PK)
unified_id (varchar) → clpr_users.unified_id
phone_number (varchar)
first_name_tg, last_name_tg, username, language_code, is_premium
first_name, last_name, middle_name, birth_date, etc.

5. clpr_claim_documents - Документы заявок

id (uuid, PK)
claim_id (varchar) → clpr_claims.id (логическая связь через payload->>'claim_id')
field_name (text)
file_id (text)
uploaded_at (timestamp)
file_name, original_file_name

6. clpr_documents - Хранилище документов

id (uuid, PK)
source (text)
content (text)
metadata (jsonb)
created_at

Логика работы с черновиками для web_form

Шаг 1: Проверка пользователя в CRM

  • n8n вызывает CreateWebContact с phone
  • Получает contact_id из CRM

Шаг 2: Поиск/создание пользователя в PostgreSQL

SQL запрос (аналогично Telegram):

WITH existing AS (
  SELECT u.id AS user_id, u.unified_id
  FROM clpr_user_accounts ua
  JOIN clpr_users u ON u.id = ua.user_id
  WHERE ua.channel = 'web_form'
    AND ua.channel_user_id = '{phone}'
  LIMIT 1
),
create_user AS (
  INSERT INTO clpr_users (unified_id, phone, created_at, updated_at)
  SELECT 'usr_' || gen_random_uuid()::text, '{phone}', now(), now()
  WHERE NOT EXISTS (SELECT 1 FROM existing)
  RETURNING id AS user_id, unified_id
),
final_user AS (
  SELECT * FROM existing
  UNION ALL
  SELECT * FROM create_user
),
create_account AS (
  INSERT INTO clpr_user_accounts(user_id, channel, channel_user_id)
  SELECT
    (SELECT user_id FROM final_user),
    'web_form',
    '{phone}'
  ON CONFLICT (channel, channel_user_id) DO NOTHING
)
SELECT unified_id FROM final_user LIMIT 1;

Шаг 3: Создание/обновление заявки

  • n8n создает/обновляет запись в clpr_claims
  • ВАЖНО: заполняет unified_id из результата шага 2
  • Сохраняет phone в payload->>'phone'
  • channel = 'web_form'
  • status_code = 'draft' для черновиков

Шаг 4: Поиск черновиков

SELECT 
  c.id,
  c.payload->>'claim_id' as claim_id,
  c.session_token,
  c.status_code,
  c.payload,
  c.created_at,
  c.updated_at
FROM clpr_claims c
WHERE c.status_code = 'draft'
  AND c.channel = 'web_form'
  AND c.unified_id = '{unified_id}'  -- ← ПОИСК ПО unified_id!
ORDER BY c.updated_at DESC
LIMIT 20;

Проблема в текущей реализации

Текущее состояние:

  • В clpr_claims поле unified_id ПУСТОЕ для всех черновиков web_form
  • Поиск идет по payload->>'phone' или session_token, что не надежно

Решение:

  • n8n должен заполнять unified_id при создании/обновлении заявки
  • Backend должен искать черновики по unified_id, а не по phone/session_id

Связи между таблицами

clpr_users (unified_id)
    ↑
    | (через unified_id)
    |
clpr_claims (unified_id)
    |
    | (через user_id)
    ↓
clpr_user_accounts (user_id → clpr_users.id)
    |
    | (channel='web_form', channel_user_id=phone)
    ↓
clpr_claims (payload->>'phone')

Для Telegram (для сравнения)

clpr_users (unified_id)
    ↑
    | (через unified_id)
    |
clpr_users_tg (unified_id)
    |
    | (telegram_id)
    ↓
clpr_user_accounts (channel='telegram', channel_user_id=telegram_id)
    |
    | (user_id)
    ↓
clpr_users (id)