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
225 lines
10 KiB
PHP
225 lines
10 KiB
PHP
<?php
|
||
/*********************************************************************************
|
||
* API-интерфейс для создания/поиска нескольких Контрагентов (Upsert Batch)
|
||
*
|
||
* Принимает JSON массив offenders, для каждого:
|
||
* - Ищет по ИНН
|
||
* - Если найден — возвращает ID (БЕЗ обновления)
|
||
* - Если не найден — создаёт новый
|
||
*
|
||
* Возвращает массив результатов с account_id для каждого offender
|
||
*
|
||
* Автор: Фёдор, 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 'includes/Loader.php';
|
||
vimport('includes.runtime.Globals');
|
||
vimport('includes.runtime.BaseModel');
|
||
vimport('includes.runtime.LanguageHandler');
|
||
|
||
/**
|
||
* Upsert нескольких контрагентов
|
||
*
|
||
* @param string $offenders_json - JSON массив offenders:
|
||
* [
|
||
* {
|
||
* "accountname": "ООО Рога и Копыта",
|
||
* "address": "Москва, ул. Ленина 1",
|
||
* "email": "info@example.com",
|
||
* "website": "example.com",
|
||
* "phone": "+7 999 123-45-67",
|
||
* "inn": "7712345678",
|
||
* "ogrn": "1234567890123",
|
||
* "role": "Турагент" // опционально, для информации
|
||
* },
|
||
* ...
|
||
* ]
|
||
* @param mixed $user - пользователь CRM
|
||
* @return string JSON с результатами
|
||
*/
|
||
function vtws_upsertaccounts($offenders_json, $user = false) {
|
||
$logFile = 'logs/UpsertAccounts.log';
|
||
$logstring = date("Y-m-d H:i:s") . ' REQUEST: ' . substr($offenders_json, 0, 2000);
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
global $adb, $current_user;
|
||
|
||
// Очистка JSON от мусора (лишние кавычки, BOM, пробелы)
|
||
$offenders_json = trim($offenders_json);
|
||
$offenders_json = preg_replace('/^\xEF\xBB\xBF/', '', $offenders_json); // Убираем BOM
|
||
|
||
// Если строка обёрнута в кавычки — убираем
|
||
if (preg_match('/^".*"$/s', $offenders_json)) {
|
||
$offenders_json = substr($offenders_json, 1, -1);
|
||
$offenders_json = stripcslashes($offenders_json); // Убираем экранирование
|
||
}
|
||
|
||
// Убираем лишнюю кавычку в конце (баг n8n)
|
||
$offenders_json = preg_replace('/"\s*$/', '', rtrim($offenders_json, '"'));
|
||
if (substr($offenders_json, -1) !== ']' && substr($offenders_json, -1) !== '}') {
|
||
// Пробуем найти конец массива/объекта
|
||
if (($pos = strrpos($offenders_json, ']')) !== false) {
|
||
$offenders_json = substr($offenders_json, 0, $pos + 1);
|
||
}
|
||
}
|
||
|
||
$logstring = date("Y-m-d H:i:s") . ' CLEANED JSON: ' . substr($offenders_json, 0, 500);
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
// Парсим JSON
|
||
$offenders = json_decode($offenders_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);
|
||
file_put_contents($logFile, date("Y-m-d H:i:s") . ' RAW: ' . $offenders_json . PHP_EOL, FILE_APPEND);
|
||
throw new WebServiceException(WebServiceErrorCode::$INVALIDID, $error);
|
||
}
|
||
|
||
if (!is_array($offenders)) {
|
||
$offenders = [$offenders]; // Если передан один объект — оборачиваем в массив
|
||
}
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' Получено offenders: ' . count($offenders);
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
// Результаты
|
||
$results = array(
|
||
'success' => true,
|
||
'total' => count($offenders),
|
||
'created' => 0,
|
||
'found' => 0,
|
||
'errors' => 0,
|
||
'accounts' => array()
|
||
);
|
||
|
||
// Обрабатываем каждого offender
|
||
foreach ($offenders as $index => $offender) {
|
||
$accountResult = array(
|
||
'index' => $index,
|
||
'success' => false,
|
||
'account_id' => null,
|
||
'action' => null,
|
||
'accountname' => $offender['accountname'] ?? '',
|
||
'inn' => $offender['inn'] ?? '',
|
||
'role' => $offender['role'] ?? null,
|
||
'message' => ''
|
||
);
|
||
|
||
try {
|
||
// Извлекаем данные
|
||
$accountname = trim($offender['accountname'] ?? '');
|
||
$address = trim($offender['address'] ?? '');
|
||
$email = trim($offender['email'] ?? '');
|
||
$website = trim($offender['website'] ?? '');
|
||
$phone = trim($offender['phone'] ?? '');
|
||
$inn = preg_replace('/[^0-9]/', '', $offender['inn'] ?? ''); // Только цифры
|
||
$ogrn = preg_replace('/[^0-9]/', '', $offender['ogrn'] ?? ''); // Только цифры
|
||
$role = trim($offender['role'] ?? '');
|
||
|
||
// Проверка обязательных полей
|
||
if (empty($accountname)) {
|
||
throw new Exception('Не указано наименование контрагента (accountname)');
|
||
}
|
||
if (empty($inn)) {
|
||
throw new Exception('Не указан ИНН');
|
||
}
|
||
|
||
// Валидация ИНН (10 или 12 цифр)
|
||
if (strlen($inn) != 10 && strlen($inn) != 12) {
|
||
$logstring = date('Y-m-d H:i:s') . " ⚠️ Нестандартный ИНН: $inn (длина " . strlen($inn) . ')';
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
// Не падаем, просто логируем
|
||
}
|
||
|
||
// ========================================
|
||
// ПОИСК ПО ИНН
|
||
// ========================================
|
||
$query = "SELECT a.accountid, a.accountname
|
||
FROM vtiger_account a
|
||
LEFT JOIN vtiger_crmentity e ON e.crmid = a.accountid
|
||
WHERE e.deleted = 0 AND a.inn = ?
|
||
LIMIT 1";
|
||
$res = $adb->pquery($query, array($inn));
|
||
|
||
if ($adb->num_rows($res) > 0) {
|
||
// === НАЙДЕН — просто возвращаем ID ===
|
||
$existingId = $adb->query_result($res, 0, 'accountid');
|
||
$existingName = $adb->query_result($res, 0, 'accountname');
|
||
|
||
$accountResult['success'] = true;
|
||
$accountResult['account_id'] = $existingId;
|
||
$accountResult['action'] = 'found';
|
||
$accountResult['message'] = 'Контрагент найден по ИНН';
|
||
$accountResult['existing_name'] = $existingName;
|
||
|
||
$results['found']++;
|
||
|
||
$logstring = date('Y-m-d H:i:s') . " ✓ [$index] Найден: $existingId ($existingName) по ИНН $inn";
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
} else {
|
||
// === НЕ НАЙДЕН — создаём ===
|
||
$params = array(
|
||
'accountname' => $accountname,
|
||
'bill_street' => $address,
|
||
'email1' => $email,
|
||
'website' => $website,
|
||
'phone' => $phone,
|
||
'inn' => $inn,
|
||
'cf_1951' => $ogrn, // ОГРН в кастомном поле
|
||
'assigned_user_id' => vtws_getWebserviceEntityId('Users', $current_user->id)
|
||
);
|
||
|
||
$logstring = date('Y-m-d H:i:s') . " 🆕 [$index] Создаём: " . json_encode($params, JSON_UNESCAPED_UNICODE);
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
$account = vtws_create('Accounts', $params, $current_user);
|
||
$newAccountId = substr($account['id'], 3); // Убираем 11x
|
||
|
||
$accountResult['success'] = true;
|
||
$accountResult['account_id'] = $newAccountId;
|
||
$accountResult['action'] = 'created';
|
||
$accountResult['message'] = 'Контрагент создан';
|
||
|
||
$results['created']++;
|
||
|
||
$logstring = date('Y-m-d H:i:s') . " ✅ [$index] Создан: $newAccountId";
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
}
|
||
|
||
} catch (WebServiceException $ex) {
|
||
$accountResult['success'] = false;
|
||
$accountResult['message'] = $ex->getMessage();
|
||
$results['errors']++;
|
||
|
||
$logstring = date('Y-m-d H:i:s') . " ❌ [$index] WebService ошибка: " . $ex->getMessage();
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
|
||
} catch (Exception $ex) {
|
||
$accountResult['success'] = false;
|
||
$accountResult['message'] = $ex->getMessage();
|
||
$results['errors']++;
|
||
|
||
$logstring = date('Y-m-d H:i:s') . " ❌ [$index] Ошибка: " . $ex->getMessage();
|
||
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
|
||
}
|
||
|
||
$results['accounts'][] = $accountResult;
|
||
}
|
||
|
||
// Итоговый статус
|
||
$results['success'] = ($results['errors'] == 0);
|
||
|
||
$logstring = date('Y-m-d H:i:s') . ' RESULT: total=' . $results['total']
|
||
. ', created=' . $results['created']
|
||
. ', found=' . $results['found']
|
||
. ', errors=' . $results['errors'] . PHP_EOL;
|
||
file_put_contents($logFile, $logstring, FILE_APPEND);
|
||
|
||
return json_encode($results, JSON_UNESCAPED_UNICODE);
|
||
} |