Files
aiform_dev/SESSION_LOG_2025-11-20.md
AI Assistant 0978e485dc feat: Add claim plan confirmation flow via Redis SSE
Problem:
- After wizard form submission, need to wait for claim data from n8n
- Claim data comes via Redis channel claim:plan:{session_token}
- Need to display confirmation form with claim data

Solution:
1. Backend: Added SSE endpoint /api/v1/claim-plan/{session_token}
   - Subscribes to Redis channel claim:plan:{session_token}
   - Streams claim data from n8n to frontend
   - Handles timeouts and errors gracefully

2. Frontend: Added subscription to claim:plan channel
   - StepWizardPlan: After form submission, subscribes to SSE
   - Waits for claim_plan_ready event
   - Shows loading message while waiting
   - On success: saves claimPlanData and shows confirmation step

3. New component: StepClaimConfirmation
   - Displays claim confirmation form in iframe
   - Receives claimPlanData from parent
   - Generates HTML form (placeholder - should call n8n for real HTML)
   - Handles confirmation/cancellation via postMessage

4. ClaimForm: Added conditional step for confirmation
   - Shows StepClaimConfirmation when showClaimConfirmation=true
   - Step appears after StepWizardPlan
   - Only visible when claimPlanData is available

Flow:
1. User fills wizard form → submits
2. Form data sent to n8n via /api/v1/claims/wizard
3. Frontend subscribes to SSE /api/v1/claim-plan/{session_token}
4. n8n processes data → publishes to Redis claim:plan:{session_token}
5. Backend receives → streams to frontend via SSE
6. Frontend receives → shows StepClaimConfirmation
7. User confirms → proceeds to next step

Files:
- backend/app/api/events.py: Added stream_claim_plan endpoint
- frontend/src/components/form/StepWizardPlan.tsx: Added subscribeToClaimPlan
- frontend/src/components/form/StepClaimConfirmation.tsx: New component
- frontend/src/pages/ClaimForm.tsx: Added confirmation step to steps array
2025-11-24 13:36:14 +03:00

347 lines
12 KiB
Markdown
Raw 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.

