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