Files
crm.clientright.ru/telegram_replay.php
Fedor d7982931cd feat: добавлен telegram_replay.php для публикации ответов поддержки в CRM
Новый endpoint для записи ответов поддержки как комментариев в CRM:
- Принимает JSON с полями: answer, contact_id, project_id (опц.), support_user_id (опц.), channel (опц.)
- Использует прямые INSERT запросы в vtiger_crmentity, vtiger_modcomments, vtiger_modcommentscf
- Обязательно создаёт запись в vtiger_modcommentscf (иначе комментарий не отображается)
- Устанавливает deleted=0 (иначе фильтруется при выборке)
- Полная проверка ошибок БД с детальным логированием
- Логи: logs/tg_replay_inbound.log

Исправлены проблемы:
- vtws_create падал без выброса исключения — заменён на прямой SQL
- Убраны несуществующие колонки (from_mailconverter, customer_email, from_mailroom)
- Добавлена обязательная запись в vtiger_modcommentscf
2026-02-03 14:02:12 +03:00

187 lines
9.3 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
/*********************************************************************************
* telegram_replay.php — публикация в CRM ответов поддержки на вопросы пользователя
*
* Вход (POST JSON):
* answer — текст ответа поддержки (обязательно)
* contact_id — ID контакта в CRM, кому отвечаем (обязательно)
* project_id — ID проекта (необязательно; для ответственного и уведомлений)
* support_user_id — ID сотрудника CRM, кто ответил (необязательно; иначе админ/ответственный по проекту)
* channel — канал, например 'Support' или 'Telegram' (необязательно, по умолчанию 'Support')
*
* Логи: logs/tg_replay_inbound.log
********************************************************************************/
error_reporting(E_ALL);
ini_set('display_errors', '1');
include_once 'modules/Users/Users.php';
include_once 'include/utils/CommonUtils.php';
include_once 'include/utils/utils.php';
require_once 'include/Webservices/Utils.php';
require_once 'include/Webservices/Create.php';
require_once 'include/Webservices/Revise.php';
require_once 'include/utils/WhatsApp.php';
require_once 'includes/Loader.php';
vimport('includes.runtime.Globals');
vimport('includes.runtime.BaseModel');
vimport('includes.runtime.LanguageHandler');
$logFile = 'logs/tg_replay_inbound.log';
$str = file_get_contents('php://input');
$logstring = date('Y-m-d H:i:s') . ' ' . $str . PHP_EOL;
file_put_contents($logFile, $logstring, FILE_APPEND);
$data = json_decode($str, true);
if (!is_array($data)) {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Ошибка: невалидный JSON' . PHP_EOL, FILE_APPEND);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => 'Invalid JSON']);
exit;
}
$answer = isset($data['answer']) ? trim($data['answer']) : (isset($data['message']) ? trim($data['message']) : '');
$contact_id = isset($data['contact_id']) ? (int) $data['contact_id'] : 0;
$project_id = isset($data['project_id']) ? (int) $data['project_id'] : 0;
$support_user_id = isset($data['support_user_id']) ? $data['support_user_id'] : null; // может быть "19x123"
$channel = isset($data['channel']) ? trim($data['channel']) : 'Support';
if (empty($answer)) {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Ошибка: не передан answer/message' . PHP_EOL, FILE_APPEND);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => 'Missing answer or message']);
exit;
}
if ($contact_id <= 0) {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Ошибка: не передан или неверный contact_id' . PHP_EOL, FILE_APPEND);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => 'Missing or invalid contact_id']);
exit;
}
global $adb;
if (empty($adb)) {
$adb = PearDatabase::getInstance();
}
file_put_contents($logFile, date('Y-m-d H:i:s') . ' $adb ok' . PHP_EOL, FILE_APPEND);
// Проверяем, что контакт существует
$check = $adb->pquery(
'SELECT c.contactid, e.smownerid FROM vtiger_contactdetails c
INNER JOIN vtiger_crmentity e ON e.crmid = c.contactid
WHERE e.deleted = 0 AND c.contactid = ?',
array($contact_id)
);
if ($adb->num_rows($check) === 0) {
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Ошибка: контакт с ID ' . $contact_id . ' не найден' . PHP_EOL, FILE_APPEND);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => 'Contact not found']);
exit;
}
file_put_contents($logFile, date('Y-m-d H:i:s') . ' Контакт ' . $contact_id . ' найден' . PHP_EOL, FILE_APPEND);
$owner_id = $adb->query_result($check, 0, 'smownerid');
$crmid = '12x' . $contact_id;
$setype = 'Contacts';
// Кто создаёт комментарий: переданный support_user_id, иначе ответственный по проекту, иначе по контакту, иначе админ
$user = null;
if (!empty($support_user_id)) {
$user = (strpos($support_user_id, 'x') !== false) ? $support_user_id : ('19x' . $support_user_id);
} elseif ($project_id > 0) {
$proj = $adb->pquery(
'SELECT e.smownerid FROM vtiger_project p INNER JOIN vtiger_crmentity e ON e.crmid = p.projectid
WHERE e.deleted = 0 AND p.projectid = ? AND p.linktoaccountscontacts = ?',
array($project_id, $contact_id)
);
if ($adb->num_rows($proj) > 0) {
$uid = $adb->query_result($proj, 0, 'smownerid');
$user = '19x' . $uid;
}
}
if (empty($user)) {
$user = '19x' . $owner_id;
}
if (empty($user) || $user === '19x') {
$user = Users::getActiveAdminUser();
}
$owner_id_num = (strpos($user, 'x') !== false) ? (int) substr($user, strpos($user, 'x') + 1) : (int) $user;
file_put_contents($logFile, date('Y-m-d H:i:s') . ' user=' . (string)$user . ' (owner_id_num=' . $owner_id_num . ')' . PHP_EOL, FILE_APPEND);
// Запись комментария напрямую в БД (без vtws_create — тот падает внутри без выброса исключения)
$commentCrmId = $adb->getUniqueID('vtiger_crmentity');
$date_var = date('Y-m-d H:i:s');
// deleted=0 обязательно — иначе запись не попадёт в выборку (WHERE vtiger_crmentity.deleted = 0)
$sql_crmentity = "INSERT INTO vtiger_crmentity (crmid, smcreatorid, smownerid, smgroupid, setype, description, modifiedby, createdtime, modifiedtime, source, deleted) VALUES (?, ?, ?, 0, 'ModComments', '', ?, ?, ?, 'CRM', 0)";
$result1 = $adb->pquery($sql_crmentity, array($commentCrmId, $owner_id_num, $owner_id_num, $owner_id_num, $date_var, $date_var));
if (!$result1) {
$error = $adb->database->ErrorMsg();
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ❌ Ошибка INSERT vtiger_crmentity: ' . $error . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'error' => 'DB error: crmentity - ' . $error]);
exit;
}
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ✅ vtiger_crmentity OK' . PHP_EOL, FILE_APPEND);
$sql_modcomments = "INSERT INTO vtiger_modcomments (modcommentsid, commentcontent, related_to, parent_comments, customer, userid, reasontoedit, is_private, filename, related_email_id, channel, messageid) VALUES (?, ?, ?, '', 0, 0, NULL, 0, NULL, NULL, ?, NULL)";
$result2 = $adb->pquery($sql_modcomments, array($commentCrmId, $answer, $contact_id, $channel));
if (!$result2) {
$error = $adb->database->ErrorMsg();
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ❌ Ошибка INSERT vtiger_modcomments: ' . $error . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'error' => 'DB error: modcomments - ' . $error]);
exit;
}
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ✅ vtiger_modcomments OK' . PHP_EOL, FILE_APPEND);
// Без строки в vtiger_modcommentscf комментарий не попадёт в список (INNER JOIN в getListQuery)
$result3 = $adb->pquery('INSERT INTO vtiger_modcommentscf (modcommentsid) VALUES (?)', array($commentCrmId));
if (!$result3) {
$error = $adb->database->ErrorMsg();
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ❌ Ошибка INSERT vtiger_modcommentscf: ' . $error . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'error' => 'DB error: modcommentscf - ' . $error]);
exit;
}
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ✅ vtiger_modcommentscf OK' . PHP_EOL, FILE_APPEND);
file_put_contents($logFile, date('Y-m-d H:i:s') . ' ✅ Создан комментарий ID ' . $commentCrmId . ' для контакта ' . $contact_id . PHP_EOL, FILE_APPEND);
$comment_id_ws = '20x' . $commentCrmId; // ModComments в vtiger обычно 20x
// Уведомление ответственного (всплывашка)
$notify_user_id = $owner_id;
if ($project_id > 0) {
$proj = $adb->pquery(
'SELECT e.smownerid FROM vtiger_project p INNER JOIN vtiger_crmentity e ON e.crmid = p.projectid
WHERE e.deleted = 0 AND p.projectid = ? AND p.linktoaccountscontacts = ? AND p.projectstatus <> ?',
array($project_id, $contact_id, 'completed')
);
if ($adb->num_rows($proj) === 1) {
$notify_user_id = $adb->query_result($proj, 0, 'smownerid');
}
}
$link = 'module=Contacts&view=Detail&record=' . $contact_id . '&app=MARKETING';
$title = 'Ответ поддержки добавлен';
$exist = $adb->pquery(
'SELECT id FROM vtiger_vdnotifierpro WHERE userid = ? AND crmid = ? AND title = ? AND status = 5',
array($notify_user_id, $contact_id, $title)
);
if ($adb->num_rows($exist) > 0) {
$id = $adb->query_result($exist, 0, 'id');
$adb->pquery('UPDATE vtiger_vdnotifierpro SET modifiedtime = ? WHERE id = ?', array($date_var, $id));
} else {
$adb->pquery(
'INSERT INTO vtiger_vdnotifierpro (userid, modulename, crmid, modiuserid, link, title, action, modifiedtime, status) VALUES (?, ?, ?, 0, ?, ?, "", ?, 5)',
array($notify_user_id, $setype, $contact_id, $link, $title, $date_var)
);
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => true,
'comment_id' => $comment_id_ws,
'contact_id' => $contact_id
]);