Files
crm.clientright.ru/include/Webservices/UpsertContact.php
Fedor 01c4fe80b5 chore: snapshot current working tree changes
Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
2026-03-26 14:19:01 +03:00

446 lines
23 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*********************************************************************************
* API-интерфейс для создания/обновления Контакта (Upsert)
* Гибкий метод: обновляет если найден, создаёт если нет
*
* Приоритет поиска:
* 1. contact_id (если передан - сразу обновляем)
* 2. mobile (ищем по мобильному)
* 3. tgid (ищем по полю phone)
* 4. chat_id по каналу (telegram → cf_2674, max → cf_2676)
* 5. unified_id (cf_2616)
*
* Все поля опциональны, кроме хотя бы одного идентификатора
*
* Автор: Фёдор, 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 $contact_id - ID контакта в CRM (если известен)
* @param string $mobile - мобильный телефон
* @param string $tgid - telegram ID
* @param string $firstname - имя
* @param string $secondname - отчество
* @param string $lastname - фамилия
* @param string $email - email
* @param string $birthday - дата рождения
* @param string $birthplace - место рождения
* @param string $mailingstreet - адрес
* @param string $inn - ИНН
* @param string $requisites - реквизиты
* @param string $code - SMS код верификации
* @param string $unified_id - внешний единый ID
* @param string $entry_channel - канал входа (telegram | max)
* @param string $chat_id - ID чата (пишется в cf_2674 или cf_2676 по каналу)
* @param string $registration_address - адрес регистрации (otherstreet)
* @param string $bank_for_compensation - банк для компенсации (cf_1265)
* @param mixed $user - пользователь CRM
* @return string JSON с результатом
*/
function vtws_upsertcontact(
$contact_id = '',
$mobile = '',
$tgid = '',
$firstname = '',
$secondname = '',
$lastname = '',
$email = '',
$birthday = '',
$birthplace = '',
$mailingstreet = '',
$inn = '',
$requisites = '',
$code = '',
$unified_id = '',
$entry_channel = '',
$chat_id = '',
$registration_address = '',
$bank_for_compensation = '',
$user = false
) {
$logFile = 'logs/UpsertContact.log';
$logstring = date("Y-m-d H:i:s") . ' REQUEST: ' . json_encode($_REQUEST);
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
global $adb, $current_user;
if (!isset($adb) || $adb === null) {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' FATAL: $adb is null' . PHP_EOL, FILE_APPEND);
throw new WebServiceException(WebServiceErrorCode::$INTERNALERROR, 'Database connection not available');
}
// Важно: не позволяем PearDatabase "die()" при SQL-ошибках, иначе ответ будет пустой 500 без логов ниже
if (method_exists($adb, 'setDieOnError')) {
$adb->setDieOnError(false);
}
$adb->dieOnError = false;
file_put_contents($logFile, date('Y-m-d H:i:s') . ' DB dieOnError=' . (isset($adb->dieOnError) ? (int)$adb->dieOnError : -1) . PHP_EOL, FILE_APPEND);
// Если первый аргумент — JSON-строка (n8n передаёт contact_json), разбираем и подставляем в $_REQUEST
if (is_string($contact_id) && $contact_id !== '' && (strpos(trim($contact_id), '{') === 0)) {
$decoded = json_decode($contact_id, true);
if (is_array($decoded)) {
foreach ($decoded as $k => $v) {
if ($v !== '' && $v !== null && !is_array($v)) {
$_REQUEST[$k] = $v;
$_POST[$k] = $v;
}
}
$contact_id = '';
}
}
// Подстановка альтернативных имён полей из JSON/n8n (first_name → firstname и т.д.)
if (empty($firstname) && isset($_REQUEST['first_name']) && $_REQUEST['first_name'] !== '') $firstname = $_REQUEST['first_name'];
if (empty($lastname) && isset($_REQUEST['last_name']) && $_REQUEST['last_name'] !== '') $lastname = $_REQUEST['last_name'];
if (empty($secondname) && isset($_REQUEST['middle_name']) && $_REQUEST['middle_name'] !== '') $secondname = $_REQUEST['middle_name'];
if (empty($mobile) && isset($_REQUEST['phone']) && $_REQUEST['phone'] !== '') $mobile = $_REQUEST['phone'];
if (empty($birthday) && isset($_REQUEST['birth_date']) && $_REQUEST['birth_date'] !== '') $birthday = $_REQUEST['birth_date'];
if (empty($birthplace) && isset($_REQUEST['birth_place']) && $_REQUEST['birth_place'] !== '') $birthplace = $_REQUEST['birth_place'];
if (empty($mailingstreet) && isset($_REQUEST['mailing_address']) && $_REQUEST['mailing_address'] !== '') $mailingstreet = $_REQUEST['mailing_address'];
// Нормализуем ключевые идентификаторы из $_REQUEST (на случай, если операция в CRM зарегистрирована не всеми параметрами)
if ((empty($contact_id) || !is_scalar($contact_id)) && isset($_REQUEST['contact_id']) && $_REQUEST['contact_id'] !== '') {
$contact_id = $_REQUEST['contact_id'];
}
if ((empty($mobile) || !is_scalar($mobile)) && isset($_REQUEST['mobile']) && $_REQUEST['mobile'] !== '') {
$mobile = $_REQUEST['mobile'];
}
if ((empty($mobile) || !is_scalar($mobile)) && isset($_REQUEST['phone']) && $_REQUEST['phone'] !== '') {
$mobile = $_REQUEST['phone'];
}
if ((empty($tgid) || !is_scalar($tgid)) && isset($_REQUEST['tgid']) && $_REQUEST['tgid'] !== '') {
$tgid = $_REQUEST['tgid'];
}
if ((empty($unified_id) || !is_scalar($unified_id)) && isset($_REQUEST['unified_id']) && $_REQUEST['unified_id'] !== '') {
$unified_id = $_REQUEST['unified_id'];
}
if ((empty($entry_channel) || !is_scalar($entry_channel)) && isset($_REQUEST['entry_channel']) && $_REQUEST['entry_channel'] !== '') {
$entry_channel = $_REQUEST['entry_channel'];
}
if ((empty($chat_id) || !is_scalar($chat_id)) && isset($_REQUEST['chat_id']) && $_REQUEST['chat_id'] !== '') {
$chat_id = $_REQUEST['chat_id'];
}
if ((empty($registration_address) || !is_scalar($registration_address)) && isset($_REQUEST['registration_address']) && $_REQUEST['registration_address'] !== '') {
$registration_address = $_REQUEST['registration_address'];
}
if ((empty($registration_address) || !is_scalar($registration_address)) && isset($_REQUEST['otherstreet']) && $_REQUEST['otherstreet'] !== '') {
$registration_address = $_REQUEST['otherstreet'];
}
if ((empty($bank_for_compensation) || !is_scalar($bank_for_compensation)) && isset($_REQUEST['bank_for_compensation']) && $_REQUEST['bank_for_compensation'] !== '') {
$bank_for_compensation = $_REQUEST['bank_for_compensation'];
}
// Скаляризация (vtiger/ADODB ожидают строки)
$contact_id = is_scalar($contact_id) ? (string)$contact_id : '';
$tgid = is_scalar($tgid) ? (string)$tgid : '';
$unified_id = is_scalar($unified_id) ? (string)$unified_id : '';
$entry_channel = is_scalar($entry_channel) ? (string)$entry_channel : '';
$chat_id = is_scalar($chat_id) ? (string)$chat_id : '';
$registration_address = is_scalar($registration_address) ? (string)$registration_address : '';
$bank_for_compensation = is_scalar($bank_for_compensation) ? (string)$bank_for_compensation : '';
try {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' TRY_START' . PHP_EOL, FILE_APPEND);
// Результат
$result = array(
'success' => false,
'contact_id' => null,
'action' => null, // 'created', 'updated', 'found'
'message' => ''
);
// ========================================
// 1. ФОРМАТИРОВАНИЕ ТЕЛЕФОНА
// ========================================
$mobile = is_scalar($mobile) ? (string)$mobile : '';
if (!empty($mobile)) {
$mobile = preg_replace('/[^0-9]/', '', $mobile);
if (strlen($mobile) == 11 && $mobile[0] == '8') {
$mobile = "7" . substr($mobile, 1);
} else if (strlen($mobile) == 10) {
$mobile = "7" . $mobile;
} else if (strlen($mobile) != 11) {
// Некорректный номер - логируем, но не падаем
$logstring = date("Y-m-d H:i:s") . ' ⚠️ Некорректный номер телефона: ' . $mobile . ' (игнорируем)';
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
$mobile = ''; // Обнуляем некорректный номер
}
}
// ========================================
// 1b. КОНВЕРТАЦИЯ ДАТЫ РОЖДЕНИЯ (d.m.Y → Y-m-d)
// ========================================
$birthday = is_scalar($birthday) ? trim((string)$birthday) : '';
if (!empty($birthday) && preg_match('/^\d{1,2}\.\d{1,2}\.\d{4}$/', $birthday)) {
$dt = DateTime::createFromFormat('d.m.Y', trim($birthday));
if ($dt) {
$birthday = $dt->format('Y-m-d');
}
}
file_put_contents($logFile, date('Y-m-d H:i:s') . ' BEFORE_SEARCH' . PHP_EOL, FILE_APPEND);
// ========================================
// 2. ПОИСК СУЩЕСТВУЮЩЕГО КОНТАКТА
// ========================================
$existingContactId = null;
$searchMethod = '';
// 2.1 По contact_id (приоритет 1)
if (!empty($contact_id)) {
$contact_id = preg_replace('/[^0-9]/', '', $contact_id); // Очищаем от 12x префикса
$query = "SELECT c.contactid 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), false, 'UpsertContact search by contact_id');
if (!$res) {
$err = method_exists($adb, 'errorMsg') ? $adb->errorMsg() : 'unknown db error';
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ⚠️ SQL error (by_contact_id): ' . $err . PHP_EOL, FILE_APPEND);
} elseif ($adb->num_rows($res) > 0) {
$existingContactId = $adb->query_result($res, 0, 'contactid');
$searchMethod = 'by_contact_id';
}
}
// 2.2 По mobile (приоритет 2)
if (empty($existingContactId) && !empty($mobile)) {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_MOBILE_START' . PHP_EOL, FILE_APPEND);
$query = "SELECT c.contactid FROM vtiger_contactdetails c
LEFT JOIN vtiger_crmentity e ON e.crmid = c.contactid
WHERE e.deleted = 0 AND c.mobile = ? LIMIT 1";
$res = $adb->pquery($query, array($mobile), false, 'UpsertContact search by mobile');
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_MOBILE_DONE res=' . ($res ? '1' : '0') . PHP_EOL, FILE_APPEND);
if (!$res) {
$err = method_exists($adb, 'errorMsg') ? $adb->errorMsg() : 'unknown db error';
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ⚠️ SQL error (by_mobile): ' . $err . PHP_EOL, FILE_APPEND);
} else {
$rows = $adb->num_rows($res);
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_MOBILE_ROWS=' . (string)$rows . PHP_EOL, FILE_APPEND);
if ($rows > 0) {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_MOBILE_RESULT_START' . PHP_EOL, FILE_APPEND);
$existingContactId = $adb->query_result($res, 0, 'contactid');
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_MOBILE_RESULT contactid=' . (string)$existingContactId . PHP_EOL, FILE_APPEND);
$searchMethod = 'by_mobile';
}
}
}
// 2.3 По tgid (приоритет 3) - tgid хранится в поле phone
if (empty($existingContactId) && !empty($tgid)) {
$query = "SELECT c.contactid FROM vtiger_contactdetails c
LEFT JOIN vtiger_crmentity e ON e.crmid = c.contactid
WHERE e.deleted = 0 AND c.phone = ? LIMIT 1";
$res = $adb->pquery($query, array($tgid), false, 'UpsertContact search by tgid');
if (!$res) {
$err = method_exists($adb, 'errorMsg') ? $adb->errorMsg() : 'unknown db error';
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ⚠️ SQL error (by_tgid): ' . $err . PHP_EOL, FILE_APPEND);
} elseif ($adb->num_rows($res) > 0) {
$existingContactId = $adb->query_result($res, 0, 'contactid');
$searchMethod = 'by_tgid';
}
}
// 2.4 По chat_id по каналу (приоритет 4): telegram → cf_2674, max → cf_2676
if (empty($existingContactId) && !empty($chat_id) && !empty($entry_channel)) {
try {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_CHAT_START channel=' . (string)$entry_channel . ' chat_id=' . (string)$chat_id . PHP_EOL, FILE_APPEND);
$cfColumn = (strtolower(trim($entry_channel)) === 'max') ? 'cf_2676' : 'cf_2674';
$query = "SELECT sc.contactid FROM vtiger_contactscf sc
INNER JOIN vtiger_crmentity e ON e.crmid = sc.contactid
WHERE e.deleted = 0 AND sc.{$cfColumn} = ? LIMIT 1";
$res = $adb->pquery($query, array($chat_id), false, 'UpsertContact search by chat_id');
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_CHAT_DONE res=' . ($res ? '1' : '0') . PHP_EOL, FILE_APPEND);
if (!$res) {
$err = method_exists($adb, 'errorMsg') ? $adb->errorMsg() : 'unknown db error';
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ⚠️ SQL error (by_chat_id): ' . $err . PHP_EOL, FILE_APPEND);
} elseif ($adb->num_rows($res) > 0) {
$existingContactId = $adb->query_result($res, 0, 'contactid');
$searchMethod = 'by_chat_id';
}
} catch (Exception $e) {
$logstring = date('Y-m-d H:i:s') . ' Поиск по chat_id пропущен: ' . $e->getMessage();
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
}
}
// 2.5 По unified_id (приоритет 5) — cf_2616
if (empty($existingContactId) && !empty($unified_id)) {
try {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_UNIFIED_START unified_id=' . (string)$unified_id . PHP_EOL, FILE_APPEND);
$query = "SELECT sc.contactid FROM vtiger_contactscf sc
INNER JOIN vtiger_crmentity e ON e.crmid = sc.contactid
WHERE e.deleted = 0 AND sc.cf_2616 = ? LIMIT 1";
$res = $adb->pquery($query, array($unified_id), false, 'UpsertContact search by unified_id');
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Q_BY_UNIFIED_DONE res=' . ($res ? '1' : '0') . PHP_EOL, FILE_APPEND);
if (!$res) {
$err = method_exists($adb, 'errorMsg') ? $adb->errorMsg() : 'unknown db error';
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ⚠️ SQL error (by_unified_id): ' . $err . PHP_EOL, FILE_APPEND);
} elseif ($adb->num_rows($res) > 0) {
$existingContactId = $adb->query_result($res, 0, 'contactid');
$searchMethod = 'by_unified_id';
}
} catch (Exception $e) {
$logstring = date('Y-m-d H:i:s') . ' Поиск по unified_id пропущен: ' . $e->getMessage();
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
}
}
$logstring = date('Y-m-d H:i:s') . ' Поиск: contact_id=' . $contact_id . ', mobile=' . $mobile . ', tgid=' . $tgid;
$logstring .= ' → Найден: ' . ($existingContactId ? $existingContactId . ' (' . $searchMethod . ')' : 'НЕТ');
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
// ========================================
// 3. ФОРМИРУЕМ ПАРАМЕТРЫ
// ========================================
$params = array();
// Только непустые поля добавляем в params
if (!empty($firstname)) $params['firstname'] = $firstname;
if (!empty($secondname)) $params['cf_1157'] = $secondname; // Отчество
if (!empty($lastname)) $params['lastname'] = $lastname;
if (!empty($mobile)) $params['mobile'] = $mobile;
if (!empty($email)) $params['email'] = $email;
if (!empty($tgid)) $params['phone'] = $tgid; // TG ID в поле phone
if (!empty($birthday)) $params['birthday'] = $birthday;
if (!empty($birthplace)) $params['cf_1263'] = $birthplace; // Место рождения
if (!empty($mailingstreet)) $params['mailingstreet'] = $mailingstreet;
if (!empty($inn)) $params['cf_1257'] = $inn; // ИНН
if (!empty($requisites)) $params['cf_1849'] = $requisites; // Реквизиты
if (!empty($code)) $params['cf_1580'] = $code; // SMS код
if (!empty($unified_id)) $params['cf_2616'] = $unified_id;
if (!empty($registration_address)) $params['otherstreet'] = $registration_address;
if (!empty($bank_for_compensation)) $params['cf_1265'] = $bank_for_compensation;
if (!empty($entry_channel) && !empty($chat_id)) {
if (strtolower(trim($entry_channel)) === 'telegram') {
$params['cf_2674'] = $chat_id;
} elseif (strtolower(trim($entry_channel)) === 'max') {
$params['cf_2676'] = $chat_id;
}
}
// ========================================
// 4. СОЗДАНИЕ ИЛИ ОБНОВЛЕНИЕ
// ========================================
try {
if (!empty($existingContactId)) {
// === ОБНОВЛЕНИЕ ===
$params['id'] = '12x' . $existingContactId;
// vtiger ожидает скалярные значения
foreach ($params as $k => $v) {
if (is_array($v) || is_object($v)) {
unset($params[$k]);
} else {
$params[$k] = (string)$v;
}
}
$logstring = date('Y-m-d H:i:s') . ' 📝 Обновляем контакт ' . $existingContactId . ': ' . json_encode($params);
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
$contact = vtws_revise($params, $current_user);
$result['success'] = true;
$result['contact_id'] = $existingContactId;
$result['action'] = 'updated';
$result['search_method'] = $searchMethod;
$result['message'] = 'Контакт обновлён';
$result['write'] = array(
'module' => 'Contacts',
'method' => 'vtws_revise',
'id' => '12x' . $existingContactId,
'fields' => $params,
);
$logstring = date('Y-m-d H:i:s') . ' ✅ Контакт ' . $existingContactId . ' обновлён';
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
} else {
// === СОЗДАНИЕ ===
// Проверяем минимальные данные для создания
$hasIdentifier = !empty($mobile) || !empty($tgid) || (!empty($chat_id) && !empty($entry_channel));
if (!$hasIdentifier) {
throw new WebServiceException(
WebServiceErrorCode::$INVALIDID,
"Для создания контакта нужен хотя бы mobile, tgid или пара entry_channel+chat_id"
);
}
// Дефолтные значения для обязательных полей CRM
if (empty($params['firstname'])) {
$params['firstname'] = 'Клиент';
}
if (empty($params['lastname'])) {
$suffix = !empty($mobile) ? substr($mobile, -4) : (!empty($tgid) ? substr($tgid, -4) : substr($chat_id, -4));
$params['lastname'] = 'Web_' . $suffix;
}
if (empty($params['birthday'])) {
$params['birthday'] = '01-01-1990';
}
// Назначаем ответственного
$params['assigned_user_id'] = vtws_getWebserviceEntityId('Users', $current_user->id);
// vtiger ожидает скалярные значения
foreach ($params as $k => $v) {
if (is_array($v) || is_object($v)) {
unset($params[$k]);
} else {
$params[$k] = (string)$v;
}
}
$logstring = date('Y-m-d H:i:s') . ' 🆕 Создаём контакт: ' . json_encode($params);
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
$contact = vtws_create('Contacts', $params, $current_user);
$newContactId = substr($contact['id'], 3); // Убираем 12x
$result['success'] = true;
$result['contact_id'] = $newContactId;
$result['action'] = 'created';
$result['message'] = 'Контакт создан';
$result['write'] = array(
'module' => 'Contacts',
'method' => 'vtws_create',
'id' => '12x' . $newContactId,
'fields' => $params,
);
$logstring = date('Y-m-d H:i:s') . ' ✅ Создан контакт ' . $newContactId;
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') . ' ❌ Ошибка: ' . $ex->getMessage();
file_put_contents($logFile, $logstring . PHP_EOL, FILE_APPEND);
throw $ex;
}
$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);
} catch (Throwable $e) {
$errMsg = $e->getMessage();
$errTrace = $e->getTraceAsString();
file_put_contents($logFile, date('Y-m-d H:i:s') . ' FATAL: ' . $errMsg . PHP_EOL . $errTrace . PHP_EOL, FILE_APPEND);
throw new WebServiceException(WebServiceErrorCode::$INTERNALERROR, 'UpsertContact error: ' . $errMsg);
}
}