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) ); } }