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
214 lines
8.8 KiB
Markdown
214 lines
8.8 KiB
Markdown
# Анализ workflow 8ZVMTsuH7Cmw7snw и предложения
|
||
|
||
## Текущая структура
|
||
|
||
### Основные ноды PostgreSQL:
|
||
|
||
1. **`claimsave`** (строка 190-210)
|
||
- Использует обновленный SQL с `$2::text` (строка claim_id)
|
||
- **ПРОБЛЕМА**: SQL запрос не использует `claim_final` CTE, который я добавил в исправленной версии
|
||
- Это основная нода для сохранения данных визарда
|
||
|
||
2. **`claimsave_final`** (строка 428-450)
|
||
- Использует другой SQL запрос с `$2::uuid`
|
||
- Используется после конвертации файлов в PDF
|
||
- **ПРОБЛЕМА**: Ожидает UUID, но может получать строку
|
||
|
||
3. **`claimsave1`** (строка 634-655)
|
||
- Использует старый SQL запрос с `$2::uuid`
|
||
- **ПРОБЛЕМА**: Не работает со строковым claim_id
|
||
|
||
## Проблемы
|
||
|
||
### 1. SQL запрос в `claimsave` неполный
|
||
|
||
Текущий SQL в ноде `claimsave`:
|
||
- ✅ Использует `$2::text` (правильно)
|
||
- ✅ Имеет `claim_lookup` и `claim_created` CTE
|
||
- ❌ **НЕ использует `claim_final` CTE** (который я добавил в исправленной версии)
|
||
- ❌ Использует `claim_lookup.claim_uuid` напрямую, что может не работать, если запись была создана в `claim_created`
|
||
|
||
### 2. Несоответствие типов данных
|
||
|
||
- `claimsave` ожидает строку (`$2::text`)
|
||
- `claimsave_final` ожидает UUID (`$2::uuid`)
|
||
- `claimsave1` ожидает UUID (`$2::uuid`)
|
||
|
||
Но везде передается `claim_id` как строка `"CLM-2025-11-18-GEQ3KL"`.
|
||
|
||
### 3. Проблема с `existing` CTE
|
||
|
||
В текущем SQL запросе `existing` может не найти запись, если она была создана в `claim_created`, потому что:
|
||
- `claim_lookup` выполняется ДО `claim_created`
|
||
- `existing` использует `claim_lookup.claim_uuid`, но запись может быть создана в `claim_created`
|
||
|
||
## Решения
|
||
|
||
### Решение 1: Обновить SQL в ноде `claimsave`
|
||
|
||
Заменить SQL запрос на исправленную версию из `FIXED_SQL_QUERY.md`:
|
||
|
||
**Ключевые изменения:**
|
||
1. Добавить `claim_final` CTE для получения правильного UUID
|
||
2. Использовать `claim_final.claim_uuid` вместо `claim_lookup.claim_uuid`
|
||
3. Исправить `old` CTE, чтобы он всегда возвращал строку
|
||
|
||
### Решение 2: Унифицировать типы данных
|
||
|
||
**Вариант A**: Все ноды используют строку `claim_id`
|
||
- Изменить `claimsave_final` и `claimsave1` на `$2::text`
|
||
- Добавить логику поиска UUID по строке `claim_id`
|
||
|
||
**Вариант B**: Все ноды используют UUID
|
||
- Перед SQL запросами добавить Code Node, который:
|
||
- Находит запись в `clpr_claims` по `payload->>'claim_id'`
|
||
- Извлекает её `id` (UUID)
|
||
- Передает UUID в SQL запрос
|
||
|
||
**Рекомендую Вариант A** (использовать строку везде), т.к.:
|
||
- Проще реализовать
|
||
- Меньше изменений в workflow
|
||
- `claim_id` в формате `CLM-YYYY-MM-DD-XXXXXX` - это основной идентификатор
|
||
|
||
### Решение 3: Упростить логику
|
||
|
||
Можно упростить SQL запрос, убрав сложную логику слияния:
|
||
|
||
```sql
|
||
WITH partial AS (
|
||
SELECT $1::jsonb AS p, $2::text AS claim_id_str
|
||
),
|
||
|
||
-- Находим или создаем запись
|
||
claim_final 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_final.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,
|
||
'answers', COALESCE(partial.p->'answers', '{}'::jsonb),
|
||
'documents_meta', COALESCE(partial.p->'documents_meta', '[]'::jsonb),
|
||
'wizard_plan', partial.p->'wizard_plan',
|
||
'wizard_answers', partial.p->'wizard_answers',
|
||
'form_data', partial.p
|
||
),
|
||
now(), now(), now() + interval '14 days'
|
||
FROM partial, claim_final
|
||
WHERE NOT EXISTS (SELECT 1 FROM clpr_claims WHERE id = claim_final.claim_uuid)
|
||
ON CONFLICT (id) DO NOTHING
|
||
RETURNING id
|
||
),
|
||
|
||
-- Сохраняем документы
|
||
inserted_docs AS (
|
||
INSERT INTO clpr_claim_documents
|
||
(claim_id, field_name, file_id, uploaded_at, file_name, original_file_name)
|
||
SELECT
|
||
claim_final.claim_uuid::text,
|
||
doc.field_name, doc.file_id,
|
||
(doc.uploaded_at)::timestamptz,
|
||
doc.file_name, doc.original_file_name
|
||
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)
|
||
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
|
||
),
|
||
|
||
-- Обновляем запись (простое слияние)
|
||
upd AS (
|
||
UPDATE clpr_claims c
|
||
SET
|
||
payload = COALESCE(c.payload, '{}'::jsonb) || partial.p,
|
||
status_code = CASE
|
||
WHEN (partial.p->'answers'->>'docs_exist' = 'true') THEN 'in_work'
|
||
ELSE COALESCE(c.status_code, 'draft')
|
||
END,
|
||
updated_at = now(),
|
||
expires_at = now() + interval '14 days'
|
||
FROM partial, claim_final
|
||
WHERE c.id = claim_final.claim_uuid
|
||
RETURNING c.id, c.status_code, c.payload
|
||
)
|
||
|
||
SELECT
|
||
(SELECT jsonb_build_object(
|
||
'claim_id', u.id::text,
|
||
'claim_id_str', (u.payload->>'claim_id'),
|
||
'status_code', u.status_code,
|
||
'payload', u.payload
|
||
) FROM upd u LIMIT 1) AS claim,
|
||
(SELECT jsonb_agg(jsonb_build_object(
|
||
'id', id,
|
||
'field_name', field_name,
|
||
'file_id', file_id
|
||
)) FROM inserted_docs) AS documents;
|
||
```
|
||
|
||
## Рекомендации
|
||
|
||
### Немедленные действия:
|
||
|
||
1. **Обновить SQL в ноде `claimsave`**
|
||
- Заменить на исправленную версию из `FIXED_SQL_QUERY.md`
|
||
- Или использовать упрощенную версию выше
|
||
|
||
2. **Проверить параметры**
|
||
- Убедиться, что `queryReplacement` правильный: `={{ $json.payload_partial_json }}, {{ $json.claim_id }}`
|
||
- `payload_partial_json` должен быть JSON объектом
|
||
- `claim_id` должен быть строкой
|
||
|
||
3. **Протестировать**
|
||
- Запустить workflow с тестовыми данными
|
||
- Проверить, что `claim` не возвращает `null`
|
||
- Проверить, что документы сохраняются правильно
|
||
|
||
### Долгосрочные улучшения:
|
||
|
||
1. **Унифицировать все SQL запросы**
|
||
- Привести `claimsave_final` и `claimsave1` к единому формату
|
||
- Использовать строковый `claim_id` везде
|
||
|
||
2. **Добавить обработку ошибок**
|
||
- Проверять результат SQL запроса
|
||
- Логировать ошибки
|
||
- Возвращать понятные сообщения об ошибках
|
||
|
||
3. **Оптимизировать workflow**
|
||
- Упростить логику слияния payload
|
||
- Использовать транзакции для атомарности операций
|
||
|
||
## Готовый SQL для копирования
|
||
|
||
Полный исправленный SQL запрос находится в файле `FIXED_SQL_QUERY.md`.
|
||
|
||
Основные изменения:
|
||
- ✅ Использует `claim_final` CTE для правильного получения UUID
|
||
- ✅ `old` CTE всегда возвращает строку (даже если запись не найдена)
|
||
- ✅ Все подзапросы используют `LIMIT 1` для гарантии одной строки
|
||
- ✅ Правильное слияние `answers` и `documents_meta`
|
||
|
||
|
||
|