Joywork/engine/classes/BiAnalyticsLogger.php

162 lines
6.0 KiB
PHP
Raw Normal View History

2026-08-05 20:06:56 +02:00
<?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)
);
}
}