From 546ce83763f30e612f70c81d85dc3e7c56c91293 Mon Sep 17 00:00:00 2001 From: Fedor Date: Sun, 2 Nov 2025 19:25:04 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=9F=D1=80=D1=8F=D0=BC=D0=BE=D0=B9=20?= =?UTF-8?q?PHP=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8=D0=BD=D1=82=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Создан api_attach_documents.php: ✅ Прямой эндпоинт без backend proxy ✅ URL: https://crm.clientright.ru/api_attach_documents.php ✅ Принимает массив документов из n8n ✅ Умная обработка S3 путей (добавляет хост если нужно) ✅ Поддержка file/file_url, filename/file_name ✅ Привязка к HelpDesk или Project (зависит от ticket_id) ✅ Проксирует к upload_documents_to_crm.php ✅ Полное логирование в logs/api_attach_documents.log Готово к использованию в n8n! --- API_ATTACH_DOCS_README.md | 89 ++++++++++++++ api_attach_documents.php | 250 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 API_ATTACH_DOCS_README.md create mode 100644 api_attach_documents.php diff --git a/API_ATTACH_DOCS_README.md b/API_ATTACH_DOCS_README.md new file mode 100644 index 00000000..33b0d122 --- /dev/null +++ b/API_ATTACH_DOCS_README.md @@ -0,0 +1,89 @@ +# 📎 API для привязки документов + +## ✅ Прямой эндпоинт (готов к использованию!) + +``` +POST https://crm.clientright.ru/api_attach_documents.php +``` + +--- + +## 🚀 Формат запроса (из n8n) + +```json +[ + { + "contact_id": "320096", + "project_id": "396868", + "ticket_id": "396936", + "filename": "boarding_pass.pdf", + "file_type": "flight_delay_boarding_or_ticket", + "file": "/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/crm2/CRM_Active_Files/Documents/HelpDesk/ЗАЯВКА_827_396936/flight_delay_boarding_or_ticket.pdf" + } +] +``` + +**Важно:** +- ✅ Массив `[...]` (даже для одного документа) +- ✅ `file` без хоста → автоматически добавится `https://s3.twcstorage.ru` +- ✅ `ticket_id` опционально (если есть → HelpDesk, иначе → Project) + +--- + +## 📊 Формат ответа + +```json +{ + "success": true, + "total_processed": 1, + "successful": 1, + "failed": 0, + "results": [ + { + "document_id": "15x396941", + "document_numeric_id": "396941", + "attached_to": "ticket", + "attached_to_id": "396936", + "file_name": "boarding_pass.pdf", + "file_type": "flight_delay_boarding_or_ticket", + "s3_bucket": "f9825c87-...", + "s3_key": "crm2/CRM_Active_Files/...", + "file_size": 85320, + "message": "Документ создан и привязан..." + } + ], + "errors": null +} +``` + +--- + +## 🧪 Тест + +```bash +curl -X POST "https://crm.clientright.ru/api_attach_documents.php" \ + -H "Content-Type: application/json" \ + -d '[ + { + "contact_id": "320096", + "project_id": "396868", + "ticket_id": "396936", + "filename": "test.pdf", + "file_type": "flight_delay_boarding_or_ticket", + "file": "/bucket/path/to/file.pdf" + } +]' +``` + +--- + +## 🔍 Логи + +```bash +tail -f /var/www/fastuser/data/www/crm.clientright.ru/logs/api_attach_documents.log +``` + +--- + +## 🎯 Готово к использованию в n8n! + diff --git a/api_attach_documents.php b/api_attach_documents.php new file mode 100644 index 00000000..73bef68d --- /dev/null +++ b/api_attach_documents.php @@ -0,0 +1,250 @@ + 'ok']); +} + +// Только POST +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + json_response(['success' => false, 'error' => 'Method not allowed'], 405); +} + +log_message('=== START API REQUEST ==='); + +// Получаем входные данные +$input = file_get_contents('php://input'); +log_message('Raw input: ' . substr($input, 0, 500)); + +$input = ltrim($input, "\xEF\xBB\xBF\x00\x09\x0A\x0D\x20"); +$data = json_decode($input, true); + +if (json_last_error() !== JSON_ERROR_NONE) { + log_message('❌ JSON Error: ' . json_last_error_msg()); + json_response([ + 'success' => false, + 'error' => 'Invalid JSON: ' . json_last_error_msg() + ], 400); +} + +// Поддерживаем как массив, так и одиночный объект +$documents_array = is_array($data) && isset($data[0]) ? $data : [$data]; + +log_message('Processing ' . count($documents_array) . ' document(s)'); + +// Обрабатываем каждый документ +$processed_documents = []; +$S3_HOST = 'https://s3.twcstorage.ru'; + +foreach ($documents_array as $idx => $doc) { + $contact_id = $doc['contact_id'] ?? null; + $project_id = $doc['project_id'] ?? null; + $ticket_id = $doc['ticket_id'] ?? null; + + // Поддерживаем оба формата: file и file_url + $file_path = $doc['file'] ?? $doc['file_url'] ?? null; + + if (!$file_path) { + log_message("❌ Document #{$idx}: missing 'file' or 'file_url'"); + continue; + } + + // Строим полный S3 URL + if (strpos($file_path, 'http') === 0) { + $file_url = $file_path; + } elseif (strpos($file_path, '/') === 0) { + $file_url = $S3_HOST . $file_path; + } else { + $file_url = $S3_HOST . '/' . $file_path; + } + + // Поддерживаем оба формата: filename и file_name + $file_name = $doc['filename'] ?? $doc['file_name'] ?? null; + + if (!$file_name) { + log_message("❌ Document #{$idx}: missing 'filename' or 'file_name'"); + continue; + } + + $file_type = $doc['file_type'] ?? 'Документ'; + + // Валидация обязательных полей + if (!$contact_id || !$project_id) { + log_message("❌ Document #{$idx}: missing contact_id or project_id"); + continue; + } + + log_message(" [{$idx}] {$file_name} (type: {$file_type})"); + log_message(" Contact: {$contact_id}, Project: {$project_id}, Ticket: " . ($ticket_id ?: 'N/A')); + log_message(" File URL: {$file_url}"); + + $processed_documents[] = [ + 'url' => $file_url, + 'file_name' => $file_name, + 'description' => $file_type, + 'projectid' => (int)$project_id, + 'ticket_id' => $ticket_id ? (int)$ticket_id : null, + 'contactid' => (int)$contact_id, + 'pages' => 1 + ]; +} + +if (empty($processed_documents)) { + log_message('❌ No valid documents to process'); + json_response([ + 'success' => false, + 'error' => 'No valid documents to process' + ], 400); +} + +log_message('📤 Sending ' . count($processed_documents) . ' documents to upload_documents_to_crm.php'); + +// Формируем запрос к upload_documents_to_crm.php +$upload_url = 'https://crm.clientright.ru/upload_documents_to_crm.php'; + +// Берем общие параметры из первого документа +$first_doc = $processed_documents[0]; + +$payload = json_encode([ + 'documents' => $processed_documents, + 'projectid' => $first_doc['projectid'], + 'ticket_id' => $first_doc['ticket_id'], + 'user_id' => 1 +], JSON_UNESCAPED_UNICODE); + +log_message('Payload: ' . substr($payload, 0, 500)); + +// Отправляем запрос +$ch = curl_init(); +curl_setopt($ch, CURLOPT_URL, $upload_url); +curl_setopt($ch, CURLOPT_POST, true); +curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_TIMEOUT, 60); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($payload) +]); + +$response = curl_exec($ch); +$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +curl_close($ch); + +if ($response === false) { + log_message('❌ CURL error: ' . curl_error($ch)); + json_response([ + 'success' => false, + 'error' => 'Internal error: ' . curl_error($ch) + ], 500); +} + +log_message("Response HTTP code: {$http_code}"); +log_message("Response: " . substr($response, 0, 500)); + +// Парсим ответ +$result = json_decode($response, true); + +if (json_last_error() !== JSON_ERROR_NONE) { + log_message('❌ Failed to parse response JSON: ' . json_last_error_msg()); + json_response([ + 'success' => false, + 'error' => 'Invalid response from upload service' + ], 500); +} + +// Проверяем успешность +if ($result && $result['success'] && isset($result['results'])) { + $results_array = $result['results']; + + // Формируем ответ + $processed_results = []; + $errors = []; + + foreach ($results_array as $idx => $res) { + if ($res['status'] === 'success') { + $crm_result = $res['crm_result'] ?? []; + + $processed_results[] = [ + 'document_id' => $crm_result['document_id'] ?? null, + 'document_numeric_id' => $crm_result['document_numeric_id'] ?? null, + 'attached_to' => isset($res['ticket_id']) && $res['ticket_id'] ? 'ticket' : 'project', + 'attached_to_id' => $res['ticket_id'] ?? $res['projectid'] ?? null, + 'file_name' => $res['file_name'] ?? null, + 'file_type' => $res['description'] ?? null, + 's3_bucket' => $crm_result['s3_bucket'] ?? null, + 's3_key' => $crm_result['s3_key'] ?? null, + 'file_size' => $crm_result['file_size'] ?? null, + 'message' => $crm_result['message'] ?? null + ]; + + log_message(" ✅ [{$idx}] {$res['file_name']} → {$crm_result['document_id']}"); + } else { + $error_msg = $res['crm_result']['message'] ?? 'Unknown error'; + $errors[] = [ + 'file_name' => $res['file_name'] ?? 'Unknown', + 'error' => $error_msg + ]; + log_message(" ❌ [{$idx}] {$res['file_name']}: {$error_msg}"); + } + } + + log_message('✅ Success: ' . count($processed_results) . ' documents attached'); + + json_response([ + 'success' => true, + 'total_processed' => count($results_array), + 'successful' => count($processed_results), + 'failed' => count($errors), + 'results' => $processed_results, + 'errors' => !empty($errors) ? $errors : null + ]); +} else { + log_message('❌ Upload failed: ' . ($result['error']['message'] ?? 'Unknown error')); + json_response([ + 'success' => false, + 'error' => $result['error']['message'] ?? 'Upload failed' + ], 500); +} +