Frontend: - Changed main title to 'Подать обращение о защите прав потребителя' - Changed browser title to 'Clientright — защита прав потребителей' - Enhanced draft cards: show problem_description (250 chars), category tag, document progress bar - Fixed 'Назад' button to always return to draft selection - Added SSE connection for OCR status updates - Renamed steps: Вход, Обращение, Документы, Заявление - Skip 'Проверка полиса' and 'Тип события' steps for new claim flow Backend: - Fixed client IP extraction (X-Forwarded-For, X-Real-IP) - Added problem_title, category, documents_required_list to draft list API - Fixed documents_uploaded count to count unique field_labels CRM Webservices: - Added UpsertContact.php - create/update contacts with tgid support - Added UpsertAccounts.php - batch upsert offenders by INN - Added UpsertProject.php - create/update projects with offender mapping Database: - Fixed documents_meta duplicates in existing claims - SQL query for deduplication by field_name provided
298 lines
14 KiB
PHP
298 lines
14 KiB
PHP
<?php
|
||
/*********************************************************************************
|
||
* API-интерфейс для создания/обновления Проекта (Upsert)
|
||
*
|
||
* Логика:
|
||
* - Если передан project_id → обновляем существующий проект
|
||
* - Если project_id не передан → создаём новый
|
||
*
|
||
* Принимает JSON с данными проекта
|
||
*
|
||
* Автор: Фёдор, 2025-12-01
|
||
********************************************************************************/
|
||
|
||
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 'include/Webservices/Revise.php';
|
||
require_once 'includes/Loader.php';
|
||
vimport('includes.runtime.Globals');
|
||
vimport('includes.runtime.BaseModel');
|
||
vimport('includes.runtime.LanguageHandler');
|
||
|
||
/**
|
||
* Upsert проекта
|
||
*
|
||
* @param string $project_json - JSON с данными проекта:
|
||
* {
|
||
* "project_id": "12345", // Опционально - если есть, обновляем
|
||
* "claim_id": "uuid", // ID заявки из PostgreSQL
|
||
* "contact_id": "320096", // ID контакта (обязательно для создания)
|
||
* "result": "JSON string", // Результат UpsertAccounts (парсится автоматически)
|
||
* "offender_ids": ["390680"], // Альтернатива result - массив ID контрагентов
|
||
* "projectdata": { // Данные проекта (cf_* поля)
|
||
* "cf_2206": "SMS код",
|
||
* "cf_1830": "категория",
|
||
* ...
|
||
* }
|
||
* }
|
||
*
|
||
* Контрагенты распределяются:
|
||
* - accounts[0] → cf_2274 (основной ответчик)
|
||
* - accounts[1] → cf_2276 (агент/второй ответчик)
|
||
*
|
||
* @param mixed $user - пользователь CRM
|
||
* @return string JSON с результатом
|
||
*/
|
||
function vtws_upsertproject($project_json, $user = false) {
|
||
$logFile = 'logs/UpsertProject.log';
|
||
$logstring = date("Y-m-d H:i:s") . ' REQUEST: ' . substr($project_json, 0, 2000);
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
global $adb, $current_user;
|
||
|
||
// Очистка JSON
|
||
$project_json = trim($project_json);
|
||
$project_json = preg_replace('/^\xEF\xBB\xBF/', '', $project_json);
|
||
if (preg_match('/^".*"$/s', $project_json)) {
|
||
$project_json = substr($project_json, 1, -1);
|
||
$project_json = stripcslashes($project_json);
|
||
}
|
||
|
||
// Парсим JSON
|
||
$data = json_decode($project_json, true);
|
||
|
||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||
$error = 'Ошибка парсинга JSON: ' . json_last_error_msg();
|
||
file_put_contents($logFile, date("Y-m-d H:i:s") . ' ❌ ' . $error . PHP_EOL, FILE_APPEND);
|
||
throw new WebServiceException(WebServiceErrorCode::$INVALIDID, $error);
|
||
}
|
||
|
||
// Результат
|
||
$result = array(
|
||
'success' => false,
|
||
'project_id' => null,
|
||
'claim_id' => null,
|
||
'action' => null,
|
||
'offender_id' => null,
|
||
'agent_id' => null,
|
||
'message' => ''
|
||
);
|
||
|
||
// Извлекаем данные
|
||
$project_id = trim($data['project_id'] ?? '');
|
||
$claim_id = trim($data['claim_id'] ?? '');
|
||
$contact_id = trim($data['contact_id'] ?? '');
|
||
$projectdata = $data['projectdata'] ?? [];
|
||
|
||
// Извлекаем контрагентов из result (если передан) или из offender_ids
|
||
$offender_ids = [];
|
||
|
||
if (!empty($data['result'])) {
|
||
// Парсим result от UpsertAccounts
|
||
$accountsResult = $data['result'];
|
||
if (is_string($accountsResult)) {
|
||
$accountsResult = json_decode($accountsResult, true);
|
||
}
|
||
|
||
// Извлекаем account_id из accounts[]
|
||
if (isset($accountsResult['accounts']) && is_array($accountsResult['accounts'])) {
|
||
foreach ($accountsResult['accounts'] as $account) {
|
||
if (!empty($account['account_id'])) {
|
||
$offender_ids[] = $account['account_id'];
|
||
}
|
||
}
|
||
}
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' Извлечены offender_ids из result: ' . json_encode($offender_ids);
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
} elseif (!empty($data['offender_ids'])) {
|
||
$offender_ids = $data['offender_ids'];
|
||
}
|
||
|
||
// cf_2274 = первый контрагент (основной ответчик)
|
||
// cf_2276 = второй контрагент (агент/второй ответчик)
|
||
$offender_id = count($offender_ids) > 0 ? $offender_ids[0] : '';
|
||
$agent_id = count($offender_ids) > 1 ? $offender_ids[1] : '';
|
||
|
||
$logstring = date('Y-m-d H:i:s') . " Данные: project_id=$project_id, claim_id=$claim_id, contact_id=$contact_id, offender_id=$offender_id, agent_id=$agent_id";
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
try {
|
||
// ========================================
|
||
// ПРОВЕРКА СУЩЕСТВОВАНИЯ ПРОЕКТА
|
||
// ========================================
|
||
$existingProjectId = null;
|
||
|
||
if (!empty($project_id)) {
|
||
$project_id = preg_replace('/[^0-9]/', '', $project_id);
|
||
$query = "SELECT p.projectid FROM vtiger_project p
|
||
LEFT JOIN vtiger_crmentity e ON e.crmid = p.projectid
|
||
WHERE e.deleted = 0 AND p.projectid = ? LIMIT 1";
|
||
$res = $adb->pquery($query, array($project_id));
|
||
if ($adb->num_rows($res) > 0) {
|
||
$existingProjectId = $adb->query_result($res, 0, 'projectid');
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// ФОРМИРУЕМ ПАРАМЕТРЫ
|
||
// ========================================
|
||
$params = array();
|
||
|
||
// Если создаём новый проект - нужны contact_id и offender_id
|
||
if (empty($existingProjectId)) {
|
||
if (empty($contact_id) || empty($offender_id)) {
|
||
throw new Exception('Для создания проекта нужны contact_id и offender_ids');
|
||
}
|
||
|
||
// Получаем название контакта
|
||
$query = "SELECT c.lastname FROM vtiger_contactdetails c
|
||
LEFT JOIN vtiger_crmentity e ON e.crmid = c.contactid
|
||
WHERE e.deleted = 0 AND c.contactid = ? LIMIT 1";
|
||
$res = $adb->pquery($query, array($contact_id));
|
||
$contactName = $adb->num_rows($res) > 0 ? $adb->query_result($res, 0, 'lastname') : 'Клиент';
|
||
|
||
// Получаем название контрагента
|
||
$query = "SELECT a.accountname FROM vtiger_account a
|
||
LEFT JOIN vtiger_crmentity e ON e.crmid = a.accountid
|
||
WHERE e.deleted = 0 AND a.accountid = ? LIMIT 1";
|
||
$res = $adb->pquery($query, array($offender_id));
|
||
$accountName = $adb->num_rows($res) > 0 ? $adb->query_result($res, 0, 'accountname') : 'Контрагент';
|
||
|
||
// Название проекта
|
||
$params['projectname'] = $contactName . ' ' . $accountName;
|
||
$params['linktoaccountscontacts'] = '12x' . $contact_id;
|
||
$params['cf_2274'] = '11x' . $offender_id; // Основной ответчик
|
||
$params['projectstatus'] = 'модерация';
|
||
$params['projecttype'] = 'Претензионно - исковая работа';
|
||
$params['assigned_user_id'] = vtws_getWebserviceEntityId('Users', $current_user->id);
|
||
|
||
// Заявитель по умолчанию
|
||
if (!isset($projectdata['cf_1994'])) {
|
||
$params['cf_1994'] = vtws_getWebserviceEntityId('Accounts', 62345); // МОО КЛИЕНТПРАВ
|
||
}
|
||
}
|
||
|
||
// Агент (второй ответчик)
|
||
if (!empty($agent_id)) {
|
||
$params['cf_2276'] = '11x' . $agent_id;
|
||
}
|
||
|
||
// Связь контакт/оффендер для обновления тоже можно передать
|
||
if (!empty($contact_id) && !empty($existingProjectId)) {
|
||
$params['linktoaccountscontacts'] = '12x' . $contact_id;
|
||
}
|
||
if (!empty($offender_id) && !empty($existingProjectId)) {
|
||
$params['cf_2274'] = '11x' . $offender_id;
|
||
}
|
||
|
||
// Маппинг полей из projectdata
|
||
$fieldMapping = array(
|
||
'cf_2206' => 'cf_2206', // SMS код
|
||
'cf_2210' => 'cf_2210', // IP
|
||
'cf_2212' => 'cf_2212', // Источник
|
||
'cf_2214' => 'cf_2214', // Регион
|
||
'cf_2208' => 'cf_2208', // Form ID
|
||
'cf_1830' => 'cf_1830', // Категория
|
||
'cf_1469' => 'cf_1469', // Направление
|
||
'cf_1191' => 'cf_1191', // Цена договора
|
||
'cf_1189' => 'cf_1189', // Предмет договора
|
||
'cf_1203' => 'cf_1203', // Дата договора
|
||
'cf_1839' => 'cf_1839', // Дата начала
|
||
'cf_1841' => 'cf_1841', // Дата окончания
|
||
'cf_1207' => 'cf_1207', // Ущерб
|
||
'cf_1479' => 'cf_1479', // Стоимость услуг
|
||
'cf_1227' => 'cf_1227', // Прогресс
|
||
'cf_1231' => 'cf_1231', // Страна
|
||
'cf_1239' => 'cf_1239', // Отель
|
||
'cf_1566' => 'cf_1566', // Транспорт
|
||
'cf_1564' => 'cf_1564', // Страховка
|
||
'cf_1249' => 'cf_1249', // Прочее
|
||
'cf_1471' => 'cf_1471', // Самостоятельно
|
||
'cf_1473' => 'cf_1473', // Дата претензии
|
||
'cf_1475' => 'cf_1475', // Возвращено
|
||
'cf_1994' => 'cf_1994', // Заявитель
|
||
'description' => 'description'
|
||
);
|
||
|
||
foreach ($fieldMapping as $input => $crm) {
|
||
if (isset($projectdata[$input]) && $projectdata[$input] !== null) {
|
||
$value = $projectdata[$input];
|
||
// Для cf_1994 (Заявитель) нужен формат 11xID
|
||
if ($crm === 'cf_1994' && !empty($value) && strpos($value, 'x') === false) {
|
||
$value = vtws_getWebserviceEntityId('Accounts', $value);
|
||
}
|
||
$params[$crm] = $value;
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// СОЗДАНИЕ ИЛИ ОБНОВЛЕНИЕ
|
||
// ========================================
|
||
if (!empty($existingProjectId)) {
|
||
// === ОБНОВЛЕНИЕ ===
|
||
$params['id'] = '33x' . $existingProjectId; // 33x для Project
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' 📝 Обновляем проект ' . $existingProjectId . ': ' . json_encode($params, JSON_UNESCAPED_UNICODE);
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
$project = vtws_revise($params, $current_user);
|
||
|
||
$result['success'] = true;
|
||
$result['project_id'] = $existingProjectId;
|
||
$result['claim_id'] = $claim_id;
|
||
$result['action'] = 'updated';
|
||
$result['offender_id'] = $offender_id;
|
||
$result['agent_id'] = $agent_id ?: null;
|
||
$result['message'] = 'Проект обновлён';
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' ✅ Проект ' . $existingProjectId . ' обновлён';
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
} else {
|
||
// === СОЗДАНИЕ ===
|
||
$logstring = date('Y-m-d H:i:s') . ' 🆕 Создаём проект: ' . json_encode($params, JSON_UNESCAPED_UNICODE);
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
$project = vtws_create('Project', $params, $current_user);
|
||
$newProjectId = substr($project['id'], strpos($project['id'], 'x') + 1); // Убираем префикс (63x)
|
||
|
||
$result['success'] = true;
|
||
$result['project_id'] = $newProjectId;
|
||
$result['claim_id'] = $claim_id;
|
||
$result['action'] = 'created';
|
||
$result['offender_id'] = $offender_id;
|
||
$result['agent_id'] = $agent_id ?: null;
|
||
$result['message'] = 'Проект создан';
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' ✅ Создан проект ' . $newProjectId;
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
}
|
||
|
||
} catch (WebServiceException $ex) {
|
||
$result['success'] = false;
|
||
$result['message'] = $ex->getMessage();
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' ❌ WebService ошибка: ' . $ex->getMessage();
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
throw $ex;
|
||
|
||
} catch (Exception $ex) {
|
||
$result['success'] = false;
|
||
$result['message'] = $ex->getMessage();
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' ❌ Ошибка: ' . $ex->getMessage();
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
throw new WebServiceException(WebServiceErrorCode::$INVALIDID, $ex->getMessage());
|
||
}
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' RESULT: ' . json_encode($result, JSON_UNESCAPED_UNICODE) . PHP_EOL;
|
||
file_put_contents($logFile, $logstring, FILE_APPEND);
|
||
|
||
return json_encode($result, JSON_UNESCAPED_UNICODE);
|
||
}
|