# Справка по системе событий EventManager ## Общее описание EventManager — это сервис для работы с событиями в системе «Бизнес.Точка». Он является обёрткой над встроенной системой событий CodeIgniter 4 и предоставляет два типа подписок на события: - **moduleOn()** — обработчик выполняется только при наличии активной подписки на модуль - **systemOn()** — обработчик выполняется всегда, без проверки статуса подписки EventManager используется для создания интеграций между модулями, когда действия в одном модуле должны автоматически вызывать события в другом. Например, при создании клиента в CRM может автоматически создаваться задача в модуле Tasks. --- ## Подключение EventManager EventManager подключается как сервис через `service('eventManager')`: ```php $eventManager = service('eventManager'); ``` Доступно в контроллерах через BaseController: ```php // В контроллере: $em = service('eventManager'); ``` --- ## Основные методы ### forModule() — привязка к модулю Метод `forModule()` привязывает все последующие вызовы `moduleOn()` к указанному модулю. Это означает, что подписки на события будут создаваться только если организация имеет активную подписку на этот модуль. ```php $em = service('eventManager'); // Привязываем события к модулю CRM $em->forModule('crm'); ``` После вызова `forModule()` все события, добавленные через `moduleOn()`, будут проверять подписку организации на модуль CRM. Если подписка не активна — обработчик не будет выполнен. ```php // Цепочка вызовов service('eventManager') ->forModule('crm') ->moduleOn('client.created', function($client) { // Этот код выполнится только если подписка на CRM активна }); ``` **Важно:** Метод `forModule()` необходимо вызвать перед `moduleOn()`, иначе будет выброшено исключение. --- ### moduleOn() — подписка с проверкой модуля Метод `moduleOn()` создаёт подписку на событие, которая выполняется только при соблюдении условий: 1. Модуль существует в конфигурации `BusinessModules` 2. Модуль глобально включён в настройках 3. Организация имеет активную подписку на модуль ```php $em = service('eventManager'); $em->forModule('crm'); $em->moduleOn('client.created', function($client) { // Обработчик выполнится только если CRM подписка активна log_message('debug', 'Клиент создан: ' . $client['name']); }); ``` **Сигнатура метода:** ```php public function moduleOn( string $event, // Имя события callable $callback, // Обработчик события int $priority = 100 // Приоритет выполнения ): bool ``` **Возвращаемое значение:** - `true` — подписка создана, обработчик будет выполнен - `false` — подписка не создана (модуль не активен, отключён или не существует) **Параметр `$callback`:** Обработчик события получает параметры, переданные при вызове события: ```php $em->forModule('crm'); $em->moduleOn('client.created', function($client, $userId) { echo 'Создан клиент ' . $client['name'] . ' пользователем ' . $userId; }); // Где-то в коде: Events::trigger('client.created', $clientData, $currentUserId); ``` **Приоритет выполнения:** Параметр `$priority` определяет порядок выполнения обработчиков. Меньшее значение — более высокий приоритет: ```php // Выполнится раньше (приоритет 50) $em->moduleOn('client.created', function($client) { // Логирование }, 50); // Выполнится позже (приоритет 100, значение по умолчанию) $em->moduleOn('client.created', function($client) { // Отправка уведомлений }); ``` --- ### systemOn() — подписка без проверки модуля Метод `systemOn()` создаёт подписку на событие без проверки статуса подписки. Обработчик будет выполнен всегда, независимо от того, какие модули активированы у организации. Используется для системных событий, которые должны работать для всех организаций: ```php $em = service('eventManager'); // Этот обработчик выполнится для всех организаций $em->systemOn('user.login', function($user) { log_message('info', 'Пользователь вошёл: ' . $user['email']); }); // Для отправки email-уведомлений при любом действии $em->systemOn('email.send', function($to, $subject, $body) { // Логирование отправки }); ``` **Сигнатура метода:** ```php public function systemOn( string $event, callable $callback, int $priority = 100 ): void ``` --- ### off() — отписка от события Метод `off()` удаляет подписку на событие: ```php $em = service('eventManager'); // Удаление всех обработчиков события $em->off('client.created'); // Удаление конкретного обработчика $em->off('client.created', $specificCallback); ``` --- ### currentModuleActive() — проверка статуса модуля Метод `currentModuleActive()` возвращает `true` если текущий модуль (установленный через `forModule()`) активен для организации. Используется внутри обработчиков для проверки: ```php $em->service('eventManager'); $em->forModule('tasks'); $em->moduleOn('deal.won', function($deal) { // Проверяем, активен ли модуль Tasks if ($em->currentModuleActive()) { // Создаём задачу createTaskForDeal($deal); } }); ``` --- ### getCurrentModuleCode() — получение кода модуля Метод `getCurrentModuleCode()` возвращает код модуля, установленного через `forModule()`: ```php $em = service('eventManager'); $em->forModule('crm'); $code = $em->getCurrentModuleCode(); // Вернёт 'crm' ``` --- ## Встроенные события системы ### События пользователя ```php // После успешной регистрации пользователя Events::trigger('user.registered', $user); // После подтверждения email Events::trigger('user.verified', $user); // При каждом входе в систему Events::trigger('user.login', $user); // При выходе из системы Events::trigger('user.logout', $user); // При смене пароля Events::trigger('user.passwordChanged', $user); // При изменении профиля Events::trigger('user.profileUpdated', $user, $oldData); ``` ### События организации ```php // При создании организации Events::trigger('organization.created', $organization); // При изменении данных организации Events::trigger('organization.updated', $organization, $changes); // При удалении организации (до удаления) Events::trigger('organization.deleting', $organization); // После удаления организации Events::trigger('organization.deleted', $organizationId); // При присоединении пользователя к организации Events::trigger('organization.userJoined', $organization, $user, $role); // При выходе пользователя из организации Events::trigger('organization.userLeft', $organization, $user); // При изменении роли пользователя Events::trigger('organization.userRoleChanged', $organization, $user, $oldRole, $newRole); ``` ### События модуля Клиенты ```php // При создании клиента Events::trigger('client.created', $client, $userId); // При обновлении клиента Events::trigger('client.updated', $client, $changes, $userId); // При удалении клиента Events::trigger('client.deleted', $clientId, $userId); // При импорте клиентов Events::trigger('client.imported', $clients, $userId); ``` --- ## Примеры интеграции модулей ### Пример 1: Создание задачи при создании клиента ```php // В модуле Tasks — файл bootstrap или Config/Events.php service('eventManager') ->forModule('tasks') ->moduleOn('client.created', function($client, $userId) { // Создаём задачу на первичный контакт $taskModel = new \App\Modules\Tasks\Models\TaskModel(); $taskModel->insert([ 'organization_id' => $client['organization_id'], 'title' => 'Первый контакт с клиентом: ' . $client['name'], 'description' => 'Необходимо связаться с клиентом для уточнения потребностей', 'assigned_to' => $userId, 'status' => 'todo', 'priority' => 'medium', 'due_at' => date('Y-m-d H:i:s', strtotime('+1 day')), 'created_by' => $userId, ]); }); ``` ### Пример 2: Автоматический переход сделки при завершении задачи ```php // В модуле CRM service('eventManager') ->forModule('crm') ->moduleOn('task.completed', function($task, $userId) { if ($task['related_type'] === 'deal' && $task['related_id']) { $dealModel = new \App\Modules\CRM\Models\DealModel(); // Получаем сделку $deal = $dealModel->find($task['related_id']); if ($deal && $deal['stage'] === 'proposal') { // Переводим сделку на следующий этап $dealModel->update($deal['id'], [ 'stage' => 'negotiation', 'updated_at' => date('Y-m-d H:i:s'), ]); // Логируем переход log_message('info', 'Сделка #' . $deal['id'] . ' переведена на этап переговоров после завершения задачи'); } } }); ``` ### Пример 3: Уведомление при записи на приём ```php // В модуле Booking service('eventManager') ->forModule('booking') ->moduleOn('booking.created', function($booking, $client, $userId) { // Отправляем уведомление клиенту $emailService = service('email'); $emailService->send( $client['email'], 'Подтверждение записи', 'Уважаемый ' . $client['name'] . ', Ваша запись на ' . $booking['service_name'] . ' подтверждена на ' . date('d.m.Y в H:i', strtotime($booking['starts_at'])) ); // Создаём задачу для менеджера $taskModel = new \App\Modules\Tasks\Models\TaskModel(); $taskModel->insert([ 'organization_id' => $booking['organization_id'], 'title' => 'Подготовка к записи: ' . $client['name'], 'description' => 'Клиент записан на услугу ' . $booking['service_name'], 'assigned_to' => $booking['staff_id'], 'status' => 'todo', 'priority' => 'normal', 'due_at' => $booking['starts_at'], ]); }); ``` ### Пример 4: Создание проекта Proof при выигрыше сделки ```php // В модуле CRM service('eventManager') ->forModule('crm') ->moduleOn('deal.won', function($deal, $userId) { // Проверяем, активен ли модуль Proof if (service('moduleSubscription')->isModuleActive('proof')) { $projectModel = new \App\Modules\Proof\Models\ProjectModel(); $projectModel->insert([ 'organization_id' => $deal['organization_id'], 'client_id' => $deal['client_id'], 'title' => 'Проект по сделке #' . $deal['id'], 'description' => 'Автоматически создан при выигрыше сделки', 'status' => 'active', 'created_by' => $userId, ]); } }); ``` --- ## Правила именования событий Для консистентности системы используйте следующие правила именования событий: ### Формат: `сущность.действие` | Сущность | Действия | Пример события | |----------|----------|----------------| | user | registered, verified, login, logout, passwordChanged, profileUpdated | `user.login` | | organization | created, updated, deleting, deleted, userJoined, userLeft, userRoleChanged | `organization.created` | | client | created, updated, deleted, imported | `client.created` | | deal | created, updated, deleted, won, lost | `deal.won` | | booking | created, updated, cancelled, completed | `booking.created` | | task | created, updated, deleted, started, completed | `task.completed` | | project | created, updated, deleted, archived | `project.created` | | file | uploaded, deleted, approved, rejected | `file.uploaded` | | email | send, sent, failed | `email.send` | ### Группы событий модулей - **CRM:** `client.*`, `deal.*`, `pipeline.*` - **Booking:** `booking.*`, `service.*`, `staff.*` - **Proof:** `project.*`, `file.*`, `comment.*` - **Tasks:** `task.*`, `board.*`, `comment.*` --- ## Вызов событий в коде Для вызова события используйте `Events::trigger()` из CodeIgniter 4: ```php use CodeIgniter\Events\Events; // Простой вызов Events::trigger('client.created', $clientData); // С несколькими параметрами Events::trigger('deal.won', $deal, $userId); // С именованными параметрами (начиная с CI 4.3+) Events::trigger('client.updated', [ 'client' => $clientData, 'changes' => $changes, 'userId' => $userId, ]); ``` --- ## Порядок инициализации событий События модулей должны инициализироваться в файле `app/Config/Events.php`: ```php forModule('crm'); $em->moduleOn('client.created', function($client, $userId) { // Логика создания задачи }); // Интеграция CRM → Proof $em->moduleOn('deal.won', function($deal, $userId) { // Логика создания проекта }); } } register_crm_events(); ``` --- ## Логирование событий EventManager автоматически логирует информацию о подписках и выполнении событий: - **Подписка создана:** `"EventManager: Subscribed to event 'client.created' for module 'crm'"` - **Модуль отключён:** `"EventManager: Module 'crm' is disabled globally"` - **Подписка не активна:** `"EventManager: Organization subscription not active for module 'crm'"` - **Системное событие:** `"EventManager: System event subscribed: 'user.login'"` Уровень логирования — `debug` для информации и `error` для ошибок конфигурации. --- ## Тестирование событий ### Ручное тестирование в разработке ```php // В контроллере для тестирования public function testEvent() { $testClient = [ 'id' => 999, 'name' => 'Тестовый клиент', 'email' => 'test@example.com', 'organization_id' => session()->get('active_org_id'), ]; // Вызываем событие напрямую Events::trigger('client.created', $testClient, session()->get('user_id')); return 'Событие вызвано, проверьте логи'; } ``` ### Отладка подписок ```php // Получение всех обработчиков события $handlers = Events::listeners('client.created'); foreach ($handlers as $handler) { log_message('debug', 'Handler: ' . print_r($handler, true)); } ``` --- ## Типичные ошибки и их устранение ### Ошибка: "Module code not set" ```php // Неправильно: $em->moduleOn('client.created', $callback); // Правильно: $em->forModule('crm')->moduleOn('client.created', $callback); ``` ### Событие не срабатывает Возможные причины: 1. Модуль не активирован для организации 2. Модуль отключён глобально в конфигурации 3. Ошибка в имени события 4. Исключение в обработчике блокирует выполнение Проверка: ```php // Проверка статуса модуля $em = service('eventManager'); $em->forModule('crm'); if ($em->currentModuleActive()) { echo 'Модуль активен'; } else { echo 'Модуль не активен'; } ``` ### Конфликты приоритетов При использовании нескольких обработчиков одного события убедитесь в корректном порядке выполнения: ```php // Сначала сохраняем данные $em->moduleOn('deal.won', function($deal) { saveDealToArchive($deal); }, 10); // Выполнится первым // Затем отправляем уведомления $em->moduleOn('deal.won', function($deal) { sendDealWonNotification($deal); }, 100); // Выполнится вторым ``` --- ## Сводка методов | Метод | Описание | Возвращает | |-------|----------|------------| | `forModule($code)` | Привязать к модулю | `$this` | | `moduleOn($event, $callback, $priority)` | Подписка с проверкой | `bool` | | `systemOn($event, $callback, $priority)` | Системная подписка | `void` | | `off($event, $callback)` | Отписка | `void` | | `currentModuleActive()` | Проверка модуля | `bool` | | `getCurrentModuleCode()` | Код модуля | `string\|null` |