2025-11-02 19:25:04 +03:00
|
|
|
|
<?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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-12 19:46:06 +03:00
|
|
|
|
|