Ticket form: new stack + description step
This commit is contained in:
190
SESSION_LOG_2025-11-14.md
Normal file
190
SESSION_LOG_2025-11-14.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# 📓 ERV Platform — лог сессии и шпаргалка (14.11.2025)
|
||||||
|
|
||||||
|
Документ составлен, чтобы любой ИИ-ассистент мгновенно разобрался, как устроена цифровая приёмка заявок ERV, где искать настройки и как чинить основные сценарии.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Картина в целом
|
||||||
|
|
||||||
|
- **Фронтенд:** React + Ant Design (`erv_platform/frontend`). Собирается `docker-compose` сервисом `frontend`, крутится на `localhost:5173`, но в прода заходит через nginx. Главная страница — `src/pages/ClaimForm.tsx`, шаги формы разбиты на компоненты `src/components/form/Step*.tsx`.
|
||||||
|
- **Бэкенд:** FastAPI (`erv_platform/backend`). Поднимается на `:8100`, все публичные вызовы идут через `/api/v1/...`. Отвечает за:
|
||||||
|
- SMS-сервис и верификацию телефона (`app/services/sms_service.py`).
|
||||||
|
- Проксирование вызовов в n8n (`app/api/n8n_proxy.py`), чтобы фронт не видел прямые webhook URL.
|
||||||
|
- SSE для live-обновлений (AI Drawer / документооборот — отдельные роуты в `app/api/sse.py`).
|
||||||
|
- Хелперы для логов, Redis, RabbitMQ, file uploads.
|
||||||
|
- **n8n:** `https://n8n.clientright.pro` (прод) и `http://147.45.146.17:5678` (dev). Здесь собраны все длинные сценарии: поиск/создание контакта, проверка полиса, создание проекта, загрузка документов, вызовы PHP-операций CRM. FastAPI знает только их прокси-URL.
|
||||||
|
- **CRM:** vTiger `https://crm.clientright.ru`. Кастомные веб-сервисы лежат в `include/Webservices/*`. Главные операции:
|
||||||
|
- `CreateWebContact` — ищет/создаёт контакт.
|
||||||
|
- `CreateWebProject` — ищет/создаёт проект по номеру полиса + контакту.
|
||||||
|
- `CreateWebClaim` — создаёт заявку (HelpDesk) и теперь двусторонне линкует её с проектом (см. правки от 14.11).
|
||||||
|
- **Хранилища:** Redis (host `crm.clientright.ru:6379`, pass `CRM_Redis_Pass_2025_Secure!`) для сессий, SMS-кодов и claim-состояния. S3 (TWC Storage) + Nextcloud (`office.clientright.ru:8443`) для файлов. RabbitMQ (`185.197.75.249:5672`) для асинхронных задач.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Где живут секреты и настройки
|
||||||
|
|
||||||
|
- **`.env`** лежит в `erv_platform/.env` (в git не попадает). Туда уезжают все токены/пароли:
|
||||||
|
- `N8N_*_WEBHOOK` ссылки,
|
||||||
|
- `CRM_USERNAME/CRM_ACCESSKEY` для PHP скриптов,
|
||||||
|
- SMTP, SMS-шлюзы, S3 ключи.
|
||||||
|
- **docker-compose** (`erv_platform/docker-compose.yml`) подсовывает в контейнеры только безопасные значения (Redis, RabbitMQ, н8n webhooks). Всё остальное backend читает из `.env` через `pydantic`-настройки (`backend/app/config.py`).
|
||||||
|
- **SSH** — доп.профиль нужно заносить в `~/.ssh/config` (правило от Фёдора). Для CI/CD используем отдельный ключ `cursor-server`.
|
||||||
|
- **Cron/Cache:** папка `/var/www/fastuser/data/www/crm.clientright.ru/cache` запрет на любые изменения (хранит данные крона CRM).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Пользовательский сценарий ERV (по шагам)
|
||||||
|
|
||||||
|
### Шаг 1. Верификация телефона
|
||||||
|
1. `Step1Phone.tsx` берёт 10 цифр, добавляет префикс `+7` только визуально, в API уходит `7XXXXXXXXXX`.
|
||||||
|
2. `/api/v1/sms/send` → `sms_service.py` сохраняет код в Redis по ключу `sms:code:{phone}` (rate-limit временно отключён).
|
||||||
|
3. `/api/v1/sms/verify` при успехе вызывает `/api/n8n/contact/create`, куда передаётся `phone` + `session_id`.
|
||||||
|
4. n8n вызывает CRM `CreateWebContact`. Либо возвращает существующий `contact_id`, либо создаёт новый и выставляет `is_new_contact=true`.
|
||||||
|
5. n8n также генерирует `claim_id` (UUID вида `CLM-2025-11-14-XXXX`) и начинает сессию в Redis `claim:{claim_id}` с TTL 48ч.
|
||||||
|
|
||||||
|
### Шаг 2. Полис
|
||||||
|
1. `Step2Policy.tsx` шлёт полис на `/api/n8n/policy/check`.
|
||||||
|
2. n8n проверяет валидность номера, ищет проект через `CreateWebProject`. Если проекта нет, создаёт в CRM:
|
||||||
|
- Название `ERV {policy} цифровой адвокат`
|
||||||
|
- `projectstatus = модерация`, `projecttype = ерв урегулирование`
|
||||||
|
- `linktoaccountscontacts = 12x{contact}`
|
||||||
|
3. Ответ сохраняет `project_id`, `is_new_project`, срок действия полиса, pdf-выписку и т.д., всё докладывается в Redis-сессию.
|
||||||
|
|
||||||
|
### Шаги 3-4. Тип события, документы, выплаты
|
||||||
|
1. В `ClaimForm.tsx` собраны все поля формы. Состояние хранится в `useState` и синхронизируется с Redis через вызовы n8n (при каждом шаге есть endpoint вида `/api/v1/claim/save-field`, который кладёт патчи в Redis).
|
||||||
|
2. Загрузка документов идёт через `/api/n8n/upload/file` (multipart). Backend пересылает файл в n8n, там:
|
||||||
|
- Файл кладётся в S3 `s3.twcstorage.ru/erv/{claim_id}/...`.
|
||||||
|
- В Redis фиксируется метаданные, генерится SSE-ивент про статус OCR/AI.
|
||||||
|
3. Платёжные реквизиты, email и прочие финальные данные отправляются так же через n8n, чтобы единообразно писать в Redis.
|
||||||
|
|
||||||
|
### Финал. Создание заявки
|
||||||
|
1. `/api/n8n/claim/create` собирает весь контекст из Redis, вызывает CRM `CreateWebClaim`.
|
||||||
|
2. `CreateWebClaim.php`:
|
||||||
|
- нормализует ID (поддержка форматов `12x123`/`123`),
|
||||||
|
- создаёт HelpDesk запись с `ticketstatus = рассмотрение`,
|
||||||
|
- проставляет поле `cf_2066 = 33x{project_id}` (связь на проект),
|
||||||
|
- с 14.11 добавляет запись в `vtiger_crmentityrel` (двусторонняя связь Project ↔ HelpDesk),
|
||||||
|
- логирует в `logs/CreateWebClaim.log`.
|
||||||
|
3. Ответ возвращает `ticket_id`, `ticket_number`, статусы. n8n завершает сессию, двигает файлы в Nextcloud (папка проекта + claim-id подпапка) и создаёт ZIP для страховой через `CopyToS3`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Логи и отладка
|
||||||
|
|
||||||
|
- **FastAPI**: `docker-compose logs backend -f`. Внутри контейнера — `/app/logs/*.log`.
|
||||||
|
- **SMS**: `erv_platform/backend/logs/sms_service.log` + Redis ключи `sms:*`.
|
||||||
|
- **n8n proxy**: `logs/backend.log` (см. `logger.info` в `n8n_proxy.py`).
|
||||||
|
- **CRM операции** (в корне проекта CRM):
|
||||||
|
- `logs/CreateWebContact.log`
|
||||||
|
- `logs/CreateWebProject.log`
|
||||||
|
- `logs/CreateWebClaim.log`
|
||||||
|
- SSE/AI drawer — `logs/ai_sse_debug.log`
|
||||||
|
- **Redis**: можно проверить `redis-cli -h crm.clientright.ru -a CRM_Redis_Pass_2025_Secure! GET claim:{id}`.
|
||||||
|
- **n8n**: интерфейс `https://n8n.clientright.pro`, вкладка Executions. Для долгих запросов повышаем timeout в HTTP Request node до 60-90 сек.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Что уже починили / текущее состояние
|
||||||
|
|
||||||
|
1. **Связь HelpDesk ↔ Project** — теперь двусторонняя, см. `include/Webservices/CreateWebClaim.php` (патчи 14.11.2025).
|
||||||
|
2. **SMS** — нормализуется формат телефона, лимит отключён для тестов.
|
||||||
|
3. **Контакты/проекты** — операции CRM возвращают `is_new` флаги, не создают дубликатов. В `CreateWebProject` SQL теперь смотрит напрямую `p.linktoaccountscontacts`.
|
||||||
|
4. **n8n webhooks** — спрятаны за `/api/n8n/*`, пустые ответы обрабатываются, таймауты логируются.
|
||||||
|
5. **claim_id на фронте** — приходит из n8n, хранится в состоянии и Redis, при сбросе форма очищает поле.
|
||||||
|
6. **Документы** — upload не падает, даже если n8n ответил пустотой; backend возвращает заглушку `{success:true}`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Как разворачивать / тестировать
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform
|
||||||
|
cp .env.example .env # если впервые
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
docker-compose logs -f backend
|
||||||
|
|
||||||
|
# Прогнать e2e happy flow:
|
||||||
|
# 1) открыть http://localhost:5173
|
||||||
|
# 2) пройти шаги с DEV кодом (кнопка в Step1Phone.tsx)
|
||||||
|
# 3) загрузить фейковый pdf (tmp/test.pdf)
|
||||||
|
# 4) убедиться, что в CRM появился контакт/проект/заявка
|
||||||
|
```
|
||||||
|
|
||||||
|
Unit-тестов почти нет, поэтому проверяем сценарии вручную через UI + смотрим n8n executions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Частые вопросы / рецепты
|
||||||
|
|
||||||
|
- **Где поменять тайм-ауты n8n?** В каждом HTTP Request node (раздел Options → Timeout). Для CreateWebProject выставляем ≥ 60 сек.
|
||||||
|
- **Как отключить/включить SMS rate limit?** В `sms_service.py` вокруг `sms_rate:{phone}` есть блок, сейчас закомментирован.
|
||||||
|
- **Как сменить webhook URL?** Поправить `.env`, затем `docker-compose restart backend`. Файлы: `backend/app/config.py` и `backend/app/api/n8n_proxy.py`.
|
||||||
|
- **Как добавить новое поле в заявку?**
|
||||||
|
1. Добавить state в `ClaimForm.tsx`.
|
||||||
|
2. Пробросить пропсы в нужный `Step*`.
|
||||||
|
3. На backend — endpoint `claim/save-field`.
|
||||||
|
4. В n8n — расширить Redis JSON / CRM payload.
|
||||||
|
- **Куда падут загруженные файлы?** Сначала `s3.twcstorage.ru/erv/{claim}`, после подтверждения — Nextcloud (`Папка в Nextcloud` кнопка в карточке проекта).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Ключевые файлы (для быстрого поиска)
|
||||||
|
|
||||||
|
| Модуль | Путь | Назначение |
|
||||||
|
|---|---|---|
|
||||||
|
| Фронт: Step1 | `frontend/src/components/form/Step1Phone.tsx` | SMS и старт CRM сессии |
|
||||||
|
| Фронт: Step3 | `frontend/src/components/form/Step3Payment.tsx` | Email + финальные данные |
|
||||||
|
| Бэкенд API | `backend/app/main.py` | FastAPI приложение |
|
||||||
|
| n8n Proxy | `backend/app/api/n8n_proxy.py` | Прокси для всех webhooks |
|
||||||
|
| SMS | `backend/app/services/sms_service.py` | Отправка/проверка кодов |
|
||||||
|
| CRM Contact | `include/Webservices/CreateWebContact.php` | Не создаёт дубликаты |
|
||||||
|
| CRM Project | `include/Webservices/CreateWebProject.php` | Поиск проекта по полису |
|
||||||
|
| CRM Claim | `include/Webservices/CreateWebClaim.php` | Создание заявки + связь |
|
||||||
|
| Описания AI | `crm_extensions/AI_DRAWER_*` | Документация по SSE/AI |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Что ещё улучшить (бэклог)
|
||||||
|
|
||||||
|
- Вернуть и настроить адекватный rate-limit SMS (фича готова, достаточно раскомментировать и подобрать окна).
|
||||||
|
- Причесать docker-compose: убрать локальные `redis/postgres` сервисы, раз мы ходим наружу.
|
||||||
|
- Добавить автотесты для Redis claim session (pytest + fakeredis).
|
||||||
|
- В n8n вынести повторяющиеся фрагменты (Redis fetch/patch) в sub-workflows.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Документ обновлён 14.11.2025, автор: GPT-5.1 Codex (по просьбе Фёдора). Если что-то меняется — дополняй файл, чтобы у будущих ассистентов была единая точка правды.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Ticket Form Intake (other.clientright.ru → новая платформа)
|
||||||
|
|
||||||
|
1. **Развёрнули отдельный стек `ticket_form`:**
|
||||||
|
- Скопировали структуру `erv_platform`, переименовали сервисы в `ticket_form_frontend`/`ticket_form_backend`.
|
||||||
|
- Порты: фронт `5175`, бек `8200`. `docker-compose.yml` читает переменные `TICKET_FORM_*`, backend берёт `.env` из корня `ticket_form`.
|
||||||
|
- Backend конфиг (`app/config.py`) получил новые значения `app_name`, `redis_prefix=ticket_form:`, актуальные `backend_url/frontend_url`.
|
||||||
|
|
||||||
|
2. **Frontend:**
|
||||||
|
- Vite proxy теперь стучится на `host.docker.internal:8200`.
|
||||||
|
- Добавлен шаг `StepDescription` между телефоном и полисом — пользователь оставляет свободное описание кейса.
|
||||||
|
- Все `fetch` привязаны к относительным путям `/api/...`, чтобы прокси корректно отрабатывал и у фронта, и у прода.
|
||||||
|
- В `Step3Payment` debug-код SMS сохраняется в стейте и отображается отдельным блоком с кнопкой “Скопировать”, исчезает только после успешной проверки.
|
||||||
|
|
||||||
|
3. **Backend:**
|
||||||
|
- Новый endpoint `POST /api/v1/claims/description` принимает `session_id` + текст проблемы и публикует событие в Redis канал `ticket_form:description`. n8n слушает его и запускает AI-агента.
|
||||||
|
- Добавлено логирование всех операций публикации (см. `backend/app/api/claims.py`).
|
||||||
|
- Dockerfile backend и скрипты запуска переведены на порт 8200.
|
||||||
|
|
||||||
|
4. **Интеграции / инфраструктура:**
|
||||||
|
- Контейнеры пересобраны (`docker compose up -d --build`). Redis внутри compose отключился (порт занят боевым), но не нужен — ходим во внешний.
|
||||||
|
- n8n воркфлоу уже ловит новые события и может отправлять рекомендации (план: ИИ формирует список документов → сохраняем в Redis → фронт подхватывает перед шагом загрузок).
|
||||||
|
|
||||||
|
5. **Что дальше (ticket_form):**
|
||||||
|
- Договориться о формате ответа AI агента (JSON с `required_documents`, `summary`, `extra_questions`).
|
||||||
|
- На фронте научиться подтягивать рекомендации из Redis и строить динамические шаги загрузок.
|
||||||
|
- Определить конечный набор endpoint’ов н8н для загрузки файлов/финала (аналог ERV, но под ticket_form сценарий).
|
||||||
|
|
||||||
|
Upd 14.11.2025, автор: GPT-5.1 Codex.
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Открываем порт
|
# Открываем порт
|
||||||
EXPOSE 8100
|
EXPOSE 8200
|
||||||
|
|
||||||
# Запускаем приложение
|
# Запускаем приложение
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8100"]
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8200"]
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,20 @@
|
|||||||
Claims API Routes - Обработка заявок
|
Claims API Routes - Обработка заявок
|
||||||
"""
|
"""
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from .models import ClaimCreateRequest, ClaimResponse
|
from .models import (
|
||||||
|
ClaimCreateRequest,
|
||||||
|
ClaimResponse,
|
||||||
|
TicketFormDescriptionRequest,
|
||||||
|
)
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from ..services.redis_service import redis_service
|
||||||
|
from ..config import settings
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1/claims", tags=["Claims"])
|
router = APIRouter(prefix="/api/v1/claims", tags=["Claims"])
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create", response_model=ClaimResponse)
|
@router.post("/create", response_model=ClaimResponse)
|
||||||
@@ -49,3 +58,43 @@ async def get_claim(claim_id: str):
|
|||||||
"message": "Заявка в обработке"
|
"message": "Заявка в обработке"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/description")
|
||||||
|
async def publish_ticket_form_description(payload: TicketFormDescriptionRequest):
|
||||||
|
"""
|
||||||
|
Публикует свободное описание проблемы в Redis канал ticket_form:description
|
||||||
|
(слушается воркфлоу в n8n)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
channel = payload.channel or f"{settings.redis_prefix}description"
|
||||||
|
event = {
|
||||||
|
"type": "ticket_form_description",
|
||||||
|
"session_id": payload.session_id,
|
||||||
|
"claim_id": payload.claim_id,
|
||||||
|
"phone": payload.phone,
|
||||||
|
"email": payload.email,
|
||||||
|
"description": payload.problem_description.strip(),
|
||||||
|
"source": payload.source,
|
||||||
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"📝 TicketForm description received",
|
||||||
|
extra={"session_id": payload.session_id, "claim_id": payload.claim_id},
|
||||||
|
)
|
||||||
|
await redis_service.publish(channel, json.dumps(event, ensure_ascii=False))
|
||||||
|
logger.info(
|
||||||
|
"📡 TicketForm description published",
|
||||||
|
extra={"channel": channel, "session_id": payload.session_id},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"channel": channel,
|
||||||
|
"event": event,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("❌ Failed to publish ticket form description")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Не удалось опубликовать описание: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -62,3 +62,14 @@ class ClaimResponse(BaseModel):
|
|||||||
claim_number: Optional[str] = None
|
claim_number: Optional[str] = None
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class TicketFormDescriptionRequest(BaseModel):
|
||||||
|
"""Отправка свободного описания проблемы (Ticket Form)"""
|
||||||
|
session_id: str = Field(..., description="ID клиентской сессии")
|
||||||
|
claim_id: Optional[str] = Field(None, description="ID заявки (если уже создана)")
|
||||||
|
phone: Optional[str] = Field(None, description="Номер телефона заявителя")
|
||||||
|
email: Optional[str] = Field(None, description="Email заявителя")
|
||||||
|
problem_description: str = Field(..., min_length=10, description="Свободное описание ситуации")
|
||||||
|
source: str = Field("ticket_form", description="Источник события")
|
||||||
|
channel: Optional[str] = Field(None, description="Переопределение Redis канала (опционально)")
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
"""
|
"""
|
||||||
Конфигурация приложения
|
Конфигурация приложения
|
||||||
"""
|
"""
|
||||||
|
from pathlib import Path
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parents[2]
|
||||||
|
ENV_PATH = BASE_DIR / ".env"
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
# ============================================
|
# ============================================
|
||||||
# APPLICATION
|
# APPLICATION
|
||||||
# ============================================
|
# ============================================
|
||||||
app_name: str = "ERV Insurance Platform"
|
app_name: str = "Ticket Form Intake Platform"
|
||||||
app_env: str = "development"
|
app_env: str = "development"
|
||||||
debug: bool = True
|
debug: bool = True
|
||||||
|
|
||||||
# API
|
# API
|
||||||
api_v1_prefix: str = "/api/v1"
|
api_v1_prefix: str = "/api/v1"
|
||||||
backend_url: str = "http://localhost:8100"
|
backend_url: str = "http://localhost:8200"
|
||||||
frontend_url: str = "http://localhost:5173"
|
frontend_url: str = "http://localhost:5175"
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# DATABASE (PostgreSQL)
|
# DATABASE (PostgreSQL)
|
||||||
@@ -49,7 +54,7 @@ class Settings(BaseSettings):
|
|||||||
redis_port: int = 6379
|
redis_port: int = 6379
|
||||||
redis_password: str = "CRM_Redis_Pass_2025_Secure!"
|
redis_password: str = "CRM_Redis_Pass_2025_Secure!"
|
||||||
redis_db: int = 0
|
redis_db: int = 0
|
||||||
redis_prefix: str = "erv:"
|
redis_prefix: str = "ticket_form:"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def redis_url(self) -> str:
|
def redis_url(self) -> str:
|
||||||
@@ -147,7 +152,7 @@ class Settings(BaseSettings):
|
|||||||
# ============================================
|
# ============================================
|
||||||
# CORS
|
# CORS
|
||||||
# ============================================
|
# ============================================
|
||||||
cors_origins: str = "http://localhost:5173,http://147.45.146.17:5173,https://erv-claims.clientright.ru,http://crm.clientright.ru"
|
cors_origins: str = "http://localhost:5175,http://127.0.0.1:5175,http://147.45.146.17:5175"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cors_origins_list(self) -> List[str]:
|
def cors_origins_list(self) -> List[str]:
|
||||||
@@ -168,10 +173,10 @@ class Settings(BaseSettings):
|
|||||||
# LOGGING
|
# LOGGING
|
||||||
# ============================================
|
# ============================================
|
||||||
log_level: str = "INFO"
|
log_level: str = "INFO"
|
||||||
log_file: str = "/app/logs/erv_platform.log"
|
log_file: str = "/app/logs/ticket_form_backend.log"
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
env_file = "/var/www/fastuser/data/www/crm.clientright.ru/erv_platform/.env"
|
env_file = str(ENV_PATH)
|
||||||
case_sensitive = False
|
case_sensitive = False
|
||||||
extra = "ignore" # Игнорируем лишние поля из .env
|
extra = "ignore" # Игнорируем лишние поля из .env
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
ERV Insurance Platform - FastAPI Backend
|
Ticket Form Intake Platform - FastAPI Backend
|
||||||
"""
|
"""
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
@@ -28,7 +28,7 @@ async def lifespan(app: FastAPI):
|
|||||||
Lifecycle events: startup and shutdown
|
Lifecycle events: startup and shutdown
|
||||||
"""
|
"""
|
||||||
# STARTUP
|
# STARTUP
|
||||||
logger.info("🚀 Starting ERV Platform...")
|
logger.info("🚀 Starting Ticket Form Intake Platform...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Подключаем PostgreSQL
|
# Подключаем PostgreSQL
|
||||||
@@ -60,25 +60,25 @@ async def lifespan(app: FastAPI):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ S3 storage not available: {e}")
|
logger.warning(f"⚠️ S3 storage not available: {e}")
|
||||||
|
|
||||||
logger.info("✅ ERV Platform started successfully!")
|
logger.info("✅ Ticket Form Intake Platform started successfully!")
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
# SHUTDOWN
|
# SHUTDOWN
|
||||||
logger.info("🛑 Shutting down ERV Platform...")
|
logger.info("🛑 Shutting down Ticket Form Intake Platform...")
|
||||||
|
|
||||||
await db.disconnect()
|
await db.disconnect()
|
||||||
await redis_service.disconnect()
|
await redis_service.disconnect()
|
||||||
await rabbitmq_service.disconnect()
|
await rabbitmq_service.disconnect()
|
||||||
await policy_service.close()
|
await policy_service.close()
|
||||||
|
|
||||||
logger.info("👋 ERV Platform stopped")
|
logger.info("👋 Ticket Form Intake Platform stopped")
|
||||||
|
|
||||||
|
|
||||||
# Создаём FastAPI приложение
|
# Создаём FastAPI приложение
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="ERV Insurance Platform API",
|
title="Ticket Form Intake API",
|
||||||
description="API для обработки страховых обращений",
|
description="API для обработки обращений Ticket Form",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
lifespan=lifespan
|
lifespan=lifespan
|
||||||
)
|
)
|
||||||
@@ -106,10 +106,10 @@ app.include_router(n8n_proxy.router) # 🔒 Безопасный proxy к n8n w
|
|||||||
async def root():
|
async def root():
|
||||||
"""Главная страница API"""
|
"""Главная страница API"""
|
||||||
return {
|
return {
|
||||||
"message": "🚀 ERV Insurance Platform API",
|
"message": "🚀 Ticket Form Intake API",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"docs": "http://147.45.146.17:8100/docs"
|
"docs": f"{settings.backend_url}/docs"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -181,10 +181,10 @@ async def test():
|
|||||||
"success": True,
|
"success": True,
|
||||||
"message": "✅ Backend API работает!",
|
"message": "✅ Backend API работает!",
|
||||||
"services": {
|
"services": {
|
||||||
"redis": "localhost:6379",
|
"redis": f"{settings.redis_host}:{settings.redis_port}",
|
||||||
"postgres": "147.45.189.234:5432",
|
"postgres": f"{settings.postgres_host}:{settings.postgres_port}",
|
||||||
"ocr": "147.45.146.17:8001",
|
"ocr": settings.ocr_api_url,
|
||||||
"rabbitmq": "185.197.75.249:5672"
|
"rabbitmq": f"{settings.rabbitmq_host}:{settings.rabbitmq_port}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ async def test():
|
|||||||
async def info():
|
async def info():
|
||||||
"""Информация о платформе"""
|
"""Информация о платформе"""
|
||||||
return {
|
return {
|
||||||
"platform": "ERV Insurance Claims",
|
"platform": settings.app_name,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"tech_stack": {
|
"tech_stack": {
|
||||||
"backend": "Python FastAPI",
|
"backend": "Python FastAPI",
|
||||||
@@ -204,9 +204,9 @@ async def info():
|
|||||||
"storage": "S3 Timeweb"
|
"storage": "S3 Timeweb"
|
||||||
},
|
},
|
||||||
"features": [
|
"features": [
|
||||||
"OCR документов (паспорт, билеты)",
|
"OCR документов",
|
||||||
"AI автозаполнение (Gemini Vision)",
|
"AI автозаполнение",
|
||||||
"Проверка рейсов (FlightAware)",
|
"Проверка статуса выплат",
|
||||||
"СБП выплаты",
|
"СБП выплаты",
|
||||||
"Интеграция с CRM Vtiger"
|
"Интеграция с CRM Vtiger"
|
||||||
]
|
]
|
||||||
@@ -215,4 +215,4 @@ async def info():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8100)
|
uvicorn.run(app, host="0.0.0.0", port=8200)
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ class OCRService:
|
|||||||
"https://openrouter.ai/api/v1/chat/completions",
|
"https://openrouter.ai/api/v1/chat/completions",
|
||||||
headers={
|
headers={
|
||||||
"Authorization": f"Bearer {self.ai_api_key}",
|
"Authorization": f"Bearer {self.ai_api_key}",
|
||||||
"HTTP-Referer": "http://147.45.146.17:8100",
|
"HTTP-Referer": settings.backend_url,
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
json={
|
json={
|
||||||
|
|||||||
@@ -1,40 +1,30 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# React Frontend
|
ticket_form_frontend:
|
||||||
frontend:
|
container_name: ticket_form_frontend
|
||||||
build: ./frontend
|
build: ./frontend
|
||||||
ports:
|
ports:
|
||||||
- "5173:3000"
|
- "${TICKET_FORM_FRONTEND_PORT:-5175}:3000"
|
||||||
environment:
|
environment:
|
||||||
- REACT_APP_API_URL=http://147.45.146.17:8100
|
- VITE_API_URL=${TICKET_FORM_BACKEND_URL:-http://localhost:8200}
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
networks:
|
networks:
|
||||||
- erv-network
|
- ticket-form-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# Python FastAPI Backend
|
ticket_form_backend:
|
||||||
backend:
|
container_name: ticket_form_backend
|
||||||
build: ./backend
|
build: ./backend
|
||||||
ports:
|
ports:
|
||||||
- "8100:8100"
|
- "${TICKET_FORM_BACKEND_PORT:-8200}:8200"
|
||||||
environment:
|
env_file:
|
||||||
- REDIS_HOST=crm.clientright.ru
|
- .env
|
||||||
- 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
|
|
||||||
- N8N_POLICY_CHECK_WEBHOOK=https://n8n.clientright.pro/webhook/9eb7bc5b-645f-477d-a5d8-5a346260a265
|
|
||||||
- N8N_FILE_UPLOAD_WEBHOOK=https://n8n.clientright.pro/webhook/7e2abc64-eaca-4671-86e4-12786700fe95
|
|
||||||
# depends_on:
|
|
||||||
# - redis
|
|
||||||
# - postgres
|
|
||||||
networks:
|
networks:
|
||||||
- erv-network
|
- ticket-form-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# Redis для кеширования
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
ports:
|
ports:
|
||||||
@@ -42,10 +32,9 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
networks:
|
networks:
|
||||||
- erv-network
|
- ticket-form-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# PostgreSQL для логов и аналитики
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
environment:
|
environment:
|
||||||
@@ -57,7 +46,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- erv-network
|
- ticket-form-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
@@ -65,6 +54,6 @@ volumes:
|
|||||||
postgres_data:
|
postgres_data:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
erv-network:
|
ticket-form-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "erv-insurance-platform-frontend",
|
"name": "ticket-form-intake-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "ERV Insurance Claims Platform - Frontend",
|
"description": "Ticket Form Intake Platform - Frontend",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Form, Input, Button, Select, message, Space, Divider } from 'antd';
|
import { Form, Input, Button, Select, message, Space, Divider } from 'antd';
|
||||||
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined } from '@ant-design/icons';
|
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined, CopyOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ export default function Step3Payment({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [verifyLoading, setVerifyLoading] = useState(false);
|
const [verifyLoading, setVerifyLoading] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [debugCode, setDebugCode] = useState<string | null>(formData.smsDebugCode ?? null);
|
||||||
|
|
||||||
const sendCode = async () => {
|
const sendCode = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -41,7 +44,7 @@ export default function Step3Payment({
|
|||||||
|
|
||||||
addDebugEvent?.('sms', 'pending', `📱 Отправляю SMS на ${phone}...`, { phone });
|
addDebugEvent?.('sms', 'pending', `📱 Отправляю SMS на ${phone}...`, { phone });
|
||||||
|
|
||||||
const response = await fetch('http://147.45.146.17:8100/api/v1/sms/send', {
|
const response = await fetch(`${API_BASE_URL}/api/v1/sms/send`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ phone }),
|
body: JSON.stringify({ phone }),
|
||||||
@@ -58,6 +61,8 @@ export default function Step3Payment({
|
|||||||
message.success('Код отправлен на ваш телефон');
|
message.success('Код отправлен на ваш телефон');
|
||||||
setCodeSent(true);
|
setCodeSent(true);
|
||||||
if (result.debug_code) {
|
if (result.debug_code) {
|
||||||
|
setDebugCode(result.debug_code);
|
||||||
|
updateFormData({ smsDebugCode: result.debug_code });
|
||||||
message.info(`DEBUG: Код ${result.debug_code}`);
|
message.info(`DEBUG: Код ${result.debug_code}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -85,7 +90,7 @@ export default function Step3Payment({
|
|||||||
|
|
||||||
addDebugEvent?.('sms', 'pending', `🔐 Проверяю SMS код...`, { phone, code });
|
addDebugEvent?.('sms', 'pending', `🔐 Проверяю SMS код...`, { phone, code });
|
||||||
|
|
||||||
const response = await fetch('http://147.45.146.17:8100/api/v1/sms/verify', {
|
const response = await fetch(`${API_BASE_URL}/api/v1/sms/verify`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ phone, code }),
|
body: JSON.stringify({ phone, code }),
|
||||||
@@ -99,6 +104,8 @@ export default function Step3Payment({
|
|||||||
verified: true
|
verified: true
|
||||||
});
|
});
|
||||||
message.success('Телефон подтвержден!');
|
message.success('Телефон подтвержден!');
|
||||||
|
setDebugCode(null);
|
||||||
|
updateFormData({ smsDebugCode: undefined });
|
||||||
setIsPhoneVerified(true);
|
setIsPhoneVerified(true);
|
||||||
} else {
|
} else {
|
||||||
addDebugEvent?.('sms', 'error', `❌ Неверный код SMS`, {
|
addDebugEvent?.('sms', 'error', `❌ Неверный код SMS`, {
|
||||||
@@ -229,6 +236,35 @@ export default function Step3Payment({
|
|||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{debugCode && !isPhoneVerified && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 8,
|
||||||
|
padding: 12,
|
||||||
|
background: '#fffbe6',
|
||||||
|
borderRadius: 8,
|
||||||
|
border: '1px dashed #faad14',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<strong>DEBUG код:</strong> {debugCode}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(debugCode);
|
||||||
|
message.success('Код скопирован');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Скопировать
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
121
frontend/src/components/form/StepDescription.tsx
Normal file
121
frontend/src/components/form/StepDescription.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { Form, Input, Button, Typography, message } from 'antd';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const { TextArea } = Input;
|
||||||
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
formData: any;
|
||||||
|
updateFormData: (data: any) => void;
|
||||||
|
onPrev: () => void;
|
||||||
|
onNext: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StepDescription({
|
||||||
|
formData,
|
||||||
|
updateFormData,
|
||||||
|
onPrev,
|
||||||
|
onNext,
|
||||||
|
}: Props) {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setFieldsValue({
|
||||||
|
problemDescription: formData.problemDescription ?? '',
|
||||||
|
});
|
||||||
|
}, [form, formData.problemDescription]);
|
||||||
|
|
||||||
|
const handleContinue = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
|
||||||
|
if (!formData.session_id) {
|
||||||
|
message.error('Не найден session_id. Попробуйте обновить страницу.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubmitting(true);
|
||||||
|
|
||||||
|
const response = await fetch('/api/v1/claims/description', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
session_id: formData.session_id,
|
||||||
|
claim_id: formData.claim_id,
|
||||||
|
phone: formData.phone,
|
||||||
|
email: formData.email,
|
||||||
|
problem_description: values.problemDescription,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка API: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('Описание отправлено, продолжаем заполнение');
|
||||||
|
updateFormData(values);
|
||||||
|
onNext();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
message.error('Не получилось сохранить описание. Попробуйте ещё раз.');
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 24 }}>
|
||||||
|
<Button onClick={onPrev} size="large">
|
||||||
|
← Назад
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
padding: 24,
|
||||||
|
background: '#f6f8fa',
|
||||||
|
borderRadius: 12,
|
||||||
|
border: '1px solid #e0e6ed',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paragraph style={{ fontSize: 18, fontWeight: 600, marginBottom: 8 }}>
|
||||||
|
📄 Опишите проблему
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph type="secondary" style={{ marginBottom: 24 }}>
|
||||||
|
Расскажите, что произошло, в свободной форме. Чем больше деталей —
|
||||||
|
тем быстрее команда сможет разобраться, какие документы нужны и куда
|
||||||
|
направить заявку.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Form layout="vertical" form={form}>
|
||||||
|
<Form.Item
|
||||||
|
label="Описание ситуации"
|
||||||
|
name="problemDescription"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: 'Поле обязательно' },
|
||||||
|
{
|
||||||
|
min: 20,
|
||||||
|
message: 'Опишите, пожалуйста, минимум в пару предложений',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
autoSize={{ minRows: 6 }}
|
||||||
|
maxLength={3000}
|
||||||
|
showCount
|
||||||
|
placeholder="Например: заключил договор на оказание услуг..., деньги списали..., услугу не выполнили..."
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
|
||||||
|
<Button type="primary" size="large" onClick={handleContinue} loading={submitting}>
|
||||||
|
Продолжить →
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ interface Props {
|
|||||||
totalDocs: number;
|
totalDocs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://147.45.146.17:8100';
|
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200';
|
||||||
|
|
||||||
const StepDocumentUpload: React.FC<Props> = ({
|
const StepDocumentUpload: React.FC<Props> = ({
|
||||||
documentConfig,
|
documentConfig,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useMemo, useCallback } from 'react';
|
import { useState, useMemo, useCallback } from 'react';
|
||||||
import { Steps, Card, message, Row, Col } from 'antd';
|
import { Steps, Card, message, Row, Col } from 'antd';
|
||||||
import Step1Phone from '../components/form/Step1Phone';
|
import Step1Phone from '../components/form/Step1Phone';
|
||||||
|
import StepDescription from '../components/form/StepDescription';
|
||||||
import Step1Policy from '../components/form/Step1Policy';
|
import Step1Policy from '../components/form/Step1Policy';
|
||||||
import Step2EventType from '../components/form/Step2EventType';
|
import Step2EventType from '../components/form/Step2EventType';
|
||||||
import StepDocumentUpload from '../components/form/StepDocumentUpload';
|
import StepDocumentUpload from '../components/form/StepDocumentUpload';
|
||||||
@@ -9,6 +10,8 @@ import DebugPanel from '../components/DebugPanel';
|
|||||||
import { getDocumentsForEventType } from '../constants/documentConfigs';
|
import { getDocumentsForEventType } from '../constants/documentConfigs';
|
||||||
import './ClaimForm.css';
|
import './ClaimForm.css';
|
||||||
|
|
||||||
|
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200';
|
||||||
|
|
||||||
const { Step } = Steps;
|
const { Step } = Steps;
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
@@ -23,6 +26,7 @@ interface FormData {
|
|||||||
session_id?: string;
|
session_id?: string;
|
||||||
project_id?: string; // ✅ ID проекта в vTiger (полис)
|
project_id?: string; // ✅ ID проекта в vTiger (полис)
|
||||||
is_new_project?: boolean; // ✅ Флаг: создан новый проект
|
is_new_project?: boolean; // ✅ Флаг: создан новый проект
|
||||||
|
problemDescription?: string;
|
||||||
|
|
||||||
// Шаг 3: Event Type
|
// Шаг 3: Event Type
|
||||||
eventType?: string;
|
eventType?: string;
|
||||||
@@ -117,7 +121,7 @@ export default function ClaimForm() {
|
|||||||
try {
|
try {
|
||||||
addDebugEvent('form', 'info', '📤 Отправка заявки на сервер');
|
addDebugEvent('form', 'info', '📤 Отправка заявки на сервер');
|
||||||
|
|
||||||
const response = await fetch('http://147.45.146.17:8100/api/v1/claims/create', {
|
const response = await fetch(`${API_BASE_URL}/api/v1/claims/create`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -183,7 +187,21 @@ export default function ClaimForm() {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Шаг 2: Policy (всегда)
|
// Шаг 2: свободное описание
|
||||||
|
stepsArray.push({
|
||||||
|
title: 'Описание',
|
||||||
|
description: 'Что случилось?',
|
||||||
|
content: (
|
||||||
|
<StepDescription
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onPrev={prevStep}
|
||||||
|
onNext={nextStep}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Шаг 3: Policy (всегда)
|
||||||
stepsArray.push({
|
stepsArray.push({
|
||||||
title: 'Проверка полиса',
|
title: 'Проверка полиса',
|
||||||
description: 'Полис ERV',
|
description: 'Полис ERV',
|
||||||
@@ -197,7 +215,7 @@ export default function ClaimForm() {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Шаг 3: Event Type Selection (всегда)
|
// Шаг 4: Event Type Selection (всегда)
|
||||||
stepsArray.push({
|
stepsArray.push({
|
||||||
title: 'Тип события',
|
title: 'Тип события',
|
||||||
description: 'Выбор случая',
|
description: 'Выбор случая',
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ export default defineConfig({
|
|||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://host.docker.internal:8100',
|
target: 'http://host.docker.internal:8200',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
},
|
},
|
||||||
'/events': {
|
'/events': {
|
||||||
target: 'http://host.docker.internal:8100',
|
target: 'http://host.docker.internal:8200',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform/backend
|
cd /var/www/fastuser/data/www/crm.clientright.ru/ticket_form/backend
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
uvicorn app.main:app --host 0.0.0.0 --port 8100 --reload
|
uvicorn app.main:app --host 0.0.0.0 --port 8200 --reload
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd /var/www/fastuser/data/www/crm.clientright.ru/erv_platform/frontend
|
cd /var/www/fastuser/data/www/crm.clientright.ru/ticket_form/frontend
|
||||||
npm run dev -- --host 0.0.0.0
|
npm run dev -- --host 0.0.0.0 --port 5175
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user