Files
aiform_dev/docs/CLAIMSAVE_FINAL_SQL.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

213 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Исправленный SQL для ноды `claimsave_final`
## Текущая проблема
Нода `claimsave_final` использует `$2::uuid`, но получает строку `"CLM-2025-11-18-GEQ3KL"`, что вызывает ошибку.
## Особенности `claimsave_final`
1. Используется **после конвертации файлов в PDF** и загрузки в S3
2. Работает с `file_url` (URL файла в S3)
3. Обновляет только `documents_meta` в payload (не трогает `answers`)
4. Использует динамический префикс таблицы (для разных схем)
## Исправленный SQL запрос
```sql
-- $1 = payload_partial_json (jsonb)
-- $2 = claim_id (text, например "CLM-2025-11-18-GEQ3KL")
WITH partial AS (
SELECT $1::jsonb AS p, $2::text AS claim_id_str
),
-- Находим UUID по строковому claim_id
claim_lookup AS (
SELECT
COALESCE(
(SELECT id FROM clpr_claims WHERE payload->>'claim_id' = partial.claim_id_str LIMIT 1),
gen_random_uuid()
) AS claim_uuid
FROM partial
),
-- Если записи нет, создаем её (на всякий случай)
claim_created AS (
INSERT INTO clpr_claims (
id,
session_token,
channel,
type_code,
status_code,
payload,
created_at,
updated_at,
expires_at
)
SELECT
claim_lookup.claim_uuid,
COALESCE(partial.p->>'session_id', 'sess-' || gen_random_uuid()::text),
'web_form',
COALESCE(partial.p->>'type_code', 'consumer'),
'draft',
jsonb_build_object(
'claim_id', partial.claim_id_str,
'documents_meta', COALESCE(partial.p->'documents_meta', '[]'::jsonb)
),
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
),
-- Извлекаем документы из payload
docs AS (
SELECT
claim_final.claim_uuid::text AS claim_id, -- преобразуем UUID в строку для clpr_claim_documents
doc.field_name::text,
doc.file_id::text,
doc.file_name::text,
doc.original_file_name::text,
(doc.uploaded_at)::timestamptz AS uploaded_at,
doc.file_url::text
FROM partial, claim_final
CROSS JOIN LATERAL jsonb_to_recordset(
COALESCE(partial.p->'documents_meta','[]'::jsonb)
) AS doc(
field_name text,
file_id text,
file_name text,
original_file_name text,
uploaded_at text,
file_url text
)
),
-- Сохраняем/обновляем документы
upsert_docs AS (
INSERT INTO clpr_claim_documents
(claim_id, field_name, file_id, uploaded_at, file_name, original_file_name)
SELECT
claim_id,
field_name,
file_id,
uploaded_at,
file_name,
original_file_name
FROM docs
ON CONFLICT (claim_id, field_name) DO UPDATE
SET file_id = EXCLUDED.file_id,
uploaded_at = EXCLUDED.uploaded_at,
file_name = EXCLUDED.file_name,
original_file_name = EXCLUDED.original_file_name
RETURNING id, claim_id, field_name, file_id
),
-- Обновляем payload (только documents_meta, не трогаем answers)
upd_claim AS (
UPDATE clpr_claims c
SET
payload = jsonb_set(
COALESCE(c.payload, '{}'::jsonb),
'{documents_meta}',
COALESCE((SELECT p->'documents_meta' FROM partial), '[]'::jsonb),
true
),
updated_at = now(),
expires_at = now() + interval '14 days'
FROM partial, claim_final
WHERE c.id = claim_final.claim_uuid
RETURNING c.id, c.payload
)
SELECT
(SELECT jsonb_build_object(
'claim_id', u.id::text,
'claim_id_str', (u.payload->>'claim_id'),
'payload', u.payload
) FROM upd_claim u LIMIT 1) AS claim,
(
SELECT jsonb_agg(
jsonb_build_object(
'id', u.id,
'field_name', u.field_name,
'file_id', u.file_id,
'file_url', d.file_url,
'file_name', d.file_name,
'original_file_name', d.original_file_name,
'uploaded_at', d.uploaded_at,
-- имя, которое безопасно отдавать во внешний API
'filename_for_upload',
COALESCE(
NULLIF(d.original_file_name, ''),
NULLIF(d.file_name, ''),
regexp_replace(d.file_id, '^.*/', '') -- хвост пути как запасной
)
)
)
FROM upsert_docs u
JOIN docs d
ON d.claim_id = u.claim_id
AND d.field_name = u.field_name
WHERE d.file_url IS NOT NULL AND d.file_url <> '' -- не показываем без URL
) AS documents;
```
## Изменения
1. **`$2::text` вместо `$2::uuid`**: Принимает строковый `claim_id`
2. **`claim_lookup` CTE**: Находит UUID по строковому `claim_id` из `payload->>'claim_id'`
3. **`claim_created` CTE**: Создает запись, если её нет (на всякий случай)
4. **`claim_final` CTE**: Получает финальный UUID (из созданной или существующей записи)
5. **`docs` CTE**: Преобразует UUID в строку для `clpr_claim_documents` (т.к. там `claim_id` имеет тип `character varying`)
6. **Убраны динамические префиксы**: Используется `clpr_claims` и `clpr_claim_documents` напрямую
## Параметры запроса
В n8n PostgreSQL Node:
```
Parameters:
$1 = {{ $json.payload_partial_json }} (JSONB)
$2 = {{ $json.claim_id }} (TEXT, строка "CLM-2025-11-18-GEQ3KL")
```
## Если нужен динамический префикс
Если всё-таки нужен динамический префикс таблицы (как в оригинале), можно использовать:
```sql
-- Вместо clpr_claims использовать:
{{ $('Edit Fields').item.json.propertyName.prefix }}claims
-- Вместо clpr_claim_documents использовать:
{{ $('Edit Fields').item.json.propertyName.prefix }}claim_documents
```
Но для `ticket_form` это не нужно, т.к. мы всегда работаем с `clpr_*` таблицами.
## Отличия от `claimsave`
1. **`claimsave`**: Сохраняет данные визарда (answers, wizard_plan, wizard_answers)
2. **`claimsave_final`**: Обновляет только `documents_meta` после обработки файлов, добавляет `file_url`
Оба запроса теперь используют строковый `claim_id` и правильно находят UUID.