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

21 KiB
Raw Blame 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 Верификация

// Генерация кода
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. Проверка полиса в БД

-- Запрос
SELECT * FROM ci20465_erv.lexrpiority 
WHERE voucher = ?

-- Замена букв (Русская → Латинская)
Е  E
А  A

Результат:

  • Найден → cf_2446 = "1", скрыть загрузку полиса
  • Не найден → cf_2446 = "0", показать загрузку полиса

3. Загрузка и обработка файлов

Клиентская валидация:

Проверки:
1. Количество  10
2. Формат  ['pdf', 'jpg', 'png', 'gif', 'jpeg']
3. Размер  5 МБ

Если валидация прошла:
   upload_file(elem)

Серверная обработка (fileupload.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"
   }

Сохранение пути:

thisfile.attr('data-uploadurl', res.empty_file)
thisfile.attr('data-uploadurl_real', res.real_file)

4. Формирование данных для CRM

Структура appends[]:

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)

// Подготовка данных
$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)

Возрастная валидация:

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')
}

Динамика типа события:

$('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('Номер рейса/поезда/парома')
  }
})

🔍 Валидация шагов

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:

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