Новый endpoint для записи ответов поддержки как комментариев в CRM: - Принимает JSON с полями: answer, contact_id, project_id (опц.), support_user_id (опц.), channel (опц.) - Использует прямые INSERT запросы в vtiger_crmentity, vtiger_modcomments, vtiger_modcommentscf - Обязательно создаёт запись в vtiger_modcommentscf (иначе комментарий не отображается) - Устанавливает deleted=0 (иначе фильтруется при выборке) - Полная проверка ошибок БД с детальным логированием - Логи: logs/tg_replay_inbound.log Исправлены проблемы: - vtws_create падал без выброса исключения — заменён на прямой SQL - Убраны несуществующие колонки (from_mailconverter, customer_email, from_mailroom) - Добавлена обязательная запись в vtiger_modcommentscf
289 lines
12 KiB
PHP
289 lines
12 KiB
PHP
<?php
|
||
/*********************************************************************************
|
||
* API-интерфейс для создания Заявки (HelpDesk) из Web-формы (V2 - JSON версия)
|
||
* Принимает JSON строку с данными заявки
|
||
* Автор: Фёдор, 2025-12-29
|
||
********************************************************************************/
|
||
|
||
include_once 'include/Webservices/Query.php';
|
||
include_once 'modules/Users/Users.php';
|
||
require_once('include/Webservices/Utils.php');
|
||
require_once 'include/Webservices/Create.php';
|
||
require_once 'includes/Loader.php';
|
||
vimport ('includes.runtime.Globals');
|
||
vimport ('includes.runtime.BaseModel');
|
||
vimport ('includes.runtime.LanguageHandler');
|
||
|
||
/**
|
||
* Создание заявки из web-формы ERV Platform (V2 - JSON версия)
|
||
*
|
||
* @param string $claim_json - JSON строка с данными заявки (обязательно)
|
||
* @param object $user - пользователь (опционально)
|
||
* @return array - {"ticket_id": "123", "ticket_number": "TT12345", "title": "...", "category": "...", "status": "..."}
|
||
*/
|
||
function vtws_createwebclaimv2($claim_json, $user = false) {
|
||
|
||
$logstring = date("Y-m-d H:i:s").' REQUEST: '.json_encode($_REQUEST);
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring.PHP_EOL, FILE_APPEND);
|
||
|
||
// Проверка обязательного параметра
|
||
if(empty($claim_json)){
|
||
$logstring = date("Y-m-d H:i:s").' Не передан параметр claim_json';
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring.PHP_EOL, FILE_APPEND);
|
||
throw new WebServiceException(WebServiceErrorCode::$INVALIDID, "Не передан параметр claim_json");
|
||
}
|
||
|
||
// Парсим JSON
|
||
$claimData = json_decode($claim_json, true);
|
||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||
// Пробуем очистить от возможных лишних символов
|
||
$cleanedJson = trim($claim_json);
|
||
$cleanedJson = preg_replace('/^[^{]*/', '', $cleanedJson); // Убираем всё до первой {
|
||
$cleanedJson = preg_replace('/[^}]*$/', '', $cleanedJson); // Убираем всё после последней }
|
||
$claimData = json_decode($cleanedJson, true);
|
||
|
||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||
$logstring = date("Y-m-d H:i:s").' Ошибка парсинга JSON: '.json_last_error_msg().', JSON: '.substr($claim_json, 0, 200);
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring.PHP_EOL, FILE_APPEND);
|
||
throw new WebServiceException(WebServiceErrorCode::$INVALIDID, "Ошибка парсинга JSON: ".json_last_error_msg());
|
||
}
|
||
}
|
||
|
||
$logstring = date("Y-m-d H:i:s").' CLEANED JSON: '.json_encode($claimData);
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring.PHP_EOL, FILE_APPEND);
|
||
|
||
// Извлекаем обязательные поля
|
||
$project_id = isset($claimData['project_id']) ? $claimData['project_id'] : '';
|
||
$contact_id = isset($claimData['contact_id']) ? $claimData['contact_id'] : '';
|
||
$event_type = isset($claimData['cf_1726']) ? $claimData['cf_1726'] : '';
|
||
$description = isset($claimData['description']) ? $claimData['description'] : '';
|
||
|
||
// Проверка обязательных полей
|
||
if(empty($project_id)){
|
||
$logstring = date("Y-m-d H:i:s").' Не указано обязательное поле: project_id';
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring.PHP_EOL, FILE_APPEND);
|
||
throw new WebServiceException(WebServiceErrorCode::$INVALIDID, "Не указан ID проекта");
|
||
}
|
||
|
||
if(empty($contact_id)){
|
||
$logstring = date("Y-m-d H:i:s").' Не указано обязательное поле: contact_id';
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring.PHP_EOL, FILE_APPEND);
|
||
throw new WebServiceException(WebServiceErrorCode::$INVALIDID, "Не указан ID контакта");
|
||
}
|
||
|
||
if(empty($event_type)){
|
||
$logstring = date("Y-m-d H:i:s").' Не указано обязательное поле: cf_1726 (event_type)';
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring.PHP_EOL, FILE_APPEND);
|
||
throw new WebServiceException(WebServiceErrorCode::$INVALIDID, "Не указан тип страхового случая");
|
||
}
|
||
|
||
global $adb, $current_user;
|
||
|
||
// Нормализуем ID контакта и проекта
|
||
$contactIdNumeric = preg_replace('/[^0-9]/', '', $contact_id);
|
||
$projectIdNumeric = preg_replace('/[^0-9]/', '', $project_id);
|
||
|
||
$contactWsId = '12x' . $contactIdNumeric;
|
||
$projectWsId = '33x' . $projectIdNumeric;
|
||
|
||
$logstring = date('Y-m-d H:i:s').' Нормализовали ID: contact='.$contactIdNumeric.' (raw='.$contact_id.'), project='.$projectIdNumeric.' (raw='.$project_id.')'.PHP_EOL;
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring, FILE_APPEND);
|
||
|
||
// Маппинг типов событий на русские названия для cf_2650
|
||
$eventTypeMap = array(
|
||
'delay_flight' => 'Задержка рейса',
|
||
'cancel_flight' => 'Отмена рейса',
|
||
'miss_connection' => 'Пропуск стыковки',
|
||
'missed_connection' => 'Пропуск стыковки',
|
||
'delay_train' => 'Задержка поезда',
|
||
'cancel_train' => 'Отмена поезда',
|
||
'delay_ferry' => 'Задержка парома',
|
||
'cancel_ferry' => 'Отмена парома'
|
||
);
|
||
|
||
// ticketcategories всегда "Цифровой адвокат ЕРВ"
|
||
$ticketCategory = 'Цифровой адвокат ЕРВ';
|
||
|
||
// Нормализуем event_type для cf_2650
|
||
$normalizedEventType = isset($eventTypeMap[$event_type]) ? $eventTypeMap[$event_type] : 'Цифровой адвокат ЕРВ';
|
||
|
||
// Извлекаем дополнительные поля
|
||
$incident_date = isset($claimData['cf_2566']) ? $claimData['cf_2566'] : '';
|
||
$transport_number = isset($claimData['cf_2568']) ? $claimData['cf_2568'] : '';
|
||
$cf_1885 = isset($claimData['cf_1885']) ? $claimData['cf_1885'] : '';
|
||
$lastname = isset($claimData['lastname']) ? $claimData['lastname'] : '';
|
||
$firstname = isset($claimData['firstname']) ? $claimData['firstname'] : '';
|
||
|
||
// Формируем ticket_title: event_type_cf_1885_lastname_firstname
|
||
$ticket_title = $event_type;
|
||
if (!empty($cf_1885)) {
|
||
$ticket_title .= '_' . $cf_1885;
|
||
}
|
||
if (!empty($lastname)) {
|
||
$ticket_title .= '_' . $lastname;
|
||
}
|
||
if (!empty($firstname)) {
|
||
$ticket_title .= '_' . $firstname;
|
||
}
|
||
|
||
// Формируем описание
|
||
$fullDescription = '';
|
||
if (!empty($description)) {
|
||
$fullDescription .= $description . "\n\n";
|
||
}
|
||
|
||
$fullDescription .= "Тип события: " . $normalizedEventType . "\n";
|
||
|
||
if (!empty($incident_date)) {
|
||
$fullDescription .= "Дата инцидента: " . $incident_date . "\n";
|
||
}
|
||
if (!empty($transport_number)) {
|
||
$fullDescription .= "Номер рейса: " . $transport_number . "\n";
|
||
}
|
||
|
||
// Добавляем cf_departure_flight и cf_departure_date, если есть
|
||
$cf_departure_flight = isset($claimData['cf_departure_flight']) ? $claimData['cf_departure_flight'] : '';
|
||
$cf_departure_date = isset($claimData['cf_departure_date']) ? $claimData['cf_departure_date'] : '';
|
||
|
||
if (!empty($cf_departure_flight)) {
|
||
$fullDescription .= "Рейс стыковки: " . $cf_departure_flight . "\n";
|
||
}
|
||
if (!empty($cf_departure_date)) {
|
||
$fullDescription .= "Дата стыковки: " . $cf_departure_date . "\n";
|
||
}
|
||
|
||
$fullDescription .= "\nИсточник: ERV Platform Web Form";
|
||
|
||
// Формируем массив параметров для создания заявки
|
||
$params = array(
|
||
'ticket_title' => $ticket_title,
|
||
'parent_id' => '11x67458', // Заявитель - контрагент
|
||
'ticketcategories' => $ticketCategory,
|
||
'ticketstatus' => 'рассмотрение',
|
||
'contact_id' => $contactWsId,
|
||
'cf_2066' => $projectWsId, // Связь с проектом
|
||
'ticketpriorities' => 'High',
|
||
'assigned_user_id' => vtws_getWebserviceEntityId('Users', $current_user->id),
|
||
'description' => $fullDescription,
|
||
'cf_1726' => $event_type, // Сырой тип события
|
||
'cf_2650' => $normalizedEventType // Нормализованный тип события
|
||
);
|
||
|
||
// Маппинг дополнительных полей
|
||
if (!empty($incident_date)) {
|
||
$params['cf_2566'] = $incident_date;
|
||
}
|
||
if (!empty($transport_number)) {
|
||
$params['cf_2568'] = $transport_number;
|
||
}
|
||
if (!empty($cf_departure_flight)) {
|
||
$params['cf_2630'] = $cf_departure_flight;
|
||
}
|
||
if (!empty($cf_departure_date)) {
|
||
$params['cf_2632'] = $cf_departure_date;
|
||
}
|
||
|
||
// Страна (cf_1909 → cf_2636)
|
||
if (isset($claimData['cf_1909']) && !empty($claimData['cf_1909'])) {
|
||
$params['cf_2636'] = $claimData['cf_1909'];
|
||
}
|
||
|
||
// cf_2502 → cf_2572
|
||
if (isset($claimData['cf_2502']) && !empty($claimData['cf_2502'])) {
|
||
$params['cf_2572'] = $claimData['cf_2502'];
|
||
}
|
||
|
||
// code → cf_2574
|
||
if (isset($claimData['code']) && !empty($claimData['code'])) {
|
||
$params['cf_2574'] = $claimData['code'];
|
||
}
|
||
|
||
// cf_1885 → cf_2642
|
||
if (!empty($cf_1885)) {
|
||
$params['cf_2642'] = $cf_1885;
|
||
}
|
||
|
||
// IP → cf_2634
|
||
if (isset($claimData['ip']) && !empty($claimData['ip'])) {
|
||
$params['cf_2634'] = $claimData['ip'];
|
||
}
|
||
|
||
// region → cf_2640
|
||
if (isset($claimData['region']) && !empty($claimData['region'])) {
|
||
$params['cf_2640'] = $claimData['region'];
|
||
}
|
||
|
||
// source → cf_2638
|
||
if (isset($claimData['source']) && !empty($claimData['source'])) {
|
||
$params['cf_2638'] = $claimData['source'];
|
||
}
|
||
|
||
// cf_2508 → cf_2508 (прямое маппирование)
|
||
if (isset($claimData['cf_2508']) && !empty($claimData['cf_2508'])) {
|
||
$params['cf_2508'] = $claimData['cf_2508'];
|
||
}
|
||
|
||
// cf_2648 → cf_2648 (прямое маппирование)
|
||
if (isset($claimData['cf_2648']) && !empty($claimData['cf_2648'])) {
|
||
$params['cf_2648'] = $claimData['cf_2648'];
|
||
}
|
||
|
||
$logstring = date('Y-m-d H:i:s').' Массив для создания Заявки: '.json_encode($params).PHP_EOL;
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring, FILE_APPEND);
|
||
|
||
try {
|
||
$result = vtws_create('HelpDesk', $params, $current_user);
|
||
|
||
$ticketId = substr($result['id'], 3); // Убираем префикс "17x"
|
||
$ticketNumber = isset($result['ticket_no']) ? $result['ticket_no'] : 'N/A';
|
||
|
||
$logstring = date('Y-m-d H:i:s').' ✅ Создана Заявка id='.$ticketId.' ticket_no='.$ticketNumber.PHP_EOL;
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring, FILE_APPEND);
|
||
|
||
// Создаём двустороннюю связь между Проектом и Заявкой
|
||
try {
|
||
$relationCheck = $adb->pquery(
|
||
"SELECT 1 FROM vtiger_crmentityrel
|
||
WHERE (crmid = ? AND relcrmid = ?)
|
||
OR (crmid = ? AND relcrmid = ?)
|
||
LIMIT 1",
|
||
array($projectIdNumeric, $ticketId, $ticketId, $projectIdNumeric)
|
||
);
|
||
|
||
if (!$relationCheck || $adb->num_rows($relationCheck) === 0) {
|
||
$adb->pquery(
|
||
"INSERT INTO vtiger_crmentityrel (crmid, module, relcrmid, relmodule) VALUES (?, ?, ?, ?)",
|
||
array($projectIdNumeric, 'Project', $ticketId, 'HelpDesk')
|
||
);
|
||
$logstring = date('Y-m-d H:i:s').' 🔗 Добавлена связь Project('.$projectIdNumeric.') ⇄ HelpDesk('.$ticketId.')'.PHP_EOL;
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring, FILE_APPEND);
|
||
} else {
|
||
$logstring = date('Y-m-d H:i:s').' 🔗 Связь Project('.$projectIdNumeric.') ⇄ HelpDesk('.$ticketId.') уже существует'.PHP_EOL;
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring, FILE_APPEND);
|
||
}
|
||
} catch (Exception $relEx) {
|
||
$logstring = date('Y-m-d H:i:s').' ⚠️ Ошибка связывания Project('.$projectIdNumeric.') ⇄ HelpDesk('.$ticketId.'): '.$relEx->getMessage().PHP_EOL;
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring, FILE_APPEND);
|
||
}
|
||
|
||
// Возвращаем массив
|
||
$output = array(
|
||
'ticket_id' => $ticketId,
|
||
'ticket_number' => $ticketNumber,
|
||
'title' => $ticket_title,
|
||
'category' => $ticketCategory,
|
||
'status' => 'рассмотрение'
|
||
);
|
||
|
||
} catch (WebServiceException $ex) {
|
||
$logstring = date('Y-m-d H:i:s').' ❌ Ошибка создания: '.$ex->getMessage().PHP_EOL;
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring, FILE_APPEND);
|
||
throw $ex;
|
||
}
|
||
|
||
$logstring = date('Y-m-d H:i:s').' Return: '.json_encode($output).PHP_EOL;
|
||
file_put_contents('logs/CreateWebClaimV2.log', $logstring, FILE_APPEND);
|
||
|
||
return $output;
|
||
}
|