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