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
219 lines
7.3 KiB
SQL
219 lines
7.3 KiB
SQL
-- ============================================================================
|
||
-- SQL запрос для n8n: Сохранение первичного черновика заявки
|
||
-- ============================================================================
|
||
-- Назначение: Сохранить первичный черновик сразу после генерации wizard_plan
|
||
-- Использует session_token для связи (claim_id генерируется позже)
|
||
--
|
||
-- Параметры:
|
||
-- $1 = payload_json (jsonb) - полный payload с wizard_plan, problem_description,
|
||
-- AI Agent1, AI Agent13 и т.д.
|
||
-- $2 = session_token (text) - сессия пользователя
|
||
-- $3 = unified_id (text, опционально) - unified_id пользователя
|
||
--
|
||
-- Возвращает:
|
||
-- claim - объект с claim_id (UUID), session_token, status_code, payload
|
||
--
|
||
-- Использование в n8n:
|
||
-- 1. PostgreSQL node
|
||
-- 2. Query Type: Execute Query
|
||
-- 3. Parameters:
|
||
-- $1 = {{ JSON.stringify({...}) }}
|
||
-- $2 = {{ $('Edit Fields11').first().json.session_token }}
|
||
-- $3 = {{ $('Edit Fields10').first().json.unified_id || null }}
|
||
-- ============================================================================
|
||
|
||
WITH partial AS (
|
||
SELECT
|
||
$1::jsonb AS p,
|
||
$2::text AS session_token_str,
|
||
NULLIF($3::text, '') AS unified_id_str
|
||
),
|
||
|
||
-- Находим существующую запись по session_token или создаем новую
|
||
claim_lookup AS (
|
||
SELECT
|
||
COALESCE(
|
||
(SELECT id FROM clpr_claims WHERE session_token = partial.session_token_str LIMIT 1),
|
||
gen_random_uuid()
|
||
) AS claim_uuid
|
||
FROM partial
|
||
),
|
||
|
||
-- Если записи нет, создаем её
|
||
claim_created AS (
|
||
INSERT INTO clpr_claims (
|
||
id,
|
||
session_token,
|
||
unified_id,
|
||
channel,
|
||
type_code,
|
||
status_code,
|
||
payload,
|
||
created_at,
|
||
updated_at,
|
||
expires_at
|
||
)
|
||
SELECT
|
||
claim_lookup.claim_uuid,
|
||
partial.session_token_str,
|
||
partial.unified_id_str,
|
||
'web_form',
|
||
COALESCE(partial.p->>'type_code', 'consumer'),
|
||
'draft',
|
||
jsonb_build_object(
|
||
-- claim_id будет сгенерирован позже, пока NULL
|
||
'claim_id', NULL,
|
||
'problem_description', partial.p->>'problem_description',
|
||
'wizard_plan',
|
||
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,
|
||
'answers_prefill',
|
||
CASE
|
||
WHEN partial.p->>'answers_prefill' IS NOT NULL
|
||
THEN (partial.p->>'answers_prefill')::jsonb
|
||
WHEN partial.p->'answers_prefill' IS NOT NULL AND jsonb_typeof(partial.p->'answers_prefill') = 'array'
|
||
THEN partial.p->'answers_prefill'
|
||
ELSE '[]'::jsonb
|
||
END,
|
||
'coverage_report',
|
||
CASE
|
||
WHEN partial.p->>'coverage_report' IS NOT NULL
|
||
THEN (partial.p->>'coverage_report')::jsonb
|
||
WHEN partial.p->'coverage_report' IS NOT NULL AND jsonb_typeof(partial.p->'coverage_report') = 'object'
|
||
THEN partial.p->'coverage_report'
|
||
ELSE NULL
|
||
END,
|
||
-- Данные из AI Agent1 (факты)
|
||
'ai_agent1_facts',
|
||
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'
|
||
ELSE NULL
|
||
END,
|
||
-- Данные из AI Agent13 (RAG ответ)
|
||
'ai_agent13_rag',
|
||
CASE
|
||
WHEN partial.p->>'ai_agent13_rag' IS NOT NULL
|
||
THEN (partial.p->>'ai_agent13_rag')::jsonb
|
||
WHEN partial.p->'ai_agent13_rag' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent13_rag') = 'object'
|
||
THEN partial.p->'ai_agent13_rag'
|
||
ELSE NULL
|
||
END,
|
||
'phone', partial.p->>'phone',
|
||
'email', partial.p->>'email'
|
||
),
|
||
now(),
|
||
now(),
|
||
now() + interval '14 days'
|
||
FROM partial, claim_lookup
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM clpr_claims WHERE id = claim_lookup.claim_uuid
|
||
)
|
||
ON CONFLICT (id) DO NOTHING
|
||
RETURNING id
|
||
),
|
||
|
||
-- Получаем финальный UUID
|
||
claim_final AS (
|
||
SELECT
|
||
CASE
|
||
WHEN EXISTS (SELECT 1 FROM claim_created)
|
||
THEN (SELECT id FROM claim_created LIMIT 1)
|
||
ELSE claim_lookup.claim_uuid
|
||
END AS claim_uuid
|
||
FROM claim_lookup
|
||
),
|
||
|
||
-- Обновляем существующую запись (если есть)
|
||
upd AS (
|
||
UPDATE clpr_claims c
|
||
SET
|
||
unified_id = COALESCE(partial.unified_id_str, c.unified_id),
|
||
payload = jsonb_set(
|
||
jsonb_set(
|
||
jsonb_set(
|
||
jsonb_set(
|
||
jsonb_set(
|
||
COALESCE(c.payload, '{}'::jsonb),
|
||
'{wizard_plan}',
|
||
COALESCE(
|
||
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,
|
||
c.payload->'wizard_plan'
|
||
),
|
||
true
|
||
),
|
||
'{ai_agent1_facts}',
|
||
COALESCE(
|
||
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'
|
||
ELSE NULL
|
||
END,
|
||
c.payload->'ai_agent1_facts'
|
||
),
|
||
true
|
||
),
|
||
'{ai_agent13_rag}',
|
||
COALESCE(
|
||
CASE
|
||
WHEN partial.p->>'ai_agent13_rag' IS NOT NULL
|
||
THEN (partial.p->>'ai_agent13_rag')::jsonb
|
||
WHEN partial.p->'ai_agent13_rag' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent13_rag') = 'object'
|
||
THEN partial.p->'ai_agent13_rag'
|
||
ELSE NULL
|
||
END,
|
||
c.payload->'ai_agent13_rag'
|
||
),
|
||
true
|
||
),
|
||
'{problem_description}',
|
||
COALESCE(partial.p->>'problem_description', c.payload->>'problem_description'),
|
||
true
|
||
),
|
||
'{answers_prefill}',
|
||
COALESCE(
|
||
CASE
|
||
WHEN partial.p->>'answers_prefill' IS NOT NULL
|
||
THEN (partial.p->>'answers_prefill')::jsonb
|
||
WHEN partial.p->'answers_prefill' IS NOT NULL AND jsonb_typeof(partial.p->'answers_prefill') = 'array'
|
||
THEN partial.p->'answers_prefill'
|
||
ELSE '[]'::jsonb
|
||
END,
|
||
c.payload->'answers_prefill',
|
||
'[]'::jsonb
|
||
),
|
||
true
|
||
),
|
||
updated_at = now(),
|
||
expires_at = now() + interval '14 days'
|
||
FROM partial, claim_final
|
||
WHERE c.id = claim_final.claim_uuid
|
||
AND EXISTS (SELECT 1 FROM claim_lookup WHERE claim_uuid = c.id)
|
||
RETURNING c.id, c.payload
|
||
)
|
||
|
||
SELECT
|
||
(SELECT jsonb_build_object(
|
||
'claim_id', COALESCE(u.id::text, cf.claim_uuid::text),
|
||
'session_token', partial.session_token_str,
|
||
'status_code', 'draft',
|
||
'payload', COALESCE(u.payload, jsonb_build_object())
|
||
)
|
||
FROM claim_final cf, partial
|
||
LEFT JOIN upd u ON true
|
||
LIMIT 1) AS claim;
|
||
|
||
|
||
|