Files
aiform_dev/docs/SQL_CLAIMSAVE_PRIMARY_DRAFT_CLEAN.sql
AI Assistant 3621ae6021 feat: Session persistence with Redis + Draft management fixes
- Implement session management API (/api/v1/session/create, verify, logout)
- Add session restoration from localStorage on page reload
- Fix session_id priority when loading drafts (use current, not old from DB)
- Add unified_id and claim_id to wizard payload sent to n8n
- Add Docker volume for frontend HMR (Hot Module Replacement)
- Add comprehensive session logging for debugging

Components updated:
- backend/app/api/session.py (NEW) - Session management endpoints
- backend/app/main.py - Include session router
- frontend/src/components/form/Step1Phone.tsx v2.0 - Create session after SMS
- frontend/src/pages/ClaimForm.tsx v3.8 - Session restoration & priority fix
- frontend/src/components/form/StepWizardPlan.tsx v1.4 - Add unified_id/claim_id
- docker-compose.yml - Add frontend volume for live reload

Session flow:
1. User verifies phone -> session created in Redis (24h TTL)
2. session_token saved to localStorage
3. Page reload -> session restored automatically
4. Draft selected -> current session_id used (not old from DB)
5. Wizard submit -> unified_id, claim_id, session_id sent to n8n
6. Logout -> session removed from Redis & localStorage

Fixes:
- Session token not persisting after page reload
- unified_id missing in n8n webhook payload
- Old session_id from draft overwriting current session
- Frontend changes requiring container rebuild
2025-11-20 18:31:42 +03:00

