# Исправленный 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.