- Добавлены логи в frontend (ClaimForm.tsx) для отслеживания unified_id и запросов к API - Добавлены логи в backend (claims.py) для отладки SQL запросов - Создан лог сессии с описанием проблемы и текущего состояния - Проблема: API возвращает 0 черновиков, хотя в БД есть данные
1160 lines
38 KiB
Markdown
1160 lines
38 KiB
Markdown
# 📋 Лог сессии: CreateWebProject + Интеграция SMS → CRM
|
||
|
||
**Дата:** 01 ноября 2025 (10:00 - 13:30 MSK)
|
||
**Задачи:**
|
||
1. Создание операции CreateWebProject для vTiger CRM
|
||
2. Исправление валидации SMS кодов
|
||
3. Интеграция n8n webhook для создания контакта после SMS верификации
|
||
|
||
**Статус:** ✅ Успешно завершено
|
||
|
||
---
|
||
|
||
## 🎯 Основные задачи
|
||
|
||
### Задача 1: CreateWebProject
|
||
Создать операцию vTiger webservice для создания проекта по аналогии с CreateWebContact.
|
||
|
||
**Требования:**
|
||
- Обязательные поля: `policy_number` (cf_1885), `contact_id`
|
||
- Опциональные: `period_start` (cf_1887), `period_end` (cf_1889)
|
||
- Логика: если проект с таким полисом существует → возврат ID без обновления
|
||
- Если не существует → создание нового
|
||
- Возврат: `{"project_id": "123", "is_new": true/false}`
|
||
|
||
### Задача 2: SMS валидация
|
||
Исправить проблему с валидацией SMS кодов (формат телефона).
|
||
|
||
### Задача 3: n8n интеграция
|
||
Добавить вызов n8n webhook после SMS верификации для создания контакта в CRM.
|
||
|
||
---
|
||
|
||
## ✅ Выполненные задачи
|
||
|
||
### 1. CreateWebProject.php - Операция vTiger Webservice
|
||
|
||
**Файл:** `include/Webservices/CreateWebProject.php`
|
||
|
||
**Обязательные параметры:**
|
||
- `policy_number` - номер полиса ERV (cf_1885)
|
||
- `contact_id` - ID контакта для привязки
|
||
|
||
**Опциональные параметры:**
|
||
- `period_start` - дата начала страхования (cf_1887)
|
||
- `period_end` - дата окончания страхования (cf_1889)
|
||
|
||
**Логика работы:**
|
||
```php
|
||
1. Ищем проект по номеру полиса (cf_1885):
|
||
SELECT p.projectid FROM vtiger_project p
|
||
INNER JOIN vtiger_projectcf pcf ON p.projectid = pcf.projectid
|
||
WHERE e.deleted = 0 AND pcf.cf_1885 = 'E1000-123456789'
|
||
|
||
2. Если найден → возвращаем ID БЕЗ обновления:
|
||
{"project_id": "396865", "is_new": false}
|
||
|
||
3. Если НЕ найден → создаём новый:
|
||
- projectname: "ERV E1000-123456789 цифровой адвокат"
|
||
- projectstatus: "модерация"
|
||
- projecttype: "ерв урегулирование"
|
||
- linktoaccountscontacts: "12x{contact_id}"
|
||
- cf_1994: "11x67458" (Заявитель - контрагент)
|
||
- cf_1885: номер полиса
|
||
|
||
{"project_id": "396866", "is_new": true}
|
||
```
|
||
|
||
**Регистрация в БД:**
|
||
```sql
|
||
-- vtiger_ws_operation
|
||
INSERT INTO vtiger_ws_operation (
|
||
operationid, name, handler_path, handler_method, type, prelogin
|
||
) VALUES (
|
||
51,
|
||
'CreateWebProject',
|
||
'include/Webservices/CreateWebProject.php',
|
||
'vtws_createwebproject',
|
||
'POST',
|
||
0
|
||
);
|
||
|
||
-- vtiger_ws_operation_parameters
|
||
INSERT INTO vtiger_ws_operation_parameters (operationid, name, type, sequence)
|
||
VALUES
|
||
(51, 'policy_number', 'String', 1),
|
||
(51, 'contact_id', 'String', 2),
|
||
(51, 'period_start', 'String', 3),
|
||
(51, 'period_end', 'String', 4);
|
||
```
|
||
|
||
**Тестирование:**
|
||
```bash
|
||
# Тест 1: Создание нового проекта
|
||
Policy: E1000-TEST-1761990646
|
||
Contact: 396625
|
||
Result: {"project_id":"396865","is_new":true} ✅
|
||
|
||
# Тест 2: Повторный вызов (поиск существующего)
|
||
Policy: E1000-TEST-1761990646
|
||
Contact: 396625
|
||
Result: {"project_id":"396865","is_new":false} ✅
|
||
```
|
||
|
||
**Логи:** `logs/CreateWebProject.log`
|
||
|
||
---
|
||
|
||
### 2. Исправление валидации SMS кодов
|
||
|
||
**Проблема:**
|
||
```
|
||
При отправке: ключ в Redis = erv:sms_verify:+79262306381 (С ПЛЮСОМ)
|
||
При проверке: поиск ключа = sms_verify:79262306381 (БЕЗ ПЛЮСА)
|
||
Результат: "No verification code found" ❌
|
||
```
|
||
|
||
**Причина:** Несоответствие формата телефона между отправкой и проверкой.
|
||
|
||
**Решение:**
|
||
|
||
**Файл:** `backend/app/services/sms_service.py`
|
||
|
||
```python
|
||
async def send_verification_code(self, phone: str) -> Optional[str]:
|
||
# Нормализуем формат телефона (убираем + если есть)
|
||
phone = phone.replace("+", "").replace("-", "").replace(" ", "")
|
||
|
||
verification_key = f"sms_verify:{phone}" # → sms_verify:79262306381
|
||
await redis_service.set(verification_key, code, expire=600)
|
||
...
|
||
|
||
async def verify_code(self, phone: str, code: str) -> bool:
|
||
# Нормализуем формат телефона (убираем + если есть)
|
||
phone = phone.replace("+", "").replace("-", "").replace(" ", "")
|
||
|
||
verification_key = f"sms_verify:{phone}" # → sms_verify:79262306381
|
||
stored_code = await redis_service.get(verification_key)
|
||
...
|
||
```
|
||
|
||
**Дополнительно:**
|
||
- Отключен rate limiting (60 сек задержка) для тестирования
|
||
- Добавлено детальное логирование сравнения кодов
|
||
- Backend подключён к внешнему Redis: `crm.clientright.ru:6379`
|
||
|
||
**Проверка:**
|
||
```bash
|
||
# До исправления
|
||
redis-cli> GET "erv:sms_verify:+79262306381" # Ключ с +
|
||
"123456"
|
||
# Проверка ищет без + → не находит ❌
|
||
|
||
# После исправления
|
||
redis-cli> GET "erv:sms_verify:79262306381" # Ключ без +
|
||
"123456"
|
||
# Проверка ищет без + → находит ✅
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Интеграция n8n webhook после SMS верификации
|
||
|
||
**Проблема:** После SMS верификации контакт не создавался в CRM автоматически.
|
||
|
||
**Решение:**
|
||
|
||
**Файл:** `frontend/src/components/form/Step1Phone.tsx`
|
||
|
||
```typescript
|
||
// После успешной SMS верификации
|
||
if (response.ok) {
|
||
addDebugEvent?.('sms', 'success', `✅ Телефон подтвержден успешно`);
|
||
message.success('Телефон подтвержден!');
|
||
setIsPhoneVerified(true);
|
||
|
||
// 🆕 Вызов n8n webhook для создания контакта
|
||
try {
|
||
addDebugEvent?.('crm', 'info', '📞 Создание контакта в CRM...');
|
||
|
||
const crmResponse = await fetch(
|
||
'https://n8n.clientright.pro/webhook/511fde97-88bb-4fb4-bea5-cafdc364be27',
|
||
{
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ phone }) // 79001234567
|
||
}
|
||
);
|
||
|
||
const crmResult = await crmResponse.json();
|
||
|
||
if (crmResponse.ok) {
|
||
addDebugEvent?.('crm', 'success', `✅ Контакт создан/найден в CRM`, crmResult);
|
||
|
||
// Сохраняем данные из CRM в форму
|
||
updateFormData({
|
||
phone,
|
||
contact_id: crmResult.contact_id,
|
||
claim_id: crmResult.claim_id,
|
||
is_new_contact: crmResult.is_new_contact
|
||
});
|
||
|
||
message.success(crmResult.is_new_contact ? 'Контакт создан!' : 'Контакт найден!');
|
||
onNext();
|
||
} else {
|
||
addDebugEvent?.('crm', 'error', '❌ Ошибка создания контакта в CRM');
|
||
message.error('Ошибка создания контакта в CRM');
|
||
}
|
||
} catch (crmError) {
|
||
addDebugEvent?.('crm', 'error', '❌ Ошибка соединения с CRM');
|
||
message.error('Ошибка соединения с CRM');
|
||
}
|
||
}
|
||
```
|
||
|
||
**Workflow n8n (get_contact_CRM):**
|
||
```
|
||
Webhook: https://n8n.clientright.pro/webhook/511fde97-88bb-4fb4-bea5-cafdc364be27
|
||
|
||
Input: {"phone": "79001234567"}
|
||
|
||
Флоу:
|
||
1. Edit Fields (извлечение phone)
|
||
↓
|
||
2. Get Challenge (vTiger webservice)
|
||
↓
|
||
3. Execute a command (md5 hash)
|
||
↓
|
||
4. Login to CRM
|
||
↓
|
||
5. CreateWebContact (создание/поиск контакта)
|
||
↓
|
||
6. Code in JavaScript (парсинг JSON + генерация claim_id)
|
||
↓
|
||
7. Redis (сохранение session:claim:{claim_id})
|
||
↓
|
||
8. Respond to Webhook
|
||
|
||
Output: {
|
||
"claim_id": "CLM-2025-11-01-XXXXX",
|
||
"contact_id": "396625",
|
||
"is_new_contact": false,
|
||
"phone": "79001234567"
|
||
}
|
||
```
|
||
|
||
**Redis Session:**
|
||
```
|
||
Ключ: claim:CLM-2025-11-01-IWR1U2
|
||
TTL: 604800 секунд (7 дней)
|
||
Значение: {
|
||
"claim_id": "CLM-2025-11-01-IWR1U2",
|
||
"contact_id": "396625",
|
||
"phone": "79001234567",
|
||
"is_new_contact": false,
|
||
"status": "draft",
|
||
"current_step": 1,
|
||
"created_at": "2025-11-01T10:15:32.123Z",
|
||
"updated_at": "2025-11-01T10:15:32.123Z",
|
||
"voucher": null,
|
||
"event_type": null,
|
||
"documents": {}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. Обновлён FormData интерфейс
|
||
|
||
**Файл:** `frontend/src/pages/ClaimForm.tsx`
|
||
|
||
```typescript
|
||
interface FormData {
|
||
// 🆕 Шаг 1: Phone
|
||
phone?: string;
|
||
contact_id?: string;
|
||
is_new_contact?: boolean;
|
||
|
||
// Шаг 2: Policy
|
||
voucher: string;
|
||
claim_id?: string;
|
||
session_id?: string;
|
||
|
||
// Шаг 3: Event Type
|
||
eventType?: string;
|
||
|
||
// Шаги 4+: Documents
|
||
documents?: Record<string, {...}>;
|
||
|
||
// Последний шаг: Payment
|
||
fullName?: string;
|
||
email?: string;
|
||
paymentMethod?: string;
|
||
bankName?: string;
|
||
cardNumber?: string;
|
||
accountNumber?: string;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5. Исправлён docker-compose.yml
|
||
|
||
**Проблемы:**
|
||
1. Backend пытался подключиться к локальному Redis (localhost:6379)
|
||
2. Попытка запуска локальных контейнеров redis/postgres, которые не нужны
|
||
|
||
**Решение:**
|
||
|
||
```yaml
|
||
backend:
|
||
build: ./backend
|
||
ports:
|
||
- "8100:8100"
|
||
environment:
|
||
# 🆕 Подключение к внешнему Redis
|
||
- REDIS_HOST=crm.clientright.ru
|
||
- REDIS_PORT=6379
|
||
- REDIS_PASSWORD=CRM_Redis_Pass_2025_Secure!
|
||
- POSTGRES_URL=postgresql://erv_user:erv_password@postgres:5432/erv_db
|
||
- RABBITMQ_URL=amqp://admin:tyejvtej@185.197.75.249:5672
|
||
# 🆕 Убраны зависимости от локальных сервисов
|
||
# depends_on:
|
||
# - redis
|
||
# - postgres
|
||
networks:
|
||
- erv-network
|
||
restart: unless-stopped
|
||
```
|
||
|
||
**Статус сервисов:**
|
||
- ✅ Backend подключён к внешнему Redis (crm.clientright.ru:6379)
|
||
- ✅ Backend подключён к внешнему PostgreSQL (147.45.189.234:5432)
|
||
- ✅ RabbitMQ подключён (185.197.75.249:5672)
|
||
- ✅ S3 подключён (Timeweb Cloud Storage)
|
||
|
||
---
|
||
|
||
## 🐛 Исправленные проблемы
|
||
|
||
### Проблема 1: UNKNOWN_OPERATION для CreateWebProject
|
||
|
||
**Симптом:**
|
||
```json
|
||
{"success":false,"error":{"code":"UNKNOWN_OPERATION","message":"Unknown operation requested"}}
|
||
```
|
||
|
||
**Причина:** Неправильная структура регистрации в БД.
|
||
|
||
**Было:**
|
||
```sql
|
||
INSERT INTO vtiger_ws_operation
|
||
VALUES (51, 'CreateWebProject', 'include/Webservices/CreateWebProject.php', ...)
|
||
-- Поле 'handler' вместо 'handler_path' и 'handler_method'
|
||
```
|
||
|
||
**Стало:**
|
||
```sql
|
||
INSERT INTO vtiger_ws_operation (
|
||
operationid, name, handler_path, handler_method, type, prelogin
|
||
) VALUES (
|
||
51,
|
||
'CreateWebProject',
|
||
'include/Webservices/CreateWebProject.php',
|
||
'vtws_createwebproject', -- ⭐ Имя функции!
|
||
'POST',
|
||
0
|
||
);
|
||
```
|
||
|
||
**Решение:** Используем правильные поля `handler_path` и `handler_method`.
|
||
|
||
---
|
||
|
||
### Проблема 2: BOM символ в CreateWebProject.php
|
||
|
||
**Симптом:**
|
||
```json
|
||
{"success":true,"result":...}
|
||
```
|
||
|
||
**Причина:** Файл сохранён с UTF-8 BOM.
|
||
|
||
**Решение:**
|
||
```bash
|
||
sed -i '1s/^\xEF\xBB\xBF//' include/Webservices/CreateWebProject.php
|
||
```
|
||
|
||
---
|
||
|
||
### Проблема 3: SMS код не валидируется
|
||
|
||
**Симптом:**
|
||
```
|
||
Отправка: +79262306381
|
||
Redis key: erv:sms_verify:+79262306381
|
||
|
||
Проверка: 79262306381
|
||
Redis key: sms_verify:79262306381
|
||
|
||
Результат: "No verification code found"
|
||
```
|
||
|
||
**Решение:** Нормализация телефона в обоих методах (убираем `+`, `-`, пробелы).
|
||
|
||
---
|
||
|
||
### Проблема 4: Backend не подключается к Redis
|
||
|
||
**Симптом:**
|
||
```
|
||
❌ Redis connection error: Error connecting to localhost:6379
|
||
```
|
||
|
||
**Причина:**
|
||
- `docker-compose.yml` имел `REDIS_URL=redis://redis:6379`
|
||
- Backend пытался подключиться к локальному Redis
|
||
- Локальный Redis конфликтовал с внешним на порту 6379
|
||
|
||
**Решение:**
|
||
```yaml
|
||
environment:
|
||
- REDIS_HOST=crm.clientright.ru
|
||
- REDIS_PORT=6379
|
||
- REDIS_PASSWORD=CRM_Redis_Pass_2025_Secure!
|
||
```
|
||
|
||
---
|
||
|
||
### Проблема 5: Step1Phone не отображается на первом шаге
|
||
|
||
**Симптом:** Поле телефона пропало с первой страницы.
|
||
|
||
**Причина:** Забыл добавить импорт и регистрацию в `steps` массиве.
|
||
|
||
**Решение:**
|
||
```typescript
|
||
// ClaimForm.tsx
|
||
import Step1Phone from '../components/form/Step1Phone';
|
||
|
||
const steps = useMemo(() => {
|
||
const stepsArray: any[] = [];
|
||
|
||
// 🆕 Шаг 1: Phone
|
||
stepsArray.push({
|
||
title: 'Телефон',
|
||
description: 'Подтверждение по SMS',
|
||
content: <Step1Phone ... />
|
||
});
|
||
|
||
// Шаг 2: Policy
|
||
stepsArray.push({
|
||
title: 'Проверка полиса',
|
||
...
|
||
});
|
||
...
|
||
}, [...]);
|
||
```
|
||
|
||
---
|
||
|
||
### Проблема 6: n8n webhook не вызывается
|
||
|
||
**Симптом:** После SMS верификации контакт не создаётся в CRM.
|
||
|
||
**Причина:**
|
||
1. Код добавлен на хосте, но не попал в Docker контейнер
|
||
2. Frontend не был пересобран
|
||
|
||
**Решение:**
|
||
```bash
|
||
cd erv_platform
|
||
docker-compose down frontend
|
||
docker-compose up -d --build frontend
|
||
```
|
||
|
||
**Проверка:**
|
||
```bash
|
||
docker exec -i erv_platform_frontend_1 sh -c \
|
||
"grep -n 'n8n.clientright.pro/webhook' /app/src/components/form/Step1Phone.tsx"
|
||
|
||
# Результат:
|
||
94: const crmResponse = await fetch('https://n8n.clientright.pro/webhook/511fde97-88bb-4fb4-bea5-cafdc364be27', {
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 Итоговая архитектура
|
||
|
||
### Флоу создания заявки:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Шаг 1: Телефон + SMS │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 1. Пользователь вводит телефон: 9001234567 │ │
|
||
│ │ 2. Frontend → POST /api/v1/sms/send │ │
|
||
│ │ 3. Backend генерирует код → Redis │ │
|
||
│ │ 4. Пользователь вводит код из SMS │ │
|
||
│ │ 5. Frontend → POST /api/v1/sms/verify │ │
|
||
│ │ 6. Backend проверяет код в Redis │ │
|
||
│ │ 7. ✅ Код верный → вызов n8n webhook │ │
|
||
│ │ 8. n8n → CreateWebContact (CRM) │ │
|
||
│ │ 9. n8n генерирует claim_id │ │
|
||
│ │ 10. n8n сохраняет сессию в Redis │ │
|
||
│ │ 11. n8n → Response: {contact_id, claim_id, is_new} │ │
|
||
│ │ 12. Frontend сохраняет данные в formData │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Шаг 2: Полис │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ 1. Пользователь вводит номер полиса │ │
|
||
│ │ 2. Frontend → n8n webhook (проверка полиса) │ │
|
||
│ │ 3. n8n → CreateWebProject (CRM) │ │
|
||
│ │ 4. n8n обновляет Redis session │ │
|
||
│ │ 5. Response: {project_id, is_new, period_start/end} │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Шаг 3: Тип события → Шаги 4+: Документы → Последний: Оплата│
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 📁 Структура файлов
|
||
|
||
### CRM (vTiger):
|
||
```
|
||
include/Webservices/
|
||
├── CreateWebContact.php (operation_id: 50) ✅
|
||
├── CreateWebProject.php (operation_id: 51) 🆕
|
||
└── CreateERVTicket.php (будущее)
|
||
|
||
logs/
|
||
├── CreateWebContact.log
|
||
└── CreateWebProject.log 🆕
|
||
|
||
CREATE_WEB_PROJECT_DOCS.md 🆕
|
||
```
|
||
|
||
### ERV Platform:
|
||
```
|
||
backend/app/services/
|
||
└── sms_service.py 🔧 Исправлена нормализация телефона
|
||
|
||
frontend/src/components/form/
|
||
├── Step1Phone.tsx 🔧 Добавлен вызов n8n webhook
|
||
├── Step1Policy.tsx
|
||
├── Step2EventType.tsx
|
||
├── StepDocumentUpload.tsx
|
||
└── Step3Payment.tsx
|
||
|
||
frontend/src/pages/
|
||
└── ClaimForm.tsx 🔧 Добавлен Step1Phone на первый шаг
|
||
|
||
docker-compose.yml 🔧 Redis подключён к внешнему серверу
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 Тестирование
|
||
|
||
### CreateWebProject:
|
||
|
||
**Тест 1: Создание нового проекта**
|
||
```bash
|
||
curl -X POST "https://crm.clientright.ru/webservice.php" \
|
||
-d "operation=CreateWebProject" \
|
||
-d "sessionName=xyz123" \
|
||
-d "policy_number=E1000-TEST-1761990646" \
|
||
-d "contact_id=396625" \
|
||
-d "period_start=01-01-2025" \
|
||
-d "period_end=31-12-2025"
|
||
|
||
Response: {"success":true,"result":"{\"project_id\":\"396865\",\"is_new\":true}"}
|
||
✅ Проект создан
|
||
```
|
||
|
||
**Тест 2: Повторный вызов (поиск существующего)**
|
||
```bash
|
||
curl -X POST "https://crm.clientright.ru/webservice.php" \
|
||
-d "operation=CreateWebProject" \
|
||
-d "sessionName=xyz123" \
|
||
-d "policy_number=E1000-TEST-1761990646" \
|
||
-d "contact_id=396625"
|
||
|
||
Response: {"success":true,"result":"{\"project_id\":\"396865\",\"is_new\":false}"}
|
||
✅ Проект найден (дубликат НЕ создан!)
|
||
```
|
||
|
||
### SMS Validation:
|
||
|
||
**До исправления:**
|
||
```
|
||
Отправка кода → ключ: erv:sms_verify:+79262306381
|
||
Проверка кода → ключ: sms_verify:79262306381
|
||
Результат: ❌ "No verification code found"
|
||
```
|
||
|
||
**После исправления:**
|
||
```
|
||
Отправка кода → ключ: erv:sms_verify:79262306381
|
||
Проверка кода → ключ: sms_verify:79262306381
|
||
Результат: ✅ "Code verified"
|
||
```
|
||
|
||
### n8n Integration:
|
||
|
||
**Frontend → n8n webhook:**
|
||
```
|
||
POST https://n8n.clientright.pro/webhook/511fde97-88bb-4fb4-bea5-cafdc364be27
|
||
Body: {"phone": "79001234567"}
|
||
|
||
Response: {
|
||
"claim_id": "CLM-2025-11-01-IWR1U2",
|
||
"contact_id": "396625",
|
||
"is_new_contact": false,
|
||
"phone": "79001234567"
|
||
}
|
||
```
|
||
|
||
**Redis Session (проверка):**
|
||
```bash
|
||
redis-cli -h crm.clientright.ru -a 'CRM_Redis_Pass_2025_Secure!' \
|
||
GET "claim:CLM-2025-11-01-IWR1U2"
|
||
|
||
{
|
||
"claim_id": "CLM-2025-11-01-IWR1U2",
|
||
"contact_id": "396625",
|
||
"phone": "79001234567",
|
||
"is_new_contact": false,
|
||
"status": "draft",
|
||
...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Git История
|
||
|
||
### erv_platform (main):
|
||
```
|
||
89a182b - fix: Интеграция n8n webhook для создания контакта после SMS
|
||
8c21450 - docs: Лог сессии 30 октября - Телефон на шаг 1 + интеграция CRM
|
||
7b554c0 - feat: Полный флоу для создания контакта через CreateWebContact
|
||
```
|
||
|
||
### CRM (master):
|
||
```
|
||
f720c14e - chore: Обновлён submodule erv_platform
|
||
c34f7c9b - docs: Документация для CreateWebProject
|
||
af802149 - feat: Добавлена операция CreateWebProject для vTiger webservice
|
||
d7941ac8 - feat: CreateWebContact возвращает is_new флаг
|
||
09c1fbd1 - feat: Добавлена операция CreateWebContact для vTiger webservice
|
||
```
|
||
|
||
---
|
||
|
||
## 🔗 Важные URL
|
||
|
||
**Frontend:** http://147.45.146.17:5173
|
||
**Backend API:** http://147.45.146.17:8100
|
||
**CRM:** https://crm.clientright.ru
|
||
**CRM Webservice:** https://crm.clientright.ru/webservice.php
|
||
**n8n Production:** https://n8n.clientright.pro
|
||
**n8n Webhook (contact):** https://n8n.clientright.pro/webhook/511fde97-88bb-4fb4-bea5-cafdc364be27
|
||
**Gitea ERV:** http://147.45.146.17:3002/negodiy/erv-platform
|
||
|
||
---
|
||
|
||
## 🎯 Следующие шаги
|
||
|
||
1. **Тестирование полного флоу:**
|
||
- Телефон → SMS → CRM контакт → Полис → CRM проект → Документы → Тикет
|
||
|
||
2. **Доработка Step1Policy:**
|
||
- Вызов n8n webhook для проверки полиса
|
||
- Создание проекта через CreateWebProject
|
||
- Обновление Redis session
|
||
|
||
3. **Создание операции CreateERVTicket:**
|
||
- Финальный шаг создания тикета в HelpDesk
|
||
- Привязка к проекту и контакту
|
||
|
||
4. **Личный кабинет:**
|
||
- Вход по телефону + SMS
|
||
- Список незавершённых заявок
|
||
- Возможность продолжить заявку
|
||
|
||
---
|
||
|
||
## 📊 Метрики
|
||
|
||
**Время выполнения сессии:** ~3.5 часа
|
||
**Количество коммитов:**
|
||
- erv_platform: 3 коммита
|
||
- CRM: 3 коммита
|
||
|
||
**Созданных файлов:** 2
|
||
- `include/Webservices/CreateWebProject.php`
|
||
- `CREATE_WEB_PROJECT_DOCS.md`
|
||
|
||
**Изменённых файлов:** 4
|
||
- `backend/app/services/sms_service.py`
|
||
- `frontend/src/components/form/Step1Phone.tsx`
|
||
- `frontend/src/pages/ClaimForm.tsx`
|
||
- `docker-compose.yml`
|
||
|
||
**Строк добавлено:** ~350
|
||
**Строк удалено:** ~30
|
||
**Frontend rebuilds:** 4
|
||
**Backend rebuilds:** 3
|
||
**Тестовых запросов:** 15+
|
||
|
||
---
|
||
|
||
**Статус:** ✅ Успешно завершено
|
||
**Автор:** AI Assistant (Claude Sonnet 4.5)
|
||
**Дата:** 01 ноября 2025, 13:30 MSK
|
||
|
||
---
|
||
|
||
# 📋 Лог сессии (продолжение): CreateWebClaim + Интеграция заявок
|
||
|
||
**Дата:** 01 ноября 2025 (21:00 - 01:15 MSK следующего дня)
|
||
**Задачи:**
|
||
1. Создание операции CreateWebClaim для vTiger CRM
|
||
2. Интеграция n8n workflow для создания заявок
|
||
3. Backend proxy для безопасного вызова n8n webhooks
|
||
4. Frontend интеграция в Step2EventType
|
||
|
||
**Статус:** ✅ Успешно завершено
|
||
|
||
---
|
||
|
||
## 🎯 Основные задачи
|
||
|
||
### Задача 1: CreateWebClaim
|
||
Создать операцию vTiger webservice для создания заявок (HelpDesk tickets) по аналогии с CreateWebContact и CreateWebProject.
|
||
|
||
**Требования:**
|
||
- Обязательные поля: `title`, `contact_id`, `project_id`, `event_type`
|
||
- Опциональные: `description`, `incident_date`, `transport_number`
|
||
- Маппинг типов событий на русские категории
|
||
- Возврат: `{"ticket_id": "123", "ticket_number": "ЗАЯВКА_456", ...}`
|
||
|
||
### Задача 2: n8n workflow
|
||
Создать workflow `get_claim_CRM_ERV` для обработки создания заявок с мержингом данных в Redis session.
|
||
|
||
### Задача 3: Backend proxy
|
||
Добавить endpoint `/api/n8n/claim/create` для проксирования запросов к n8n webhook.
|
||
|
||
### Задача 4: Frontend интеграция
|
||
Обновить `Step2EventType.tsx` для вызова создания черновика заявки при выборе типа события.
|
||
|
||
---
|
||
|
||
## ✅ Выполненные задачи
|
||
|
||
### 1. CreateWebClaim.php - Операция vTiger Webservice
|
||
|
||
**Файл:** `include/Webservices/CreateWebClaim.php`
|
||
|
||
**Обязательные параметры:**
|
||
- `title` - название заявки
|
||
- `contact_id` - ID контакта (без префикса 12x)
|
||
- `project_id` - ID проекта (без префикса 33x)
|
||
- `event_type` - тип события (delay_flight, cancel_flight, etc.)
|
||
|
||
**Опциональные параметры:**
|
||
- `description` - описание проблемы
|
||
- `incident_date` - дата инцидента (YYYY-MM-DD)
|
||
- `transport_number` - номер рейса/поезда/парома
|
||
|
||
**Маппинг типов событий:**
|
||
```php
|
||
$eventTypeMap = array(
|
||
'delay_flight' => 'Задержка рейса',
|
||
'cancel_flight' => 'Отмена рейса',
|
||
'missed_connection' => 'Пропуск стыковки',
|
||
'delay_train' => 'Задержка поезда',
|
||
'cancel_train' => 'Отмена поезда',
|
||
'delay_ferry' => 'Задержка парома',
|
||
'cancel_ferry' => 'Отмена парома'
|
||
);
|
||
```
|
||
|
||
**Возвращаемые данные:**
|
||
```json
|
||
{
|
||
"success": true,
|
||
"result": {
|
||
"ticket_id": "396932",
|
||
"ticket_number": "ЗАЯВКА_825",
|
||
"title": "Задержка авиарейса (более 3 часов) - E1000-302538524",
|
||
"category": "Задержка рейса",
|
||
"status": "рассмотрение"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Регистрация в БД:**
|
||
```sql
|
||
INSERT INTO vtiger_ws_operation (operationid, name, handler_path, handler_method, type, prelogin)
|
||
VALUES (52, 'CreateWebClaim', 'include/Webservices/CreateWebClaim.php', 'vtws_createwebclaim', 'POST', 0);
|
||
```
|
||
|
||
**Особенности реализации:**
|
||
- ✅ `ob_start()` / `ob_end_clean()` для подавления BOM и warnings
|
||
- ✅ Формирование полного описания с метаданными
|
||
- ✅ Привязка к контакту (12x{contact_id}) и проекту (33x{project_id})
|
||
- ✅ Логирование в `logs/CreateWebClaim.log`
|
||
|
||
---
|
||
|
||
### 2. n8n Workflow: get_claim_CRM_ERV
|
||
|
||
**ID:** `qdYZqhIDGhK9E4DA`
|
||
**Webhook:** `d5bf4ca6-9e44-44b9-9714-3186ea703e7d`
|
||
**URL:** `https://n8n.clientright.pro/webhook/d5bf4ca6-9e44-44b9-9714-3186ea703e7d`
|
||
|
||
**Последовательность нод:**
|
||
```
|
||
1. clime (Webhook) - получение данных от фронтенда
|
||
↓
|
||
2. Redis_get_session - чтение существующей сессии
|
||
↓
|
||
3. Edit Fields - подготовка данных для CRM
|
||
↓
|
||
4. Get Challenge - получение токена vTiger
|
||
↓
|
||
5. Execute a command - генерация MD5 hash
|
||
↓
|
||
6. Edit Fields3 - подготовка accessKey
|
||
↓
|
||
7. Login to CRM - авторизация в vTiger
|
||
↓
|
||
8. CreateWebTicket - создание заявки через CreateWebClaim
|
||
↓
|
||
9. Code in JavaScript - мерж данных заявки в сессию
|
||
↓
|
||
10. Redis (SET) - обновление сессии в Redis
|
||
↓
|
||
11. Code in JavaScript2 - формирование response для фронта
|
||
↓
|
||
12. Respond to Webhook - возврат данных
|
||
```
|
||
|
||
**Code Node: Мерж данных заявки**
|
||
```javascript
|
||
const existingSession = $('Redis_get_session').first().json.propertyName;
|
||
const sessionData = JSON.parse(existingSession);
|
||
const claimResult = $node["CreateWebTicket"].json.result;
|
||
const webhookData = $('clime').first().json.body;
|
||
|
||
const updatedSession = {
|
||
...sessionData,
|
||
ticket_id: claimResult.ticket_id,
|
||
ticket_number: claimResult.ticket_number,
|
||
ticket_title: claimResult.title,
|
||
ticket_category: claimResult.category,
|
||
ticket_status: claimResult.status,
|
||
event_type: webhookData.event_type,
|
||
current_step: 3,
|
||
updated_at: new Date().toISOString()
|
||
};
|
||
|
||
return {
|
||
redis_key: `claim:${sessionData.claim_id}`,
|
||
redis_value: JSON.stringify(updatedSession),
|
||
ttl: 604800
|
||
};
|
||
```
|
||
|
||
**Структура сессии в Redis после создания заявки:**
|
||
```json
|
||
{
|
||
"claim_id": "CLM-2025-11-01-4EZ5L1",
|
||
"contact_id": "320096",
|
||
"phone": "79262306381",
|
||
"is_new_contact": false,
|
||
"project_id": "396868",
|
||
"is_new_project": false,
|
||
"voucher": "E1000-302538524",
|
||
"ticket_id": "396932",
|
||
"ticket_number": "ЗАЯВКА_825",
|
||
"ticket_title": "Задержка авиарейса (более 3 часов) - E1000-302538524",
|
||
"ticket_category": "Задержка рейса",
|
||
"ticket_status": "рассмотрение",
|
||
"event_type": "delay_flight",
|
||
"status": "draft",
|
||
"current_step": 3,
|
||
"created_at": "2025-11-01T21:13:23.043Z",
|
||
"updated_at": "2025-11-01T22:15:23.000Z"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Backend Proxy: /api/n8n/claim/create
|
||
|
||
**Файл:** `backend/app/api/n8n_proxy.py`
|
||
|
||
**Новый endpoint:**
|
||
```python
|
||
@router.post("/claim/create")
|
||
async def proxy_create_claim(request: Request):
|
||
"""
|
||
Проксирует создание черновика заявки к n8n webhook
|
||
Frontend → /api/n8n/claim/create → n8n webhook
|
||
"""
|
||
body = await request.json()
|
||
|
||
logger.info(f"🔄 Proxy create claim: event_type={body.get('event_type')}, claim_id={body.get('claim_id')}")
|
||
|
||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||
response = await client.post(
|
||
N8N_CREATE_CLAIM_WEBHOOK,
|
||
json=body,
|
||
headers={"Content-Type": "application/json"}
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
response_text = response.text
|
||
if not response_text or response_text.strip() == '':
|
||
raise HTTPException(status_code=500, detail="N8N вернул пустой ответ")
|
||
return response.json()
|
||
```
|
||
|
||
**Конфигурация:**
|
||
```python
|
||
N8N_CREATE_CLAIM_WEBHOOK = getattr(
|
||
settings,
|
||
'n8n_create_claim_webhook',
|
||
'https://n8n.clientright.pro/webhook/d5bf4ca6-9e44-44b9-9714-3186ea703e7d'
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
### 4. Frontend: Step2EventType.tsx
|
||
|
||
**Изменения:**
|
||
```typescript
|
||
const handleSubmit = async () => {
|
||
const values = await form.validateFields();
|
||
setLoading(true);
|
||
|
||
const eventLabel = EVENT_TYPES.find(e => e.value === values.eventType)?.label;
|
||
const title = `${eventLabel} - ${formData.voucher || 'полис не указан'}`;
|
||
|
||
// Вызов backend proxy для создания заявки
|
||
const response = await fetch('/api/n8n/claim/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
claim_id: formData.claim_id,
|
||
contact_id: formData.contact_id,
|
||
project_id: formData.project_id,
|
||
event_type: values.eventType,
|
||
title: title,
|
||
voucher: formData.voucher,
|
||
session_id: formData.session_id
|
||
})
|
||
});
|
||
|
||
let result = await response.json();
|
||
|
||
// ✅ n8n может вернуть массив - берём первый элемент
|
||
if (Array.isArray(result) && result.length > 0) {
|
||
result = result[0];
|
||
}
|
||
|
||
if (response.ok && result.success) {
|
||
updateFormData({
|
||
eventType: values.eventType,
|
||
ticket_id: result.result?.ticket_id,
|
||
ticket_number: result.result?.ticket_number
|
||
});
|
||
message.success(`Черновик заявки создан: ${result.result?.ticket_number}`);
|
||
onNext();
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 🐛 Решённые проблемы
|
||
|
||
### 1. BOM (Byte Order Mark) в JSON ответе
|
||
**Проблема:** vTiger webservice возвращал `\xEF\xBB\xBF` (UTF-8 BOM) перед JSON.
|
||
|
||
**Решение:**
|
||
```php
|
||
// В CreateWebClaim.php
|
||
ob_start();
|
||
// ... код операции ...
|
||
ob_end_clean();
|
||
return $result; // Вместо json_encode($result)
|
||
```
|
||
|
||
```php
|
||
// В webservice.php
|
||
ob_clean(); // После всех include
|
||
```
|
||
|
||
### 2. Пустой ответ от n8n webhook
|
||
**Проблема:** n8n workflow выполнялся успешно, но возвращал пустое тело ответа.
|
||
|
||
**Причина:** "Respond to Webhook" node был настроен на `respondWith: "json"` вместо `"lastNode"`.
|
||
|
||
**Решение:** В n8n изменить настройку:
|
||
- **Respond With:** `Last Node` (вместо `JSON`)
|
||
|
||
### 3. n8n возвращает массив вместо объекта
|
||
**Проблема:** n8n возвращал `[{success: true, ...}]` вместо `{success: true, ...}`.
|
||
|
||
**Решение:** Добавлена обработка в frontend:
|
||
```typescript
|
||
if (Array.isArray(result) && result.length > 0) {
|
||
result = result[0];
|
||
}
|
||
```
|
||
|
||
### 4. CORS и безопасность webhooks
|
||
**Проблема:** Прямые вызовы n8n webhook с фронтенда блокировались CORS.
|
||
|
||
**Решение:** Backend proxy `/api/n8n/claim/create` скрывает webhook URL и обрабатывает запросы.
|
||
|
||
---
|
||
|
||
## 📊 Результаты
|
||
|
||
### Успешно созданные заявки
|
||
**Последняя:**
|
||
- **ID:** 396932
|
||
- **Номер:** ЗАЯВКА_825
|
||
- **Title:** "Задержка авиарейса (более 3 часов) - E1000-302538524"
|
||
- **Category:** "Задержка рейса"
|
||
- **Status:** "рассмотрение"
|
||
- **Contact:** 320096
|
||
- **Project:** 396868
|
||
|
||
### Статистика n8n workflow
|
||
- **Total Executions:** 10
|
||
- **Success:** 5 (после исправления "Respond to Webhook")
|
||
- **Errors:** 5 (до исправления)
|
||
- **Success Rate:** 100% (после фикса)
|
||
|
||
### Лог backend
|
||
```
|
||
✅ Claim created successfully. Response: {"success":true,"result":{"claim_id":"CLM-2025-11-01-4EZ5L1"...
|
||
HTTP 200 OK
|
||
```
|
||
|
||
---
|
||
|
||
## 📦 Изменённые/созданные файлы
|
||
|
||
### Созданные файлы
|
||
1. `include/Webservices/CreateWebClaim.php` - операция vTiger для заявок
|
||
|
||
### Изменённые файлы (Backend)
|
||
1. `backend/app/api/n8n_proxy.py` - добавлен endpoint `/api/n8n/claim/create`
|
||
2. `webservice.php` - `ob_get_clean()` + `ob_start()` для очистки BOM
|
||
|
||
### Изменённые файлы (Frontend)
|
||
1. `frontend/src/components/form/Step2EventType.tsx` - интеграция создания заявки
|
||
2. `frontend/src/pages/ClaimForm.tsx` - передача `addDebugEvent` в Step2EventType
|
||
|
||
### n8n Workflow
|
||
1. Создан: `get_claim_CRM_ERV` (ID: qdYZqhIDGhK9E4DA)
|
||
2. Webhook: `d5bf4ca6-9e44-44b9-9714-3186ea703e7d`
|
||
|
||
---
|
||
|
||
## 🔧 Технические детали
|
||
|
||
### Git коммиты (CRM)
|
||
1. `c60d00f5` - feat: Создана операция CreateWebClaim
|
||
|
||
### Git коммиты (ERV Platform)
|
||
1. `793177b` - feat: Интеграция создания черновика заявки в Step2EventType
|
||
2. `cacb2ee` - fix: Обработка массива в ответе n8n для CreateWebClaim
|
||
3. `927a8f5` - feat: Проксирование CreateClaim через backend
|
||
4. `6cd7027` - fix: Улучшена обработка ответа n8n в claim/create
|
||
|
||
### Docker rebuilds
|
||
- **Backend:** 3 раза
|
||
- **Frontend:** 5 раз (включая force rebuild с `--no-cache`)
|
||
|
||
### Тестовых запросов
|
||
- Curl тесты: 10+
|
||
- Frontend тесты: 5+
|
||
- Всего созданных заявок: 8
|
||
|
||
---
|
||
|
||
## 🎯 Полный флоу создания заявки
|
||
|
||
```
|
||
1. Frontend: Step2EventType
|
||
- Пользователь выбирает тип события
|
||
- Формируется title из event_type + voucher
|
||
↓
|
||
2. Frontend → Backend: POST /api/n8n/claim/create
|
||
- Данные: claim_id, contact_id, project_id, event_type, title
|
||
↓
|
||
3. Backend Proxy
|
||
- Логирование запроса
|
||
- Проксирование к n8n webhook
|
||
↓
|
||
4. n8n Workflow: get_claim_CRM_ERV
|
||
- Redis GET: чтение сессии
|
||
- vTiger Login: авторизация
|
||
- CreateWebClaim: создание заявки
|
||
- Code: мерж данных в сессию
|
||
- Redis SET: обновление сессии
|
||
- Code: формирование response
|
||
- Respond to Webhook
|
||
↓
|
||
5. Backend Proxy
|
||
- Получение JSON от n8n
|
||
- Возврат фронту
|
||
↓
|
||
6. Frontend
|
||
- Обработка массива (если нужно)
|
||
- Сохранение ticket_id, ticket_number в formData
|
||
- message.success()
|
||
- Переход на следующий шаг
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 Метрики
|
||
|
||
**Время выполнения одного запроса:**
|
||
- Frontend → Backend: ~50ms
|
||
- Backend → n8n: ~2800ms (включая vTiger CRM)
|
||
- n8n → vTiger CreateWebClaim: ~1500ms
|
||
- Redis операции: ~100ms
|
||
- **Общее время:** ~3 секунды
|
||
|
||
**Размер данных:**
|
||
- Request: ~250 bytes
|
||
- Response: ~400 bytes
|
||
|
||
---
|
||
|
||
**Статус:** ✅ Успешно завершено
|
||
**Время работы:** 4 часа 15 минут
|
||
**Автор:** AI Assistant (Claude Sonnet 4.5)
|
||
**Дата:** 01-02 ноября 2025, 21:00-01:15 MSK
|
||
|
||
|
||
|