Files
crm.clientright.ru/erv_ticket/TECHNICAL_FLOW.md

565 lines
21 KiB
Markdown
Raw Normal View History

# Техническая документация: Потоки данных и процессы
## 🔄 Диаграмма основного потока
```
┌─────────────────────────────────────────────────────────────────┐
│ ПОЛЬЗОВАТЕЛЬ │
│ (Браузер) │
└────────────┬────────────────────────────────────────────────────┘
│ GET index.php
┌─────────────────────────────────────────────────────────────────┐
│ INDEX.PHP │
│ - Определение IP через ip-api.com │
│ - Генерация session_id для sub_dir │
│ - Рендеринг формы (3 шага) │
└────────────┬────────────────────────────────────────────────────┘
│ [SMS ВЕРИФИКАЦИЯ]
├─► POST sms-test.php
│ • Генерация кода (6 цифр)
│ • Отправка через SigmaSMS API
│ • Возврат success/error
│ Пользователь вводит код
│ Проверка на клиенте (JS)
┌─────────────────────────────────────────────────────────────────┐
│ ШАГ 1: Проверка полиса │
└────────────┬────────────────────────────────────────────────────┘
├─► POST database.php
│ {
│ action: "user_verify",
│ birthday: "DD.MM.YYYY",
│ inn: "полис номер"
│ }
│ ↓
│ SELECT * FROM ci20465_erv.lexrpiority
│ WHERE voucher = 'полис номер'
│ ↓
│ Response:
│ {
│ success: "true|false",
│ message: "Полис найден|не найден",
│ result: {
│ insured_from: "дата",
│ insured_to: "дата"
│ }
│ }
┌─────────────────────────────────────────────────────────────────┐
│ Заполнение персональных данных │
│ • ФИО │
│ • Дата рождения → проверка возраста │
│ • Если < 18: показать поля законного представителя
│ • Банковские реквизиты │
└────────────┬────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ШАГ 2: Описание события │
└────────────┬────────────────────────────────────────────────────┘
├─► Выбор типа события (select)
│ • Задержка рейса
│ • Отмена рейса → показать поле подтверждения
│ • Стыковочный → показать доп. поля
│ • Посадка на запасной
│ • Поезд/паром
├─► Загрузка документов
│ ├─► Выбор файлов (макс 10, до 5MB)
│ │ • Валидация формата
│ │ • Валидация размера
│ │
│ ├─► POST fileupload_v2.php
│ │ FormData:
│ │ • files: field_name-0, field_name-1, ...
│ │ • lastname
│ │ • files_names[]
│ │ • docs_names[]
│ │ • sub_dir (session_id)
│ │ ↓
│ │ [ImageMagick convert] → PDF
│ │ [Ghostscript merge] → единый PDF
│ │ ↓
│ │ Response:
│ │ {
│ │ success: "true",
│ │ empty_file: "путь/к/объединенному.pdf",
│ │ real_file: "путь/к/оригиналу.pdf"
│ │ }
│ │
│ └─► Сохранение upload_url в data-атрибут input
┌─────────────────────────────────────────────────────────────────┐
│ ШАГ 3: Документы и согласия │
└────────────┬────────────────────────────────────────────────────┘
├─► Адрес (с автозаполнением через DaData)
├─► Документ удостоверяющий личность
├─► Страна события
├─► Email
├─► Загрузка скана паспорта
└─► Чекбокс согласия
│ [SUBMIT FORM]
┌─────────────────────────────────────────────────────────────────┐
│ ФИНАЛЬНАЯ ОТПРАВКА │
└────────────┬────────────────────────────────────────────────────┘
├─► Сбор всех данных формы
│ FormData {
│ upload_urls[]: массив путей к файлам
│ upload_urls_real[]: оригинальные пути
│ files_names[]: имена полей
│ docs_names[]: названия документов
│ docs_ticket_files_ids[]: индексы файлов билетов
│ appends[]: массив JSON-объектов с полями
│ {
│ ws_name: "имя поля",
│ ws_type: "client|contractor|project|other|ticket",
│ field_val: "значение"
│ }
│ lastname: фамилия
│ sub_dir: session_id
│ }
├─► POST https://form.clientright.ru/server_webservice2.php
│ ↓
│ [Обработка на стороне server_webservice2.php]
│ ├─► Создание записей в CRM
│ ├─► Привязка файлов
│ └─► Отправка email
├─► Показ модалки успеха (Fancybox)
└─► Redirect → https://lexpriority.ru/ok (через 30ms)
```
---
## 🎯 Детализация процессов
### 1. SMS Верификация
```javascript
// Генерация кода
sended_code = Math.floor(Math.random()*(999999-100000+1)+100000)
// Отправка
POST sms-test.php
{
smscode: "123456",
phonenumber: "9991234567"
}
// SigmaSMS API
POST https://online.sigmasms.ru/api/sendings
Headers: {
Authorization: "Token 27f89492e00973263ff746a655663678fae7203bac8b62919700e489e33b3902"
}
Body: {
type: "sms",
recipient: "79991234567",
payload: {
sender: "Clientright",
text: "Код подтверждения: 123456"
}
}
```
**Таймер**: 30 секунд до повторной отправки
---
### 2. Проверка полиса в БД
```sql
-- Запрос
SELECT * FROM ci20465_erv.lexrpiority
WHERE voucher = ?
-- Замена букв (Русская → Латинская)
Е → E
А → A
```
**Результат**:
- ✅ Найден → `cf_2446 = "1"`, скрыть загрузку полиса
-Не найден → `cf_2446 = "0"`, показать загрузку полиса
---
### 3. Загрузка и обработка файлов
#### Клиентская валидация:
```javascript
Проверки:
1. Количество ≤ 10
2. Формат ∈ ['pdf', 'jpg', 'png', 'gif', 'jpeg']
3. Размер ≤ 5 МБ
Если валидация прошла:
→ upload_file(elem)
```
#### Серверная обработка (fileupload.php):
```php
1. Получение файлов (field_name-0, field_name-1, ...)
2. Для каждого файла:
IF расширение != 'pdf':
convert image.jpg image_timestamp.pdf
→ Добавить в массив $pdfFiles[]
ELSE:
→ Добавить в массив $pdfFiles[]
→ Подсчитать страницы: identify file.pdf
3. Объединение всех PDF:
gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
-sOutputFile=output.pdf file1.pdf file2.pdf ...
4. Имя результата:
{docname}_{дата}_{фамилия}_{кол-во страниц}_CTP.pdf
Пример:
Podtverzhdayushchie_dokumenty_23-10-2025_Ivanov_15_CTP.pdf
5. Response:
{
success: "true",
message: "uploads/path/to/file.pdf"
}
```
#### Сохранение пути:
```javascript
thisfile.attr('data-uploadurl', res.empty_file)
thisfile.attr('data-uploadurl_real', res.real_file)
```
---
### 4. Формирование данных для CRM
#### Структура appends[]:
```javascript
appends[] = [
// Клиент
'{"ws_name":"lastname","ws_type":"client","field_val":"Иванов"}',
'{"ws_name":"firstname","ws_type":"client","field_val":"Иван"}',
'{"ws_name":"mobile","ws_type":"client","field_val":"9991234567"}',
'{"ws_name":"email","ws_type":"client","field_val":"ivan@mail.ru"}',
// Контрагент (ERV)
'{"ws_name":"inn","ws_type":"contractor","field_val":"7714312079"}',
'{"ws_name":"accountname","ws_type":"contractor","field_val":"Филиал ООО РСО ЕВРОИНС..."}',
// Проект (кастомные поля)
'{"ws_name":"cf_1885","ws_type":"other","field_val":"E123-456789"}', // Номер полиса
'{"ws_name":"cf_1887","ws_type":"other","field_val":"01-01-2025"}', // Дата от
'{"ws_name":"cf_1889","ws_type":"other","field_val":"31-12-2025"}', // Дата до
// Тикет
'{"ws_name":"cf_1726","ws_type":"ticket","field_val":"delay_flight"}', // Тип события
'{"ws_name":"description","ws_type":"other","field_val":"Описание..."}',
// Другие
'{"ws_name":"cf_2446","ws_type":"other","field_val":"1"}', // В базе
'{"ws_name":"cf_2502","ws_type":"project","field_val":"1"}' // Согласие
]
```
#### Маппинг ws_type:
- `client` → Модуль Contacts (Контакты)
- `contractor` → Модуль Organizations (Организации)
- `project` → Модуль HelpDesk или кастомный модуль
- `ticket` → Модуль Tickets
- `other` → Общие поля
---
### 5. Отправка в CRM (server.php или server_webservice2.php)
```php
// Подготовка данных
$new_post = [
'__vtrftk' => 'sid:session_token',
'publicid' => '3ddc71c2d79ef101c09b0d4e9c6bd08b',
'urlencodeenable' => '1',
'name' => 'websiteticket'
];
// Добавление полей из appends[]
foreach($appends as $item) {
$data = json_decode($item);
$new_post[$data->crm_name] = $data->field_val;
}
// Добавление файлов
foreach($upload_urls as $index => $url) {
$files_array[$files_names[$index]] = new CURLFile(realpath($url));
}
// Отправка
$final_post = array_merge($new_post, $files_array);
CURL POST → https://crm.clientright.ru/modules/Webforms/capture.php
```
---
## 🧩 Динамическая логика (JavaScript)
### Возрастная валидация:
```javascript
function getAge(dateString) {
// Преобразование DD-MM-YYYY → Date
var birthDate = new Date(dateString.replace(/(\d{2})-(\d{2})-(\d{4})/, "$2/$1/$3"))
var today = new Date()
var age = today.getFullYear() - birthDate.getFullYear()
// Корректировка если день рождения еще не наступил
var m = today.getMonth() - birthDate.getMonth()
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
age--
}
return age
}
// Применение
if (getAge(birthday) < 18) {
// Показать поля законного представителя
$("input[data-enableby=birthday]").removeClass('disabled')
$("input[data-disabledby=birthday]").removeClass('disabled')
} else {
// Скрыть
$("input[data-enableby=birthday]").addClass('disabled')
}
```
### Динамика типа события:
```javascript
$('select[name="event_type"]').on('change', function() {
const selectedValue = $(this).val()
// Скрыть все доп. поля
$('.connection-fields, .connection-date-fields, .cancel-flight-docs').hide()
switch(selectedValue) {
case 'miss_connection':
// Стыковочный рейс
$('#transport_number_label').text('Укажите номер рейса прибытия')
$('.connection-fields, .connection-date-fields').show()
break
case 'cancel_flight':
// Отмена рейса
$('.cancel-flight-docs').show()
break
default:
// Остальные типы
$('#transport_number_label').text('Номер рейса/поезда/парома')
}
})
```
---
## 🔍 Валидация шагов
```javascript
function validate_step(step_index) {
// Найти все обязательные поля на текущем шаге
let inputs = $('.form-step.active').find(
'input[type="text"], input[type="file"], input[type="email"], textarea, input[type="checkbox"]'
)
let res_array = []
inputs.each(function() {
let field_fill = false
// Пропустить disabled и notvalidate
if ($(this).hasClass('disabled') || $(this).hasClass('notvalidate')) {
field_fill = true
}
// Пропустить поля с ошибками
else if ($(this).hasClass('error')) {
field_fill = false
}
// Проверить заполненность
else if ($(this).val() == '') {
$(this).closest('.form-item').find('.form-item__warning')
.text('Пожалуйста, заполните все обязательные поля')
field_fill = false
}
// Email валидация
else if ($(this).attr('type') == 'email') {
if (validateEmail($(this).val())) {
field_fill = true
} else {
$(this).closest('.form-item').find('.form-item__warning')
.text($(this).data('warmes'))
field_fill = false
}
}
// Checkbox
else if ($(this).attr('type') == 'checkbox') {
field_fill = $(this).is(':checked')
}
// Остальные поля
else {
field_fill = true
}
res_array.push(field_fill)
})
// Проверка на шаге 3: обязательно согласие
if (step_index == 3 &&
$('.form-step[data-step=3]').find('input[type="checkbox"]:checked').length < 1) {
$('.form__warning').text('Необходимо согласие с политикой...')
return false
}
// Если все поля валидны
if (!res_array.includes(false)) {
$('.form__warning').hide()
return true
} else {
$('.form__warning').show()
return false
}
}
```
---
## 📊 Состояния формы
```
INITIAL STATE
├─ .sms-check (visible)
│ └─ Поле телефона
│ └─ Кнопка "Отправить SMS"
├─ .sms-success (hidden, d-none)
│ ├─ .db-validate (проверка полиса)
│ └─ .db-success (hidden, d-none)
│ ├─ .form-step[data-step=1] (персональные данные)
│ ├─ .form-step[data-step=2] (событие)
│ └─ .form-step[data-step=3] (документы)
└─ Модалки
├─ #confirm_sms (подтверждение SMS)
└─ #success_modal (успешная отправка)
AFTER SMS VERIFICATION
├─ .sms-check (disabled)
├─ .sms-success (visible)
└─ .db-validate (visible)
AFTER POLICY CHECK
├─ .db-success (visible)
└─ .form-step[data-step=1].active
NAVIGATION
index = 1 (default)
├─ Кнопка "Вперед" → index++, переход на следующий шаг
├─ Кнопка "Назад" → index--, переход на предыдущий шаг
└─ index == 3 → Показать кнопку "Подать обращение"
```
---
## 🌐 Внешние зависимости
### API:
1. **ip-api.com** - Геолокация по IP
```
GET http://ip-api.com/json/{IP}?lang=ru
```
2. **SigmaSMS** - Отправка SMS
```
POST https://online.sigmasms.ru/api/login
POST https://online.sigmasms.ru/api/sendings
```
3. **DaData** - Автозаполнение реквизитов
```
POST https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/party
```
4. **form.clientright.ru** - Обработка файлов и отправка
```
POST https://form.clientright.ru/fileupload_v2.php
POST https://form.clientright.ru/server_webservice2.php
```
### Системные утилиты:
- **ImageMagick convert** - конвертация изображений в PDF
- **Ghostscript gs** - объединение PDF
- **PHPMailer** - отправка email
---
## 🔄 Обработка ошибок
### JavaScript AJAX:
```javascript
error: function(jqXHR, exception) {
if (jqXHR.status === 0) {
alert('Not connect. Verify Network.')
} else if (jqXHR.status == 404) {
alert('Requested page not found (404).')
} else if (jqXHR.status == 500) {
alert('Internal Server Error (500).')
} else if (exception === 'parsererror') {
// Парсинг JSON ошибка
} else if (exception === 'timeout') {
alert('Time out error.')
} else if (exception === 'abort') {
alert('Ajax request aborted.')
} else {
alert('Uncaught Error. ' + jqXHR.responseText)
}
}
```
### PHP (пока отсутствует нормальная обработка):
- Только базовые try-catch в PHPMailer
- Нет логирования ошибок
- Нет пользовательских сообщений
---
## 📁 Структура session storage
```
uploads/{session_id}/
├─ original_file1.jpg
├─ original_file1_timestamp.pdf
├─ original_file2.pdf
├─ ...
└─ Podtverzhdayushchie_dokumenty_23-10-2025_Ivanov_15_CTP.pdf
```
После успешной отправки → удаление всех файлов из `uploads/`
---
Документация обновлена: **23.10.2025**