- Исправлена потеря документов при обновлении черновика (SQL объединяет вместо перезаписи) - Исправлено определение типа документа (приоритет field_label над field_name) - Исправлены дубликаты в documents_meta и documents_uploaded - Добавлена передача group_index с фронтенда для правильного field_name - Исправлены все документы в таблице clpr_claim_documents с правильными field_name - Обновлены SQL запросы: claimsave и claimsave_final для нового флоу - Добавлена поддержка multi-file upload для одного документа - Исправлены дубликаты в списке загруженных документов на фронтенде Файлы: - SQL: SQL_CLAIMSAVE_FIXED_NEW_FLOW.sql, SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED.sql - n8n: N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js (поддержка group_index) - Backend: documents.py (передача group_index в n8n) - Frontend: StepWizardPlan.tsx (передача group_index, исправление дубликатов) - Скрипты: fix_claim_documents_field_names.py, fix_documents_meta_duplicates.py Результат: документы больше не теряются, имеют правильные типы и field_name
346 lines
13 KiB
SQL
346 lines
13 KiB
SQL
-- ============================================================================
|
||
-- SQL запрос для n8n: Сохранение черновика (НОВЫЙ ФЛОУ с документами)
|
||
-- ============================================================================
|
||
-- Назначение: Сохранить черновик сразу после анализа описания проблемы
|
||
-- AI Agent возвращает facts + docs (список документов)
|
||
--
|
||
-- Вход от AI Agent:
|
||
-- output: { facts_short, facts_full, problem, recommendation, docs: [...] }
|
||
-- propertyName: { session_id, phone, unified_id, contact_id, ФИО и т.д. }
|
||
--
|
||
-- Параметры:
|
||
-- $1 = payload_json (jsonb) - полный payload с output и propertyName
|
||
-- $2 = session_token (text) - сессия пользователя (из propertyName.session_id)
|
||
-- $3 = unified_id (text) - unified_id пользователя
|
||
-- $4 = problem_description (text) - исходное описание проблемы от пользователя
|
||
--
|
||
-- Возвращает:
|
||
-- claim - объект с claim_id, session_token, status_code, documents_required
|
||
-- ============================================================================
|
||
|
||
WITH input_data AS (
|
||
SELECT
|
||
$1::jsonb AS payload,
|
||
$2::text AS session_token_str,
|
||
NULLIF($3::text, '') AS unified_id_str,
|
||
NULLIF($4::text, '') AS problem_desc
|
||
),
|
||
|
||
-- Извлекаем данные из payload
|
||
parsed_data AS (
|
||
SELECT
|
||
input_data.*,
|
||
input_data.payload->'output' AS ai_output,
|
||
input_data.payload->'propertyName' AS user_data,
|
||
input_data.payload->'output'->'docs' AS documents_required
|
||
FROM input_data
|
||
),
|
||
|
||
-- Проверяем существующий черновик по session_token
|
||
existing_claim AS (
|
||
SELECT id, payload
|
||
FROM clpr_claims
|
||
WHERE session_token = (SELECT session_token_str FROM input_data)
|
||
LIMIT 1
|
||
),
|
||
|
||
-- Генерируем или используем существующий UUID
|
||
claim_id_resolved AS (
|
||
SELECT
|
||
COALESCE(
|
||
(SELECT id FROM existing_claim),
|
||
gen_random_uuid()
|
||
) AS claim_uuid
|
||
),
|
||
|
||
-- INSERT или UPDATE черновика
|
||
upserted_claim AS (
|
||
INSERT INTO clpr_claims (
|
||
id,
|
||
session_token,
|
||
unified_id,
|
||
channel,
|
||
type_code,
|
||
status_code,
|
||
payload,
|
||
created_at,
|
||
updated_at,
|
||
expires_at
|
||
)
|
||
SELECT
|
||
claim_id_resolved.claim_uuid,
|
||
parsed_data.session_token_str,
|
||
COALESCE(parsed_data.unified_id_str, parsed_data.user_data->>'unified_id'),
|
||
'web_form',
|
||
'consumer',
|
||
'draft_new', -- ✅ Новый статус: только описание + документы
|
||
jsonb_build_object(
|
||
'claim_id', claim_id_resolved.claim_uuid::text,
|
||
'problem_description', COALESCE(parsed_data.problem_desc, parsed_data.user_data->>'problem_description'),
|
||
|
||
-- AI анализ
|
||
'ai_analysis', jsonb_build_object(
|
||
'facts_short', parsed_data.ai_output->>'facts_short',
|
||
'facts_full', parsed_data.ai_output->>'facts_full',
|
||
'problem', parsed_data.ai_output->>'problem',
|
||
'recommendation', parsed_data.ai_output->>'recommendation'
|
||
),
|
||
|
||
-- ✅ Список необходимых документов (новое!)
|
||
'documents_required', COALESCE(parsed_data.documents_required, '[]'::jsonb),
|
||
'documents_uploaded', '[]'::jsonb,
|
||
'documents_skipped', '[]'::jsonb,
|
||
'current_doc_index', 0,
|
||
|
||
-- Данные пользователя
|
||
'phone', COALESCE(parsed_data.user_data->>'phone', ''),
|
||
'email', COALESCE(parsed_data.user_data->>'email', ''),
|
||
'contact_id', parsed_data.user_data->>'contact_id',
|
||
|
||
-- ФИО и паспортные данные (для заявления)
|
||
'applicant', jsonb_build_object(
|
||
'lastname', parsed_data.user_data->>'lastname',
|
||
'firstname', parsed_data.user_data->>'firstname',
|
||
'middle_name', parsed_data.user_data->>'middle_name',
|
||
'birthday', parsed_data.user_data->>'birthday',
|
||
'birthplace', parsed_data.user_data->>'birthplace',
|
||
'inn', parsed_data.user_data->>'inn',
|
||
'address', parsed_data.user_data->>'mailingstreet',
|
||
'zip', parsed_data.user_data->>'mailingzip'
|
||
),
|
||
|
||
-- Telegram ID если есть
|
||
'tg_id', parsed_data.user_data->>'tg_id',
|
||
|
||
-- Флаги готовности
|
||
'wizard_ready', false,
|
||
'claim_ready', false
|
||
),
|
||
now(),
|
||
now(),
|
||
now() + interval '14 days'
|
||
FROM parsed_data, claim_id_resolved
|
||
ON CONFLICT (id) DO UPDATE SET
|
||
unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id),
|
||
status_code = 'draft_new',
|
||
payload = clpr_claims.payload || EXCLUDED.payload,
|
||
updated_at = now(),
|
||
expires_at = now() + interval '14 days'
|
||
RETURNING id, session_token, status_code, payload
|
||
)
|
||
|
||
-- Возвращаем результат для n8n
|
||
SELECT
|
||
jsonb_build_object(
|
||
'claim_id', upserted_claim.id::text,
|
||
'session_token', upserted_claim.session_token,
|
||
'status_code', upserted_claim.status_code,
|
||
'documents_required', upserted_claim.payload->'documents_required',
|
||
'documents_count', jsonb_array_length(COALESCE(upserted_claim.payload->'documents_required', '[]'::jsonb))
|
||
) AS claim
|
||
FROM upserted_claim;
|
||
|
||
|
||
-- ============================================================================
|
||
-- Пример вызова в n8n (PostgreSQL Node):
|
||
-- ============================================================================
|
||
--
|
||
-- Параметры:
|
||
-- $1 = {{ JSON.stringify($json) }} -- Весь payload от AI Agent
|
||
-- $2 = {{ $json.propertyName.session_id }} -- session_token
|
||
-- $3 = {{ $json.propertyName.unified_id }} -- unified_id
|
||
-- $4 = {{ $node["Redis Trigger"].json.description }} -- Исходное описание проблемы
|
||
--
|
||
-- После выполнения SQL, в Code Node пушим в Redis:
|
||
--
|
||
-- const result = $input.first().json.claim;
|
||
--
|
||
-- return {
|
||
-- json: {
|
||
-- channel: `ocr_events:${result.session_token}`,
|
||
-- event: {
|
||
-- event_type: 'documents_list_ready',
|
||
-- claim_id: result.claim_id,
|
||
-- session_id: result.session_token,
|
||
-- documents_required: result.documents_required,
|
||
-- documents_count: result.documents_count,
|
||
-- timestamp: new Date().toISOString()
|
||
-- }
|
||
-- }
|
||
-- };
|
||
-- ============================================================================
|
||
|
||
|
||
-- SQL запрос для n8n: Сохранение черновика (НОВЫЙ ФЛОУ с документами)
|
||
-- ============================================================================
|
||
-- Назначение: Сохранить черновик сразу после анализа описания проблемы
|
||
-- AI Agent возвращает facts + docs (список документов)
|
||
--
|
||
-- Вход от AI Agent:
|
||
-- output: { facts_short, facts_full, problem, recommendation, docs: [...] }
|
||
-- propertyName: { session_id, phone, unified_id, contact_id, ФИО и т.д. }
|
||
--
|
||
-- Параметры:
|
||
-- $1 = payload_json (jsonb) - полный payload с output и propertyName
|
||
-- $2 = session_token (text) - сессия пользователя (из propertyName.session_id)
|
||
-- $3 = unified_id (text) - unified_id пользователя
|
||
-- $4 = problem_description (text) - исходное описание проблемы от пользователя
|
||
--
|
||
-- Возвращает:
|
||
-- claim - объект с claim_id, session_token, status_code, documents_required
|
||
-- ============================================================================
|
||
|
||
WITH input_data AS (
|
||
SELECT
|
||
$1::jsonb AS payload,
|
||
$2::text AS session_token_str,
|
||
NULLIF($3::text, '') AS unified_id_str,
|
||
NULLIF($4::text, '') AS problem_desc
|
||
),
|
||
|
||
-- Извлекаем данные из payload
|
||
parsed_data AS (
|
||
SELECT
|
||
input_data.*,
|
||
input_data.payload->'output' AS ai_output,
|
||
input_data.payload->'propertyName' AS user_data,
|
||
input_data.payload->'output'->'docs' AS documents_required
|
||
FROM input_data
|
||
),
|
||
|
||
-- Проверяем существующий черновик по session_token
|
||
existing_claim AS (
|
||
SELECT id, payload
|
||
FROM clpr_claims
|
||
WHERE session_token = (SELECT session_token_str FROM input_data)
|
||
LIMIT 1
|
||
),
|
||
|
||
-- Генерируем или используем существующий UUID
|
||
claim_id_resolved AS (
|
||
SELECT
|
||
COALESCE(
|
||
(SELECT id FROM existing_claim),
|
||
gen_random_uuid()
|
||
) AS claim_uuid
|
||
),
|
||
|
||
-- INSERT или UPDATE черновика
|
||
upserted_claim AS (
|
||
INSERT INTO clpr_claims (
|
||
id,
|
||
session_token,
|
||
unified_id,
|
||
channel,
|
||
type_code,
|
||
status_code,
|
||
payload,
|
||
created_at,
|
||
updated_at,
|
||
expires_at
|
||
)
|
||
SELECT
|
||
claim_id_resolved.claim_uuid,
|
||
parsed_data.session_token_str,
|
||
COALESCE(parsed_data.unified_id_str, parsed_data.user_data->>'unified_id'),
|
||
'web_form',
|
||
'consumer',
|
||
'draft_new', -- ✅ Новый статус: только описание + документы
|
||
jsonb_build_object(
|
||
'claim_id', claim_id_resolved.claim_uuid::text,
|
||
'problem_description', COALESCE(parsed_data.problem_desc, parsed_data.user_data->>'problem_description'),
|
||
|
||
-- AI анализ
|
||
'ai_analysis', jsonb_build_object(
|
||
'facts_short', parsed_data.ai_output->>'facts_short',
|
||
'facts_full', parsed_data.ai_output->>'facts_full',
|
||
'problem', parsed_data.ai_output->>'problem',
|
||
'recommendation', parsed_data.ai_output->>'recommendation'
|
||
),
|
||
|
||
-- ✅ Список необходимых документов (новое!)
|
||
'documents_required', COALESCE(parsed_data.documents_required, '[]'::jsonb),
|
||
'documents_uploaded', '[]'::jsonb,
|
||
'documents_skipped', '[]'::jsonb,
|
||
'current_doc_index', 0,
|
||
|
||
-- Данные пользователя
|
||
'phone', COALESCE(parsed_data.user_data->>'phone', ''),
|
||
'email', COALESCE(parsed_data.user_data->>'email', ''),
|
||
'contact_id', parsed_data.user_data->>'contact_id',
|
||
|
||
-- ФИО и паспортные данные (для заявления)
|
||
'applicant', jsonb_build_object(
|
||
'lastname', parsed_data.user_data->>'lastname',
|
||
'firstname', parsed_data.user_data->>'firstname',
|
||
'middle_name', parsed_data.user_data->>'middle_name',
|
||
'birthday', parsed_data.user_data->>'birthday',
|
||
'birthplace', parsed_data.user_data->>'birthplace',
|
||
'inn', parsed_data.user_data->>'inn',
|
||
'address', parsed_data.user_data->>'mailingstreet',
|
||
'zip', parsed_data.user_data->>'mailingzip'
|
||
),
|
||
|
||
-- Telegram ID если есть
|
||
'tg_id', parsed_data.user_data->>'tg_id',
|
||
|
||
-- Флаги готовности
|
||
'wizard_ready', false,
|
||
'claim_ready', false
|
||
),
|
||
now(),
|
||
now(),
|
||
now() + interval '14 days'
|
||
FROM parsed_data, claim_id_resolved
|
||
ON CONFLICT (id) DO UPDATE SET
|
||
unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id),
|
||
status_code = 'draft_new',
|
||
payload = clpr_claims.payload || EXCLUDED.payload,
|
||
updated_at = now(),
|
||
expires_at = now() + interval '14 days'
|
||
RETURNING id, session_token, status_code, payload
|
||
)
|
||
|
||
-- Возвращаем результат для n8n
|
||
SELECT
|
||
jsonb_build_object(
|
||
'claim_id', upserted_claim.id::text,
|
||
'session_token', upserted_claim.session_token,
|
||
'status_code', upserted_claim.status_code,
|
||
'documents_required', upserted_claim.payload->'documents_required',
|
||
'documents_count', jsonb_array_length(COALESCE(upserted_claim.payload->'documents_required', '[]'::jsonb))
|
||
) AS claim
|
||
FROM upserted_claim;
|
||
|
||
|
||
-- ============================================================================
|
||
-- Пример вызова в n8n (PostgreSQL Node):
|
||
-- ============================================================================
|
||
--
|
||
-- Параметры:
|
||
-- $1 = {{ JSON.stringify($json) }} -- Весь payload от AI Agent
|
||
-- $2 = {{ $json.propertyName.session_id }} -- session_token
|
||
-- $3 = {{ $json.propertyName.unified_id }} -- unified_id
|
||
-- $4 = {{ $node["Redis Trigger"].json.description }} -- Исходное описание проблемы
|
||
--
|
||
-- После выполнения SQL, в Code Node пушим в Redis:
|
||
--
|
||
-- const result = $input.first().json.claim;
|
||
--
|
||
-- return {
|
||
-- json: {
|
||
-- channel: `ocr_events:${result.session_token}`,
|
||
-- event: {
|
||
-- event_type: 'documents_list_ready',
|
||
-- claim_id: result.claim_id,
|
||
-- session_id: result.session_token,
|
||
-- documents_required: result.documents_required,
|
||
-- documents_count: result.documents_count,
|
||
-- timestamp: new Date().toISOString()
|
||
-- }
|
||
-- }
|
||
-- };
|
||
-- ============================================================================
|
||
|
||
|