Files
crm.clientright.ru/erv_ticket/TECHNICAL_FLOW.md
Fedor 9245768987 🚀 CRM Files Migration & Real-time Features
 Features:
- Migrated ALL files to new S3 structure (Projects, Contacts, Accounts, HelpDesk, Invoice, etc.)
- Added Nextcloud folder buttons to ALL modules
- Fixed Nextcloud editor integration
- WebSocket server for real-time updates
- Redis Pub/Sub integration
- File path manager for organized storage
- Redis caching for performance (Functions.php)

📁 New Structure:
Documents/Project/ProjectName_ID/file_docID.ext
Documents/Contacts/FirstName_LastName_ID/file_docID.ext
Documents/Accounts/AccountName_ID/file_docID.ext

🔧 Technical:
- FilePathManager for standardized paths
- S3StorageService integration
- WebSocket server (Node.js + Docker)
- Redis cache for getBasicModuleInfo()
- Predis library for Redis connectivity

📝 Scripts:
- Migration scripts for all modules
- Test pages for WebSocket/SSE/Polling
- Documentation (MIGRATION_*.md, REDIS_*.md)

🎯 Result: 15,000+ files migrated successfully!
2025-10-24 19:59:28 +03:00

565 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Техническая документация: Потоки данных и процессы
## 🔄 Диаграмма основного потока
```
┌─────────────────────────────────────────────────────────────────┐
│ ПОЛЬЗОВАТЕЛЬ │
│ (Браузер) │
└────────────┬────────────────────────────────────────────────────┘
│ 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**