211 lines
6.9 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- ============================================================================
-- SQL запрос для n8n: Сохранение первичного черновика заявки (ЧИСТАЯ ВЕРСИЯ)
-- ============================================================================
-- Назначение: Сохранить первичный черновик сразу после генерации wizard_plan
-- Использует session_token для связи (claim_id генерируется позже)
--
-- Параметры:
-- $1 = payload_json (jsonb) - полный payload с wizard_plan, problem_description,
-- AI Agent1, AI Agent13 и т.д.
-- $2 = session_token (text) - сессия пользователя
-- $3 = unified_id (text, опционально) - unified_id пользователя
--
-- Возвращает:
-- claim - объект с claim_id (UUID), session_token, status_code, payload
-- ============================================================================
WITH partial AS (
SELECT
$1::jsonb AS p,
$2::text AS session_token_str,
NULLIF($3::text, '') AS unified_id_str
),
claim_lookup AS (
SELECT
COALESCE(
(SELECT id FROM clpr_claims WHERE session_token = partial.session_token_str LIMIT 1),
gen_random_uuid()
) AS claim_uuid
FROM partial
),
claim_created AS (
INSERT INTO clpr_claims (
id,
session_token,
unified_id,
channel,
type_code,
status_code,
payload,
created_at,
updated_at,
expires_at
)
SELECT
claim_lookup.claim_uuid,
partial.session_token_str,
partial.unified_id_str,
'web_form',
COALESCE(partial.p->>'type_code', 'consumer'),
'draft',
jsonb_build_object(
'claim_id', NULL,
'problem_description', partial.p->>'problem_description',
'wizard_plan',
CASE
WHEN partial.p->>'wizard_plan' IS NOT NULL
THEN (partial.p->>'wizard_plan')::jsonb
WHEN partial.p->'wizard_plan' IS NOT NULL AND jsonb_typeof(partial.p->'wizard_plan') = 'object'
THEN partial.p->'wizard_plan'
ELSE NULL
END,
'answers_prefill',
CASE
WHEN partial.p->>'answers_prefill' IS NOT NULL
THEN (partial.p->>'answers_prefill')::jsonb
WHEN partial.p->'answers_prefill' IS NOT NULL AND jsonb_typeof(partial.p->'answers_prefill') = 'array'
THEN partial.p->'answers_prefill'
ELSE '[]'::jsonb
END,
'coverage_report',
CASE
WHEN partial.p->>'coverage_report' IS NOT NULL
THEN (partial.p->>'coverage_report')::jsonb
WHEN partial.p->'coverage_report' IS NOT NULL AND jsonb_typeof(partial.p->'coverage_report') = 'object'
THEN partial.p->'coverage_report'
ELSE NULL
END,
'ai_agent1_facts',
CASE
WHEN partial.p->'ai_agent1_facts' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent1_facts') = 'object'
THEN partial.p->'ai_agent1_facts'
ELSE NULL
END,
'ai_agent13_rag',
CASE
WHEN partial.p->'ai_agent13_rag' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent13_rag') = 'object'
THEN partial.p->'ai_agent13_rag'
WHEN partial.p->>'ai_agent13_rag' IS NOT NULL
THEN to_jsonb(partial.p->>'ai_agent13_rag')
ELSE NULL
END,
'phone', partial.p->>'phone',
'email', partial.p->>'email'
),
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, status_code, payload
),
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
),
upd AS (
UPDATE clpr_claims c
SET
unified_id = COALESCE(partial.unified_id_str, c.unified_id),
payload = jsonb_set(
jsonb_set(
jsonb_set(
jsonb_set(
jsonb_set(
COALESCE(c.payload, '{}'::jsonb),
'{wizard_plan}',
COALESCE(
CASE
WHEN partial.p->>'wizard_plan' IS NOT NULL
THEN (partial.p->>'wizard_plan')::jsonb
WHEN partial.p->'wizard_plan' IS NOT NULL AND jsonb_typeof(partial.p->'wizard_plan') = 'object'
THEN partial.p->'wizard_plan'
ELSE NULL
END,
c.payload->'wizard_plan'
),
true
),
'{ai_agent1_facts}',
COALESCE(
CASE
WHEN partial.p->'ai_agent1_facts' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent1_facts') = 'object'
THEN partial.p->'ai_agent1_facts'
ELSE NULL
END,
c.payload->'ai_agent1_facts'
),
true
),
'{ai_agent13_rag}',
COALESCE(
CASE
WHEN partial.p->'ai_agent13_rag' IS NOT NULL AND jsonb_typeof(partial.p->'ai_agent13_rag') = 'object'
THEN partial.p->'ai_agent13_rag'
WHEN partial.p->>'ai_agent13_rag' IS NOT NULL
THEN to_jsonb(partial.p->>'ai_agent13_rag')
ELSE NULL
END,
c.payload->'ai_agent13_rag'
),
true
),
'{problem_description}',
to_jsonb(COALESCE(partial.p->>'problem_description', c.payload->>'problem_description')),
true
),
'{answers_prefill}',
COALESCE(
CASE
WHEN partial.p->>'answers_prefill' IS NOT NULL
THEN (partial.p->>'answers_prefill')::jsonb
WHEN partial.p->'answers_prefill' IS NOT NULL AND jsonb_typeof(partial.p->'answers_prefill') = 'array'
THEN partial.p->'answers_prefill'
ELSE '[]'::jsonb
END,
c.payload->'answers_prefill',
'[]'::jsonb
),
true
),
updated_at = now(),
expires_at = now() + interval '14 days'
FROM partial, claim_final
WHERE c.id = claim_final.claim_uuid
AND EXISTS (SELECT 1 FROM claim_lookup WHERE claim_uuid = c.id)
RETURNING c.id, c.status_code, c.payload
)
SELECT
jsonb_build_object(
'claim_id', COALESCE(
(SELECT id::text FROM upd LIMIT 1),
(SELECT claim_uuid::text FROM claim_final LIMIT 1),
(SELECT id::text FROM claim_created LIMIT 1)
),
'session_token', (SELECT session_token_str FROM partial LIMIT 1),
'status_code', COALESCE(
(SELECT status_code FROM upd LIMIT 1),
(SELECT status_code FROM claim_created LIMIT 1),
'draft'
),
'payload', COALESCE(
(SELECT payload FROM upd LIMIT 1),
(SELECT payload FROM claim_created LIMIT 1),
jsonb_build_object()
)
) AS claim;