fix: Исправление загрузки документов и SQL запросов
- Исправлена потеря документов при обновлении черновика (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
This commit is contained in:
345
docs/SQL_SAVE_DRAFT_NEW_FLOW.sql
Normal file
345
docs/SQL_SAVE_DRAFT_NEW_FLOW.sql
Normal file
@@ -0,0 +1,345 @@
|
||||
-- ============================================================================
|
||||
-- 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()
|
||||
-- }
|
||||
-- }
|
||||
-- };
|
||||
-- ============================================================================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user