# 📋 Лог сессии: 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; // Последний шаг: 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: }); // Шаг 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