# Лог сессии: 2025-11-20 - Session Persistence & Draft Management
## Дата: 20 ноября 2025
---
## 🎯 Основные задачи
1. ✅ Реализация сохранения сессии в Redis
2. ✅ Восстановление сессии из localStorage после перезагрузки страницы
3. ✅ Исправление передачи `unified_id` и `claim_id` в n8n при отправке визарда
4. ✅ Исправление приоритета `session_id` при загрузке черновика
---
## 📝 Выполненные изменения
### 1. Backend - Session API (`/api/v1/session`)
**Файл:** `ticket_form/backend/app/api/session.py`
**Создан новый роутер** для управления сессиями в Redis:
- `POST /api/v1/session/create` - создание сессии с TTL 24 часа
- `POST /api/v1/session/verify` - проверка валидности сессии
- `POST /api/v1/session/logout` - удаление сессии
**Данные сессии:**
```python
{
"session_token": "sess_...",
"unified_id": "usr_...",
"phone": "79262306381",
"contact_id": "320096",
"verified_at": "2025-11-20T14:54:01.279Z",
"expires_at": "2025-11-21T14:54:01.279Z"
}
```
**Файл:** `ticket_form/backend/app/main.py`
- Добавлен импорт `session` роутера
- Подключен роутер: `app.include_router(session.router)`
---
### 2. Frontend - Session Management
**Файл:** `ticket_form/frontend/src/components/form/Step1Phone.tsx`
**Версия:** v2.0 - 2025-11-20 14:40
**Изменения:**
- После успешной SMS-верификации вызывается `POST /api/v1/session/create`
- `session_token` сохраняется в `localStorage`
- Добавлены подробные debug логи для отладки сессии
**Код:**
```typescript
// После получения unified_id от n8n
const sessionPayload = {
session_token: finalSessionId,
unified_id: unifiedIdToPass,
phone: formData.phone!,
contact_id: result.contact_id,
};
const sessionResponse = await fetch('/api/v1/session/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sessionPayload),
});
if (sessionResponse.ok) {
localStorage.setItem('session_token', finalSessionId);
}
```
---
### 3. Frontend - Session Restoration
**Файл:** `ticket_form/frontend/src/pages/ClaimForm.tsx`
**Версия:** v3.8 - 2025-11-20 15:10
**Изменения:**
#### A. Проверка сессии при загрузке компонента:
```typescript
useEffect(() => {
const sessionToken = localStorage.getItem('session_token');
if (!sessionToken) return;
// Проверяем валидность сессии
fetch('/api/v1/session/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_token: sessionToken }),
})
.then(res => res.json())
.then(data => {
if (data.success && data.valid) {
// Восстанавливаем данные сессии
updateFormData({
unified_id: data.unified_id,
phone: data.phone,
contact_id: data.contact_id,
});
setIsPhoneVerified(true);
checkDrafts(data.unified_id, data.phone, formData.session_id);
} else {
localStorage.removeItem('session_token');
}
});
}, []);
```
#### B. Кнопка "Выход":
```typescript
const handleExitToList = () => {
const sessionToken = localStorage.getItem('session_token');
if (sessionToken) {
fetch('/api/v1/session/logout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_token: sessionToken }),
});
localStorage.removeItem('session_token');
}
// Сброс формы
updateFormData({
unified_id: undefined,
phone: '',
contact_id: '',
});
setIsPhoneVerified(false);
setCurrentStep(0);
};
```
#### C. Исправление приоритета `session_id` при загрузке черновика:
**До:**
```typescript
session_id: claim.session_token || sessionIdRef.current, // ❌ Старый из черновика
```
**После:**
```typescript
session_id: sessionIdRef.current || formData.session_id, // ✅ Текущий актуальный
```
**Причина:** При загрузке черновика старый `session_id` из БД перезаписывал новый, полученный от n8n после SMS-верификации.
---
### 4. Frontend - Wizard Payload Fix
**Файл:** `ticket_form/frontend/src/components/form/StepWizardPlan.tsx`
**Версия:** v1.4 - 2025-11-20 15:00
**Проблема:** При отправке визарда в n8n не передавались `unified_id` и `claim_id`.
**Исправление:**
```typescript
// Добавляем unified_id и claim_id (если есть)
if (formData.unified_id) formPayload.append('unified_id', formData.unified_id);
if (formData.claim_id) formPayload.append('claim_id', formData.claim_id);
```
**Debug лог:**
```typescript
console.log('📤 Отправка в n8n:', {
session_id: formData.session_id,
unified_id: formData.unified_id,
claim_id: formData.claim_id,
contact_id: formData.contact_id,
phone: formData.phone,
});
```
---
### 5. Docker Volumes для Hot Module Replacement
**Файл:** `ticket_form/docker-compose.yml`
**Добавлен volume для фронтенда:**
```yaml
ticket_form_frontend:
volumes:
- ./frontend/src:/app/src:ro
```
**Цель:** Включить live reload (HMR) при изменении файлов фронтенда без пересборки контейнера.
---
## 🔄 Workflow изменений
### Полный цикл работы с сессией:
1. **Пользователь вводит телефон и SMS-код**
- → Step1Phone вызывает n8n для верификации
- → n8n возвращает `unified_id`, `contact_id`, `session_id`
- → Step1Phone создаёт сессию в Redis через `POST /api/v1/session/create`
-`session_token` сохраняется в `localStorage`
2. **Пользователь закрывает/обновляет страницу**
- → ClaimForm при загрузке проверяет `localStorage`
- → Вызывается `POST /api/v1/session/verify`
- → Если сессия валидна, восстанавливаются `unified_id`, `phone`, `contact_id`
- → Автоматически загружаются черновики
3. **Пользователь продолжает черновик**
- → При загрузке черновика используется ТЕКУЩИЙ `session_id` (не старый из БД)
- → При отправке визарда передаются `unified_id`, `claim_id`, актуальный `session_id`
4. **Пользователь нажимает "Выход"**
- → Вызывается `POST /api/v1/session/logout`
- → Сессия удаляется из Redis
-`session_token` удаляется из `localStorage`
- → Редирект на Step1Phone
---
## 🐛 Исправленные проблемы
### Проблема #1: Session token not found in localStorage
**Причина:** Backend эндпоинт `/api/v1/session/create` не был подключен.
**Решение:** Добавлен импорт и подключение роутера в `main.py`.
### Проблема #2: unified_id не передавался в n8n
**Причина:** В `StepWizardPlan.tsx` не было строки `formPayload.append('unified_id', ...)`.
**Решение:** Добавлена передача `unified_id` и `claim_id` в FormData.
### Проблема #3: Старый session_id перезаписывал новый
**Причина:** При загрузке черновика приоритет был у `claim.session_token` из БД.
**Решение:** Изменён приоритет на `sessionIdRef.current` (текущая сессия).
### Проблема #4: Frontend не обновлялся без пересборки
**Причина:** Docker контейнер не монтировал исходники фронтенда.
**Решение:** Добавлен volume `./frontend/src:/app/src:ro` для HMR.
---
## 📊 Результаты
### Payload в n8n после исправлений:
```json
{
"stage": "wizard",
"form_id": "ticket_form",
"session_id": "sess_e6e3f447-8770-47af-ae87-8c022c686d9f", Актуальный от n8n
"unified_id": "usr_90599ff2-ac79-4236-b950-0df85395096c", Добавлен
"claim_id": "19572ab7-cad5-4f8d-a622-4617487c07ce", Добавлен
"contact_id": "320096",
"phone": "79262306381",
"wizard_plan": "{...}",
"wizard_answers": "{...}",
"wizard_skipped_documents": "[]"
}
```
### Сессия в Redis (TTL 24 часа):
```
Key: crm:session:sess_e6e3f447-8770-47af-ae87-8c022c686d9f
Value: {
"session_token": "sess_e6e3f447-8770-47af-ae87-8c022c686d9f",
"unified_id": "usr_90599ff2-ac79-4236-b950-0df85395096c",
"phone": "79262306381",
"contact_id": "320096",
"verified_at": "2025-11-20T14:54:01.279Z",
"expires_at": "2025-11-21T14:54:01.279Z"
}
TTL: 86400 секунд
```
---
## 📦 Изменённые файлы
1.`ticket_form/backend/app/api/session.py` (создан)
2.`ticket_form/backend/app/main.py` (добавлен импорт session)
3.`ticket_form/frontend/src/components/form/Step1Phone.tsx` (v2.0)
4.`ticket_form/frontend/src/pages/ClaimForm.tsx` (v3.8)
5.`ticket_form/frontend/src/components/form/StepWizardPlan.tsx` (v1.4)
6.`ticket_form/docker-compose.yml` (добавлен volume)
---
## 🧪 Тестирование
### Сценарий 1: Новая сессия
- ✅ Ввод телефона и SMS-кода
- ✅ Создание сессии в Redis
- ✅ Сохранение session_token в localStorage
- ✅ Отображение черновиков (если есть)
### Сценарий 2: Восстановление сессии
- ✅ Ctrl+F5 (hard refresh)
- ✅ Автоматическая верификация сессии
- ✅ Восстановление unified_id, phone, contact_id
- ✅ Автоматическое отображение черновиков
### Сценарий 3: Продолжение черновика
- ✅ Выбор черновика из списка
- ✅ Загрузка данных черновика
- ✅ Сохранение актуального session_id (не старого из БД)
- ✅ Отправка в n8n с unified_id, claim_id, session_id
### Сценарий 4: Выход
- ✅ Нажатие кнопки "🚪 Выход"
- ✅ Удаление сессии из Redis
- ✅ Удаление session_token из localStorage
- ✅ Редирект на Step1Phone
---
## 🎉 Итоги
Реализован полноценный механизм управления сессиями:
- Персистентность через Redis (TTL 24 часа)
- Восстановление после перезагрузки страницы
- Корректная передача идентификаторов в n8n
- Безопасный выход с очисткой данных
Все изменения протестированы и готовы к продакшену! 🚀
---
## 📝 Следующие шаги (опционально)
1. Добавить обновление TTL сессии при активности пользователя
2. Реализовать уведомление о скором истечении сессии (за 5 минут)
3. Добавить мониторинг активных сессий в админке
4. Реализовать "запомнить меня" с увеличенным TTL (7 дней)
---
**Автор:** AI Assistant
**Дата:** 2025-11-20
**Статус:** ✅ Завершено