Joywork/engine/classes/BiAnalyticsLogger.php
2026-08-05 21:06:56 +03:00

162 lines
6.0 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
/**
* Класс для логирования событий в BI Analytics (outbox)
*
* Используется для отправки событий из JoyWork в ClickHouse
* через таблицу bi_analytics_events_outbox
*/
class BiAnalyticsLogger {
/**
* @var MysqlPdo Подключение к базе данных
*/
private $db;
/**
* @var int ID компании (agency_id)
*/
private $companyId;
/**
* Конструктор
*
* @param MysqlPdo $db Подключение к БД
* @param int $companyId ID компании
*/
public function __construct($db, $companyId) {
$this->db = $db;
$this->companyId = (int)$companyId;
}
/**
* Создать событие в outbox
*
* @param string $entityType Тип сущности: 'client' или 'requisition'
* @param int $entityId ID сущности
* @param string $eventType Тип события
* @param int $actorUserId ID пользователя
* @param array $before Данные до изменения
* @param array $after Данные после изменения
*
* @return string UUID созданного события
* @throws Exception
*/
public function logEvent($entityType, $entityId, $eventType, $actorUserId, $before = array(), $after = array()) {
// Валидация
if (!in_array($entityType, array('client', 'requisition'))) {
throw new Exception("Invalid entity type: " . $entityType);
}
$entityId = (int)$entityId;
$actorUserId = (int)$actorUserId;
// Определяем изменённые поля
$changedFields = array_keys(array_diff_assoc($after, $before));
// Формируем payload
$payload = array(
'schema_version' => 1,
'entity' => array(
'type' => $entityType,
'id' => $entityId,
'company_id' => $this->companyId
),
'occurred_at' => date('c'),
'actor_user_id' => $actorUserId,
'event' => array(
'type' => $eventType,
'changed_fields' => $changedFields
),
'before' => $before,
'after' => $after
);
// Генерируем UUID v4
$eventUuid = $this->generateUuid();
// Вставляем в outbox с prepared statement
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE);
// Экранируем строковые значения для безопасной вставки
$escapedEventUuid = $this->db->quote($eventUuid);
$escapedCompanyId = (int)$this->companyId; // целое число не требует экранирования
$escapedEntityType = $this->db->quote($entityType);
$escapedEntityId = (int)$entityId;
$escapedEventType = $this->db->quote($eventType);
$escapedActorUserId = (int)$actorUserId;
$escapedPayloadJson = $this->db->quote($payloadJson);
$sql = "INSERT INTO bi_analytics_events_outbox
(event_uuid, company_id, entity_type, entity_id, event_type, occurred_at, actor_user_id, payload_json)
VALUES ($escapedEventUuid, $escapedCompanyId, $escapedEntityType, $escapedEntityId, $escapedEventType, NOW(3), $escapedActorUserId, $escapedPayloadJson)";
try {
$this->db->query($sql);
return $eventUuid;
} catch (Exception $e) {
throw new Exception("Failed to insert event: " . $e->getMessage());
}
}
/**
* Создать событие: клиент создан
*/
public function logClientCreated($clientId, $actorUserId, $clientData) {
return $this->logEvent('client', $clientId, 'client.created', $actorUserId, array(), $clientData);
}
/**
* Создать событие: клиент обновлён
*/
public function logClientUpdated($clientId, $actorUserId, $before, $after) {
return $this->logEvent('client', $clientId, 'client.updated', $actorUserId, $before, $after);
}
/**
* Создать событие: заявка создана
*/
public function logRequisitionCreated($requisitionId, $actorUserId, $requisitionData) {
return $this->logEvent('requisition', $requisitionId, 'requisition.created', $actorUserId, array(), $requisitionData);
}
/**
* Создать событие: заявка обновлена
*/
public function logRequisitionUpdated($requisitionId, $actorUserId, $before, $after) {
return $this->logEvent('requisition', $requisitionId, 'requisition.updated', $actorUserId, $before, $after);
}
/**
* Создать событие: заявка перешла на этап
*/
public function logRequisitionMovedToStep($requisitionId, $actorUserId, $fromStepId, $toStepId) {
$before = array('step_id' => $fromStepId);
$after = array('step_id' => $toStepId);
return $this->logEvent('requisition', $requisitionId, 'requisition.moved_to_step', $actorUserId, $before, $after);
}
/**
* Создать событие: смена ответственного
*/
public function logResponsibleChanged($entityType, $entityId, $actorUserId, $fromUserId, $toUserId) {
$before = array('who_work' => $fromUserId);
$after = array('who_work' => $toUserId);
return $this->logEvent($entityType, $entityId, 'responsible_changed', $actorUserId, $before, $after);
}
/**
* Сгенерировать UUID v4
*
* @return string
*/
private function generateUuid() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff) & 0x0fff | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
}