-- Исправленный SQL для сохранения документов (claimsave_final) -- ✅ ИСПРАВЛЕНО: Ищем запись и по ID, и по payload->>'claim_id', чтобы избежать дубликатов -- $1 = payload_partial_json (jsonb) -- $2 = claim_id (text или uuid в виде строки) WITH partial AS ( SELECT $1::jsonb AS p, $2::text AS claim_id_str ), -- ✅ ИСПРАВЛЕНО: Ищем запись и по ID, и по payload->>'claim_id' claim_lookup AS ( SELECT c.id, c.payload FROM clpr_claims c, partial WHERE c.id::text = partial.claim_id_str OR c.payload->>'claim_id' = partial.claim_id_str ORDER BY CASE WHEN c.id::text = partial.claim_id_str THEN 1 ELSE 2 END, c.updated_at DESC LIMIT 1 ), docs AS ( SELECT claim_lookup.id::text AS claim_id, -- uuid -> text для clpr_claim_documents doc.field_name::text AS field_name, doc.file_id::text AS file_id, doc.file_name::text AS file_name, doc.original_file_name::text AS original_file_name, (doc.uploaded_at)::timestamptz AS uploaded_at, doc.file_url::text AS file_url FROM partial, claim_lookup 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) SELECT claim_id, field_name, file_id, uploaded_at FROM docs ON CONFLICT (claim_id, field_name) DO UPDATE SET file_id = EXCLUDED.file_id, uploaded_at = EXCLUDED.uploaded_at RETURNING id, claim_id, field_name, file_id ), -- ✅ ИСПРАВЛЕНО: Используем ID из claim_lookup вместо partial.cid 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_lookup WHERE c.id = claim_lookup.id RETURNING c.id, c.payload ) SELECT (SELECT jsonb_build_object('claim_id', u.id, 'payload', u.payload) FROM upd_claim u) 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; /* ============================================================================ ИСПРАВЛЕНИЯ (2025-11-24): ============================================================================ ПРОБЛЕМА: Запрос искал запись только по ID (WHERE c.id = partial.cid) Если запись была создана с другим ID, но с тем же claim_id в payload, она не находилась и не обновлялась. РЕШЕНИЕ: 1. Добавлен CTE claim_lookup - ищет запись и по ID, и по payload->>'claim_id': WHERE id::text = claim_id_str OR payload->>'claim_id' = claim_id_str 2. Используется найденный ID из claim_lookup вместо partial.cid 3. Это гарантирует, что всегда используется правильная запись ИЗМЕНЕНИЯ: - partial теперь принимает $2::text (вместо $2::uuid) - Добавлен claim_lookup CTE для поиска записи - upd_claim использует claim_lookup.id вместо partial.cid - docs использует claim_lookup.id вместо partial.cid ============================================================================ ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ: ============================================================================ 1. С claim_id как UUID строкой: $1 = '{"documents_meta": [...]}'::jsonb $2 = '0eb051ec-23a6-4e06-8b98-f02d20d35f68'::text 2. С claim_id как UUID из payload: $1 = '{"documents_meta": [...]}'::jsonb $2 = '0eb051ec-23a6-4e06-8b98-f02d20d35f68'::text (запрос найдет запись по payload->>'claim_id', если ID не совпадает) ============================================================================ */