Files
crm.clientright.ru/api_attach_documents.php
Fedor 546ce83763 feat: Прямой PHP эндпоинт для привязки документов
Создан 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!
2025-11-02 19:25:04 +03:00

251 lines
8.2 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 для привязки документов к проекту/заявке
*
* Использование из n8n:
* POST https://crm.clientright.ru/api_attach_documents.php
*
* Входные данные (JSON массив):
* [
* {
* "contact_id": "320096",
* "project_id": "396868",
* "ticket_id": "396936",
* "filename": "boarding_pass.pdf",
* "file_type": "flight_delay_boarding_or_ticket",
* "file": "/bucket/path/to/file.pdf"
* }
* ]
*/
error_reporting(E_ALL);
ini_set('display_errors', '0');
// Функция для логирования
function log_message($message) {
$timestamp = date('Y-m-d H:i:s');
$line = "[$timestamp] $message\n";
@file_put_contents(__DIR__ . '/logs/api_attach_documents.log', $line, FILE_APPEND | LOCK_EX);
error_log('[api_attach_documents] ' . $message);
}
// Функция для JSON ответа
function json_response($data, $code = 200) {
if (!headers_sent()) {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
}
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
// CORS preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
json_response(['status' => '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);
}