Files
erv-clientright/upload_file_crm_v2.php
2026-03-13 10:42:01 +03:00

428 lines
18 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
error_reporting(E_ALL);
ini_set('display_errors', '1');
// Для POST запросов отключаем вывод ошибок в HTML
$IS_POST = ($_SERVER['REQUEST_METHOD'] === 'POST');
if ($IS_POST) {
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
}
// Функция для записи в лог
function writeLog($message) {
$timestamp = date('Y-m-d H:i:s');
$line = "[$timestamp] $message\n";
// Пробуем писать в основной лог
@file_put_contents(__DIR__ . '/logs/upload_documents_v2.log', $line, FILE_APPEND | LOCK_EX);
// И в /tmp как запасной вариант
@file_put_contents('/tmp/upload_documents_v2.log', $line, FILE_APPEND | LOCK_EX);
// И в системный лог
error_log('[upload_documents_v2] ' . $message);
}
// Функция для JSON ответа
function json_response($data, $code = 200) {
if (!headers_sent()) {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
}
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
// Быстрый ping
if (isset($_GET['ping'])) {
header('Content-Type: text/plain; charset=utf-8');
echo 'pong v2';
exit;
}
// Для POST запросов регистрируем обработчик фатальных ошибок
if ($IS_POST) {
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
writeLog('FATAL: ' . $error['message'] . ' in ' . $error['file'] . ':' . $error['line']);
json_response([
'success' => false,
'error' => [
'type' => 'fatal',
'message' => 'Internal error'
]
], 500);
}
});
}
// Функция для инициализации CRM (вызывается только при необходимости)
function initializeCRM() {
global $adb, $crmPath;
$crmPath = '/var/www/fastuser/data/www/crm.clientright.ru/';
require_once $crmPath . 'config.inc.php';
require_once $crmPath . 'include/utils/utils.php';
require_once $crmPath . 'includes/Loader.php';
vimport('includes.runtime.Globals');
require_once $crmPath . 'include/database/PearDatabase.php';
require_once $crmPath . 'modules/Users/Users.php';
require_once $crmPath . 'include/Webservices/Utils.php';
require_once $crmPath . 'include/Webservices/Create.php';
require_once $crmPath . 'include/Webservices/AddRelated.php';
$adb = PearDatabase::getInstance();
}
// Вспомогательные функции
function getUserWsPrefix() {
global $adb;
$rs = $adb->pquery("SELECT id FROM vtiger_ws_entity WHERE name=?", ['Users']);
return ($rs && $adb->num_rows($rs) > 0) ? $adb->query_result($rs, 0, 'id') : 19;
}
function getProjectWsIdFromDB($projectId) {
global $adb;
$rs = $adb->pquery("SELECT id FROM vtiger_ws_entity WHERE name=?", ['Project']);
return ($rs && $adb->num_rows($rs) > 0) ? $adb->query_result($rs, 0, 'id') . 'x' . (int)$projectId : null;
}
function getDocumentFoldersWsPrefix() {
global $adb;
$rs = $adb->pquery("SELECT id FROM vtiger_ws_entity WHERE name=?", ['DocumentFolders']);
return ($rs && $adb->num_rows($rs) > 0) ? (int)$adb->query_result($rs, 0, 'id') : 22;
}
function getFolderWsIdByName($folderName) {
global $adb;
$rs = $adb->pquery('SELECT folderid FROM vtiger_attachmentsfolder WHERE foldername = ? LIMIT 1', [$folderName]);
if ($rs && $adb->num_rows($rs) > 0) {
$folderId = (int)$adb->query_result($rs, 0, 'folderid');
$prefix = getDocumentFoldersWsPrefix();
return $prefix . 'x' . $folderId;
}
return null;
}
// 🚀 НОВАЯ ФУНКЦИЯ: Создание документов с готовой сессией (формат v3)
function createDocumentsWithSession($data) {
global $adb, $current_user;
// Инициализируем CRM только при необходимости
initializeCRM();
$sessionName = $data['sessionName'];
$projectId = (int)$data['projectid'];
$contactId = (int)$data['contactid'];
$userId = (int)($data['user_id'] ?? 1);
$filesArray = $data['files'] ?? [];
writeLog('🚀 Начинаем создание документов с готовой сессией: ' . $sessionName);
writeLog("📋 Проект: $projectId, Контакт: $contactId, Пользователь: $userId");
writeLog('📋 Файлов к обработке: ' . count($filesArray));
// Устанавливаем current_user для CRMEntity
$current_user = new Users();
$current_user->retrieveCurrentUserInfoFromFile($userId);
// Получаем WS ID проекта один раз
$projectWsId = getProjectWsIdFromDB($projectId);
if (!$projectWsId) {
writeLog("Не найден Project ID $projectId");
return ['error' => "Не найден Project ID $projectId"];
}
writeLog("✅ Project WS ID: $projectWsId");
// WS ID пользователя
$usersPrefix = getUserWsPrefix();
$assignedUserWsId = $usersPrefix . 'x' . $userId;
// Папка (сначала Суд, потом Default)
$folderWsId = getFolderWsIdByName('Суд');
if (!$folderWsId) {
$folderWsId = '22x1';
writeLog("⚠️ Папка 'Суд' не найдена, используем Default");
}
writeLog("📁 Папка: $folderWsId");
// Инициализируем CURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, 'https://crm.clientright.ru/webservice.php');
curl_setopt($ch, CURLOPT_POST, 1);
$results = [];
foreach ($filesArray as $i => $file) {
$fileName = $file['original_file_name'] ?? $file['file_name'] ?? 'Unknown';
writeLog("📄 Обрабатываем файл #{$i}: $fileName");
try {
// Проверяем обязательные поля
if (empty($file['file_url'])) {
throw new Exception("Отсутствует file_url");
}
// Определяем название и описание документа
$documentTitle = $file['upload_description'] ?? $file['original_file_name'] ?? $file['file_name'] ?? 'Документ';
$documentContent = sprintf(
'Загружено через n8n. ID: %s, Контакт: %d, Проект: %d, Загружено: %s',
$file['id'] ?? 'N/A',
$contactId,
$projectId,
$file['uploaded_at'] ?? date('Y-m-d H:i:s')
);
// Создаём документ
$docElement = [
'notes_title' => $documentTitle,
'filename' => $file['file_url'],
'assigned_user_id' => $assignedUserWsId,
'notecontent' => $documentContent,
'filetype' => 'application/pdf',
'filesize' => '0',
'filelocationtype' => 'E', // External URL
'fileversion' => '1.0',
'filestatus' => '1', // Active
'folderid' => $folderWsId,
];
writeLog("📤 Создаём документ '$documentTitle'");
// Отправляем запрос на создание документа
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'operation' => 'create',
'sessionName' => $sessionName,
'elementType' => 'Documents',
'element' => json_encode($docElement, JSON_UNESCAPED_UNICODE),
]);
$resp = curl_exec($ch);
if ($resp === false) {
throw new Exception('CURL error: ' . curl_error($ch));
}
$resp = ltrim($resp, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20");
writeLog("📥 Ответ создания документа: " . substr($resp, 0, 300));
$doc = json_decode($resp, true);
if (!$doc || !$doc['success'] || empty($doc['result']['id'])) {
throw new Exception('Failed to create document: ' . substr($resp, 0, 200));
}
$documentWsId = $doc['result']['id'];
list(, $docNumericId) = explode('x', $documentWsId, 2);
writeLog("✅ Документ создан: $documentWsId");
// Привязываем к проекту
writeLog("🔗 Привязываем документ $documentWsId к проекту $projectWsId");
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'operation' => 'AddRelated',
'sessionName' => $sessionName,
'sourceRecordId' => $projectWsId,
'relatedRecordId' => $documentWsId,
]);
$resp = curl_exec($ch);
$relationOk = false;
if ($resp !== false) {
$resp = ltrim($resp, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20");
writeLog("📥 Ответ AddRelated: " . substr($resp, 0, 200));
$rel = json_decode($resp, true);
$relationOk = isset($rel['result']['message']) && $rel['result']['message'] === 'successfull';
}
// Если webservice не сработал - используем прямую привязку
if (!$relationOk) {
writeLog("⚠️ AddRelated не сработал, используем прямую привязку");
try {
global $crmPath;
require_once $crmPath . 'data/CRMEntity.php';
require_once $crmPath . 'modules/Vtiger/CRMEntity.php';
$focus = CRMEntity::getInstance('Project');
relateEntities($focus, 'Project', $projectId, 'Documents', (int)$docNumericId);
writeLog("✅ Прямая привязка успешна");
} catch (Exception $e) {
writeLog("❌ Прямая привязка не удалась: " . $e->getMessage());
}
} else {
writeLog("✅ AddRelated успешен");
}
// Возвращаем результат с сохранением всех исходных данных
$result = array_merge($file, [
'status' => 'success',
'projectid' => $projectId,
'contactid' => $contactId,
'crm_result' => [
'document_id' => $documentWsId,
'document_numeric_id' => $docNumericId,
'project_id' => $projectId,
'contact_id' => $contactId,
'folder_id' => $folderWsId,
'message' => 'Документ создан и привязан к проекту' . (!$relationOk ? ' (прямая привязка)' : '')
]
]);
$results[] = $result;
writeLog("✅ Файл '$fileName' успешно обработан");
} catch (Exception $e) {
writeLog("❌ Ошибка для файла '$fileName': " . $e->getMessage());
$results[] = array_merge($file, [
'status' => 'error',
'projectid' => $projectId,
'contactid' => $contactId,
'crm_result' => [
'message' => $e->getMessage()
]
]);
}
}
curl_close($ch);
$successCount = count(array_filter($results, function($r) { return $r['status'] === 'success'; }));
writeLog("🏁 Обработка завершена. Успешно: $successCount/" . count($results));
return $results;
}
// Обработка запроса
if ($IS_POST) {
writeLog('=== START POST REQUEST V2 ===');
writeLog('Headers: ' . json_encode(getallheaders(), JSON_UNESCAPED_UNICODE));
// Получаем и проверяем входные данные
$input = file_get_contents('php://input');
writeLog('Raw input: ' . substr($input, 0, 1000) . (strlen($input) > 1000 ? '...(truncated)' : ''));
$input = ltrim($input, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20");
$data = json_decode($input, true);
if (json_last_error() !== JSON_ERROR_NONE) {
writeLog('❌ JSON Error: ' . json_last_error_msg());
json_response([
'success' => false,
'error' => ['message' => 'Invalid JSON: ' . json_last_error_msg()]
], 400);
}
writeLog('Parsed data keys: ' . implode(', ', array_keys($data)));
// 🔑 НОВЫЙ ФОРМАТ: Ожидаем все данные в одном объекте
if (empty($data['sessionName'])) {
writeLog('❌ Error: sessionName is required');
json_response([
'success' => false,
'error' => ['message' => 'sessionName is required in request data']
], 400);
}
if (empty($data['projectid'])) {
writeLog('❌ Error: projectid is required');
json_response([
'success' => false,
'error' => ['message' => 'projectid is required in request data']
], 400);
}
if (empty($data['contactid'])) {
writeLog('❌ Error: contactid is required');
json_response([
'success' => false,
'error' => ['message' => 'contactid is required in request data']
], 400);
}
// Поддерживаем оба формата: files и documents
$filesArray = null;
if (!empty($data['files']) && is_array($data['files'])) {
$filesArray = $data['files'];
} elseif (!empty($data['documents']) && is_array($data['documents'])) {
$filesArray = $data['documents'];
}
if (empty($filesArray)) {
writeLog('❌ Error: files or documents array is required');
json_response([
'success' => false,
'error' => ['message' => 'files or documents array is required in request data']
], 400);
}
writeLog("🔑 Сессия: {$data['sessionName']}");
writeLog("📋 Проект: {$data['projectid']}, Контакт: {$data['contactid']}");
writeLog('📄 Файлов: ' . count($filesArray));
// Нормализуем данные: всегда используем 'files' внутри функции
$normalizedData = $data;
$normalizedData['files'] = $filesArray;
// Создаём документы с готовой сессией
$results = createDocumentsWithSession($normalizedData);
// Успешный ответ
writeLog('✅ Success: processed ' . count($results) . ' files');
json_response([
'success' => true,
'total_processed' => count($filesArray),
'results' => $results,
'session_used' => $sessionName
]);
} else {
// GET запрос - документация API
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => true,
'message' => 'Upload Documents API v2 - Ready',
'endpoint' => 'POST to this URL with complete data object',
'format' => [
'sessionName' => 'string (required - from n8n CRM login)',
'projectid' => 'string (required - project ID)',
'contactid' => 'string (required - contact ID)',
'user_id' => 'string (optional - default "1")',
'files' => [ // или 'documents' - поддерживаются оба формата
[
'id' => 'string (file UUID)',
'file_id' => 'string (S3 path)',
'file_url' => 'string (required - full S3 URL)',
'file_name' => 'string (S3 filename)',
'field_name' => 'string (form field)',
'uploaded_at' => 'string (ISO datetime)',
'original_file_name' => 'string (user filename)',
'upload_description' => 'string (document description)',
'filename_for_upload' => 'string (display filename)'
]
]
],
'example' => [
'sessionName' => '27c160f968cc0cea6dd38',
'projectid' => '392790',
'contactid' => '320096',
'user_id' => '1',
'documents' => [ // пример с 'documents' (как от n8n)
[
'id' => 'd0d3d844-2db0-4c68-82e4-a803881b7527',
'file_id' => 'clpr_claims/5eac8512-1bc5-49ee-952c-3ec390db6964/d0d3d844-2db0-4c68-82e4-a803881b7527__pretenzionnaya-rabota.pdf',
'file_url' => 'https://s3.twcstorage.ru/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/clpr_claims/5eac8512-1bc5-49ee-952c-3ec390db6964/d0d3d844-2db0-4c68-82e4-a803881b7527__pretenzionnaya-rabota.pdf',
'file_name' => 'd0d3d844-2db0-4c68-82e4-a803881b7527__pretenzionnaya-rabota.pdf',
'field_name' => 'uploads[1][0]',
'uploaded_at' => '2025-09-15T22:46:51.913+03:00',
'original_file_name' => '1757965604702.pdf',
'upload_description' => 'Претензионная работа',
'filename_for_upload' => '1757965604702.pdf'
]
]
]
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
?>