Files
crm.clientright.ru/crm_extensions/simple_project_updater_v2.php
Fedor 0f7e8ba247 Добавлена система уведомлений о приходе судебных писем
- Реализована функция createMailNotification() в simple_project_updater_v2.php
- Автоматическое создание уведомлений VDNotifierPro при поступлении документов
- Уведомления отправляются ответственному по проекту
- Исправлен формат ссылок (без index.php? для корректной работы в VDNotifierPro)
- Защита от дубликатов - обновление существующих непрочитанных уведомлений
- Добавлена документация MAIL_NOTIFICATION_SYSTEM.md
- Протестировано и работает корректно
2025-10-16 16:02:08 +03:00

528 lines
25 KiB
PHP
Raw Permalink 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
// simple_project_updater_v2.php
// Правильная версия: использует CRM API вместо прямых SQL запросов
// Устанавливаем рабочую директорию
chdir(__DIR__ . '/..');
require_once 'config.inc.php';
require_once 'include/utils/utils.php';
require_once 'includes/Loader.php';
vimport('includes.runtime.Globals');
require_once 'include/database/PearDatabase.php';
require_once 'modules/Users/Users.php';
require_once 'include/Webservices/Utils.php';
require_once 'include/Webservices/Create.php';
require_once 'include/Webservices/Login.php';
require_once 'include/Webservices/AuthToken.php';
require_once 'include/Webservices/AddRelated.php';
require_once 'data/CRMEntity.php';
require_once 'modules/Vtiger/CRMEntity.php';
require_once 'crm_extensions/file_storage/S3Client.php';
$adb = PearDatabase::getInstance();
// Логирование
function log_message($level, $message) {
$log_file = __DIR__ . '/logs/project_update.log';
$timestamp = date('Y-m-d H:i:s');
$log_entry = "{$timestamp} - {$level}: {$message}\n";
file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
}
// Функция для создания документа через внутренние функции CRM
function createDocumentViaAPI($documentData) {
global $adb, $current_user;
// Создаем пользователя для CRM
if (!isset($current_user) || !$current_user) {
$current_user = new Users();
$current_user->retrieveCurrentUserInfoFromFile(8); // Фёдор Коробков
}
// Создаем документ через прямые SQL запросы (как в simple_project_updater.php)
log_message('DEBUG', "Создаем документ через прямые SQL запросы");
global $mysqli;
// Получаем следующий ID
log_message('DEBUG', "Получаем следующий ID для документа");
$result = $mysqli->query("SELECT MAX(crmid) as max_id FROM vtiger_crmentity");
$row = $result->fetch_assoc();
$next_id = ($row['max_id'] ?? 0) + 1;
log_message('DEBUG', "Следующий ID: $next_id");
$created_time = date('Y-m-d H:i:s');
// Создаем запись в vtiger_crmentity
log_message('DEBUG', "Создаем запись в vtiger_crmentity");
$sql = "INSERT INTO vtiger_crmentity (crmid, smcreatorid, smownerid, modifiedby, setype, description, createdtime, modifiedtime, presence, deleted, label, source) VALUES ($next_id, 8, 8, 8, 'Documents', '" . $mysqli->real_escape_string($documentData['notecontent']) . "', '$created_time', '$created_time', 1, 0, '" . $mysqli->real_escape_string($documentData['notes_title']) . "', 'WEBSERVICE')";
log_message('DEBUG', "SQL: $sql");
$result = $mysqli->query($sql);
if (!$result) {
throw new Exception('Ошибка создания записи в vtiger_crmentity: ' . $mysqli->error);
}
log_message('DEBUG', "Запись в vtiger_crmentity создана");
// Создаем запись в vtiger_notes
log_message('DEBUG', "Создаем запись в vtiger_notes");
$note_no = ОК_' . $next_id;
// Формируем S3 URL для filename
$s3_bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
$s3_key = 'crm2/CRM_Active_Files/Documents/' . $next_id . '/' . $documentData['filename'];
$s3_url = "https://s3.twcstorage.ru/$s3_bucket/$s3_key";
$sql = "INSERT INTO vtiger_notes (notesid, note_no, title, filename, notecontent, folderid, filetype, filelocationtype, filedownloadcount, filestatus, filesize, fileversion, tags, its4you_company, s3_bucket, s3_key, s3_etag, nc_path) VALUES ($next_id, '$note_no', '" . $mysqli->real_escape_string($documentData['notes_title']) . "', '" . $mysqli->real_escape_string(strip_tags(trim($s3_url))) . "', '" . $mysqli->real_escape_string($documentData['notecontent']) . "', 1, '" . $mysqli->real_escape_string($documentData['filetype']) . "', '" . $mysqli->real_escape_string($documentData['filelocationtype']) . "', 0, '" . $mysqli->real_escape_string($documentData['filestatus']) . "', " . intval($documentData['filesize']) . ", '" . $mysqli->real_escape_string($documentData['fileversion']) . "', '', '', '', '', '', '')";
log_message('DEBUG', "SQL vtiger_notes: $sql");
$result = $mysqli->query($sql);
if (!$result) {
throw new Exception('Ошибка создания записи в vtiger_notes: ' . $mysqli->error);
}
log_message('DEBUG', "Запись в vtiger_notes создана");
// Обновляем последовательность
$mysqli->query("UPDATE vtiger_crmentity_seq SET id = $next_id");
log_message('DEBUG', "Документ создан с ID: $next_id");
return [
'id' => '15x' . $next_id,
'notes_title' => $documentData['notes_title'],
'createdtime' => $created_time,
'modifiedtime' => $created_time
];
}
// Функция для создания уведомления о приходе письма
function createMailNotification($projectId, $documentId, $documentTitle, $plaintiffFio) {
global $mysqli;
log_message('INFO', "Создаем уведомление для проекта $projectId о документе $documentId");
// Получаем ответственного по проекту
$query = "SELECT e.smownerid FROM vtiger_crmentity e WHERE e.crmid = ? AND e.deleted = 0";
$stmt = $mysqli->prepare($query);
$stmt->bind_param('i', $projectId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
log_message('WARNING', "Проект $projectId не найден или удален");
return false;
}
$row = $result->fetch_assoc();
$userId = $row['smownerid'];
log_message('DEBUG', "Ответственный по проекту: пользователь $userId");
// Формируем текст уведомления
$notificationTitle = "Письмо от $plaintiffFio - $documentTitle";
// Формируем ссылку на документ (VDNotifierPro убирает index.php? из ссылки)
$documentLink = "module=Documents&view=Detail&record=$documentId";
// Проверяем, нет ли уже непрочитанного уведомления для этого документа
$checkQuery = "SELECT id FROM vtiger_vdnotifierpro WHERE userid = ? AND crmid = ? AND status = 5";
$checkStmt = $mysqli->prepare($checkQuery);
$checkStmt->bind_param('ii', $userId, $documentId);
$checkStmt->execute();
$checkResult = $checkStmt->get_result();
if ($checkResult->num_rows > 0) {
// Обновляем время существующего уведомления
$existingId = $checkResult->fetch_assoc()['id'];
$updateQuery = "UPDATE vtiger_vdnotifierpro SET modifiedtime = NOW() WHERE id = ?";
$updateStmt = $mysqli->prepare($updateQuery);
$updateStmt->bind_param('i', $existingId);
$updateStmt->execute();
log_message('INFO', "Обновлено существующее уведомление ID: $existingId");
return $existingId;
} else {
// Создаем новое уведомление
$insertQuery = "INSERT INTO vtiger_vdnotifierpro (userid, modulename, crmid, modiuserid, link, title, action, modifiedtime, status) VALUES (?, 'Documents', ?, 0, ?, ?, '', NOW(), 5)";
$insertStmt = $mysqli->prepare($insertQuery);
$insertStmt->bind_param('iiss', $userId, $documentId, $documentLink, $notificationTitle);
$insertStmt->execute();
$notificationId = $mysqli->insert_id;
log_message('SUCCESS', "Создано новое уведомление ID: $notificationId для пользователя $userId");
return $notificationId;
}
}
// Функция для привязки документа к проекту через прямые SQL запросы
function linkDocumentToProject($projectId, $documentId) {
global $mysqli;
log_message('DEBUG', "Привязываем документ $documentId к проекту $projectId");
// Создаем связь в vtiger_senotesrel
$stmt = $mysqli->prepare("INSERT INTO vtiger_senotesrel (crmid, notesid) VALUES (?, ?)");
$stmt->bind_param('ii', $projectId, $documentId);
$stmt->execute();
$stmt->close();
log_message('DEBUG', "Связь создана между проектом $projectId и документом $documentId");
return true;
}
try {
// Читаем JSON из stdin
$input = file_get_contents('php://stdin');
$data = json_decode($input, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('Ошибка парсинга JSON: ' . json_last_error_msg());
}
// Проверяем структуру данных - может быть массив или объект
if (is_array($data) && isset($data[0])) {
// Если это массив, берем первый элемент
$item = $data[0];
} elseif (is_array($data) && isset($data['content'])) {
// Если это объект, используем его напрямую
$item = $data;
} else {
throw new Exception('Неверная структура данных: ожидается объект с полем content или массив объектов');
}
// Проверяем наличие обязательных полей
if (!$item || !isset($item['content']) || !is_array($item['content'])) {
throw new Exception('Неверная структура данных: отсутствует content');
}
$content = $item['content'];
$file_url = trim($item['file'] ?? '');
$file_name = $item['file_name'] ?? '';
if (empty($file_url) || empty($file_name)) {
throw new Exception('Отсутствует URL файла или имя файла');
}
// Инициализируем пользователя CRM
$current_user = new Users();
$current_user->retrieveCurrentUserInfoFromFile(8); // Фёдор Коробков
log_message('INFO', "Инициализирован пользователь CRM: " . $current_user->user_name);
// Подключаемся к базе данных для поиска проекта
$mysqli = new mysqli('localhost', 'ci20465_72new', 'EcY979Rn', 'ci20465_72new');
if ($mysqli->connect_error) {
throw new Exception('Не удалось подключиться к базе данных: ' . $mysqli->connect_error);
}
$mysqli->set_charset('utf8');
// Проверяем кодировку соединения
$charset_result = $mysqli->query("SELECT @@character_set_connection, @@collation_connection");
if ($charset_result) {
$charset_row = $charset_result->fetch_row();
log_message('DEBUG', "Кодировка БД: " . $charset_row[0] . ", collation: " . $charset_row[1]);
}
$plaintiff_fio = $content['plaintiff_fio'] ?? '';
$case_number = $content['case_number'] ?? '';
$uid = $content['uid'] ?? '';
log_message('DEBUG', "Поиск проекта по данным: plaintiff_fio='$plaintiff_fio', case_number='$case_number', uid='$uid'");
$project_id = null;
// Улучшенный поиск по комбинации критериев с нестрогим соответствием
$search_criteria = [];
$search_params = [];
$param_types = '';
// ФИО с учетом мужского/женского рода
if ($plaintiff_fio) {
$surname = explode(' ', $plaintiff_fio)[0];
$surname_male = $surname; // Соколов
$surname_female = $surname . 'а'; // Соколова
$search_criteria[] = "(p.projectname LIKE ? OR e.description LIKE ? OR p.projectname LIKE ? OR e.description LIKE ?)";
$search_params[] = "%" . $mysqli->real_escape_string($surname_male) . "%";
$search_params[] = "%" . $mysqli->real_escape_string($surname_male) . "%";
$search_params[] = "%" . $mysqli->real_escape_string($surname_female) . "%";
$search_params[] = "%" . $mysqli->real_escape_string($surname_female) . "%";
$param_types .= 'ssss';
log_message('DEBUG', "Поиск по ФИО: '$surname_male' и '$surname_female'");
}
// Суд
if (!empty($content['court'])) {
$court = $content['court'];
$search_criteria[] = "(e.description LIKE ? OR cf.cf_1499 LIKE ? OR cf.cf_2278 LIKE ?)";
$search_params[] = "%" . $mysqli->real_escape_string($court) . "%";
$search_params[] = "%" . $mysqli->real_escape_string($court) . "%";
$search_params[] = "%" . $mysqli->real_escape_string($court) . "%";
$param_types .= 'sss';
log_message('DEBUG', "Поиск по суду: '$court'");
}
// Номер дела
if ($case_number) {
$search_criteria[] = "(p.project_no = ? OR e.description LIKE ?)";
$search_params[] = $case_number;
$search_params[] = "%" . $mysqli->real_escape_string($case_number) . "%";
$param_types .= 'ss';
log_message('DEBUG', "Поиск по номеру дела: '$case_number'");
}
// УИД
if ($uid) {
$search_criteria[] = "(e.description LIKE ?)";
$search_params[] = "%" . $mysqli->real_escape_string($uid) . "%";
$param_types .= 's';
log_message('DEBUG', "Поиск по УИД: '$uid'");
}
// Выполняем поиск если есть критерии
if (!empty($search_criteria)) {
// Сначала пробуем строгий поиск (все критерии)
$where_clause = implode(' AND ', $search_criteria);
$sql = "
SELECT p.projectid, p.projectname, p.project_no, e.description
FROM vtiger_project p
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
LEFT JOIN vtiger_projectcf cf ON cf.projectid = p.projectid
WHERE $where_clause
ORDER BY
CASE
WHEN p.projectname LIKE '%" . $mysqli->real_escape_string($surname ?? '') . "%' THEN 1
WHEN e.description LIKE '%" . $mysqli->real_escape_string($surname ?? '') . "%' THEN 2
ELSE 3
END,
p.projectid ASC
LIMIT 1
";
log_message('DEBUG', "Строгий поиск SQL: $sql");
log_message('DEBUG', "Параметры: " . json_encode($search_params));
$stmt = $mysqli->prepare($sql);
if ($stmt) {
if (!empty($search_params)) {
$stmt->bind_param($param_types, ...$search_params);
}
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
$project_id = $row['projectid'];
log_message('SUCCESS', "Найден проект (строгий поиск): ID=$project_id, название='{$row['projectname']}'");
}
$stmt->close();
} else {
log_message('ERROR', "Ошибка подготовки SQL: " . $mysqli->error);
}
// Если строгий поиск не дал результатов, пробуем поиск только по ФИО
if (!$project_id && !empty($surname)) {
$sql = "
SELECT p.projectid, p.projectname, p.project_no, e.description
FROM vtiger_project p
JOIN vtiger_crmentity e ON e.crmid = p.projectid AND e.deleted = 0
LEFT JOIN vtiger_projectcf cf ON cf.projectid = p.projectid
WHERE (p.projectname LIKE ? OR e.description LIKE ? OR p.projectname LIKE ? OR e.description LIKE ?)
ORDER BY
CASE
WHEN p.projectname LIKE '%" . $mysqli->real_escape_string($surname) . "%' THEN 1
WHEN e.description LIKE '%" . $mysqli->real_escape_string($surname) . "%' THEN 2
ELSE 3
END,
p.projectid ASC
LIMIT 1
";
log_message('DEBUG', "Поиск только по ФИО SQL: $sql");
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$surname_male = "%" . $mysqli->real_escape_string($surname) . "%";
$surname_female = "%" . $mysqli->real_escape_string($surname . 'а') . "%";
log_message('DEBUG', "Параметры поиска по ФИО: '$surname_male', '$surname_female'");
log_message('DEBUG', "Длина параметров: " . strlen($surname_male) . ", " . strlen($surname_female));
log_message('DEBUG', "Кодировка параметров: " . mb_detect_encoding($surname_male) . ", " . mb_detect_encoding($surname_female));
$stmt->bind_param('ssss', $surname_male, $surname_male, $surname_female, $surname_female);
$stmt->execute();
$result = $stmt->get_result();
log_message('DEBUG', "Количество найденных записей: " . $result->num_rows);
if ($row = $result->fetch_assoc()) {
$project_id = $row['projectid'];
log_message('SUCCESS', "Найден проект (поиск по ФИО): ID=$project_id, название='{$row['projectname']}'");
} else {
log_message('DEBUG', "Проект не найден по ФИО");
}
$stmt->close();
} else {
log_message('ERROR', "Ошибка подготовки SQL для поиска по ФИО: " . $mysqli->error);
}
}
}
if (!$project_id) {
throw new Exception('Проект не найден по указанным данным');
}
log_message('INFO', "Найден проект ID: $project_id");
// Формируем название документа
$document_title = "СУДЕБНЫЙ ДОКУМЕНТ";
if (!empty($content['document_title'])) {
$document_title = $content['document_title'];
}
if (!empty($content['case_number'])) {
$document_title .= " по делу " . $content['case_number'];
}
if (!empty($content['plaintiff_fio'])) {
$document_title .= " " . $content['plaintiff_fio'];
}
$document_title .= " " . date('d.m.Y');
// Создаём документ через CRM API
$documentData = [
'notes_title' => $document_title,
'filename' => $file_name,
'assigned_user_id' => '19x8', // Фёдор Коробков
'notecontent' => "Документ создан автоматически из судебного документа.\n\n" .
"Номер дела: " . ($content['case_number'] ?? 'не указан') . "\n" .
"Суд: " . ($content['court'] ?? 'не указан') . "\n" .
"Истец: " . ($content['plaintiff_fio'] ?? 'не указан') . "\n" .
"УИД: " . ($content['uid'] ?? 'не указан') . "\n" .
"Дата создания: " . date('d.m.Y H:i:s'),
'filetype' => 'application/pdf',
'filesize' => '0', // Будет обновлено после загрузки
'filelocationtype' => 'E', // External URL
'fileversion' => '1.0',
'filestatus' => '1', // Active
'folderid' => '22x1', // Default папка
];
log_message('INFO', "Создаём документ: $document_title");
$documentResult = createDocumentViaAPI($documentData);
$documentWsId = $documentResult['id'];
list(, $documentNumericId) = explode('x', $documentWsId, 2);
log_message('SUCCESS', "Документ создан: $documentWsId (numeric: $documentNumericId)");
// S3 метаданные уже установлены при создании записи
log_message('SUCCESS', "S3 метаданные установлены для документа $documentNumericId");
// Загружаем файл в S3
if (!empty($file_url) && !empty($file_name)) {
log_message('DEBUG', "Начинаем загрузку файла в S3: $file_url");
// Скачиваем файл
$file_content = file_get_contents($file_url);
if ($file_content === false) {
log_message('ERROR', "Не удалось скачать файл: $file_url");
} else {
log_message('DEBUG', "Файл скачан, размер: " . strlen($file_content) . " байт");
// Генерируем ETag для S3
$s3_etag = '"' . md5($file_content) . '"';
// Формируем S3 ключ
$s3_bucket = 'f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c';
$s3_key = 'crm2/CRM_Active_Files/Documents/' . $documentNumericId . '/' . $file_name;
// Загружаем файл в S3 через S3Client
try {
log_message('DEBUG', "Загружаем конфигурацию S3");
// Загружаем конфигурацию S3
$config = require __DIR__ . '/file_storage/config.php';
log_message('DEBUG', "Конфигурация S3 загружена");
$s3Client = new S3Client($config['s3']);
log_message('DEBUG', "S3Client создан");
// Создаем временный файл
$temp_file = tempnam(sys_get_temp_dir(), 'crm_doc_');
file_put_contents($temp_file, $file_content);
// Загружаем файл в S3
log_message('DEBUG', "Загружаем файл в S3 с ключом: $s3_key");
$result = $s3Client->uploadFile($temp_file, $s3_key, [
'ContentType' => 'application/pdf'
]);
// Удаляем временный файл
unlink($temp_file);
log_message('SUCCESS', "Файл успешно загружен в S3");
// Обновляем ETag, размер файла и S3 метаданные в БД
$stmt = $mysqli->prepare("
UPDATE vtiger_notes
SET s3_etag = ?, filesize = ?, s3_bucket = ?, s3_key = ?
WHERE notesid = ?
");
$file_size = strlen($file_content);
$stmt->bind_param('sissi', $s3_etag, $file_size, $s3_bucket, $s3_key, $documentNumericId);
$stmt->execute();
$stmt->close();
log_message('SUCCESS', "S3 ETag и размер файла обновлены");
} catch (Exception $e) {
log_message('ERROR', "Ошибка загрузки в S3: " . $e->getMessage());
}
}
}
// Привязываем документ к проекту
$linkSuccess = linkDocumentToProject($project_id, (int)$documentNumericId);
if ($linkSuccess) {
log_message('SUCCESS', "Документ успешно привязан к проекту");
// Создаем уведомление о приходе письма
$notificationId = createMailNotification($project_id, (int)$documentNumericId, $document_title, $plaintiff_fio);
if ($notificationId) {
log_message('SUCCESS', "Уведомление создано с ID: $notificationId");
} else {
log_message('WARNING', "Не удалось создать уведомление");
}
} else {
log_message('ERROR', "Ошибка привязки документа к проекту");
}
$mysqli->close();
// Формируем результат
$result = [
'success' => true,
'project_id' => $project_id,
'plaintiff_fio' => explode(' ', $plaintiff_fio)[0],
'case_number' => $case_number,
'uid' => $uid,
'document_id' => $documentNumericId,
'document_ws_id' => $documentWsId,
's3_url' => $s3_url,
'notification_id' => $notificationId ?? null,
'message' => 'Документ успешно создан через CRM API и добавлен к проекту' . ($notificationId ? ' с уведомлением' : '')
];
log_message('SUCCESS', "Обработка завершена успешно: " . json_encode($result, JSON_UNESCAPED_UNICODE));
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
return 0;
} catch (Exception $e) {
$error_message = $e->getMessage();
log_message('ERROR', "Ошибка: $error_message");
echo json_encode([
'success' => false,
'error' => $error_message,
'timestamp' => date('Y-m-d H:i:s')
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
return 1;
}
?>