Internal API: различия между версиями

Материал из WiKi - UserSide
(Новая страница: «Событийная система»)
 
([IronBot] Убрана ручная нумерация заголовков событий)
 
(не показана 1 промежуточная версия этого же участника)
Строка 1: Строка 1:
Событийная система
Custom Events v2 - это локальная система внутренних событий ERP. Она выполняет клиентский PHP-код внутри текущего PHP-процесса ERP без HTTP-запросов, веб-хуков и внешних очередей.
 
Старый механизм <code>legacy/Config/custom_api.txt</code> с функцией <code>api_function(...)</code> остается legacy-контрактом. В v2 старые названия используются только для карты совместимости. Новый клиентский код должен использовать константы <code>CustomEvent::...</code>.
 
== Файлы ==
 
Рабочий клиентский файл один:
 
<pre>evolution/CustomEvents/v2/handlers.php</pre>
 
Пример:
 
<pre>evolution/CustomEvents/v2/handlers.example.php</pre>
 
Список всех констант:
 
<pre>evolution/CustomEvents/Runtime/v2/CustomEvent.php</pre>
 
Карта старых имен в новые события:
 
<pre>evolution/CustomEvents/Runtime/v2/CustomEventLegacyEventMap.php</pre>
 
Кеш:
 
<pre>var/cache/CustomEvents/handlers.v2.cache.php</pre>
 
Лог:
 
<pre>var/log/custom-events-v2.log</pre>
 
== Как писать обработчики ==
 
Используется одна функция регистрации: <code>internal_event(...)</code>.
 
В одном файле можно зарегистрировать несколько обработчиков подряд:
 
<syntaxhighlight lang="php">
internal_event(
    CustomEvent::TASK_CREATED,
    static function ($event): bool {
        $taskId = $event->get('task_id');
 
        return true;
    },
);
 
internal_event(
    CustomEvent::CUSTOMER_STATUS_CHANGE_BEFORE,
    static function ($event): bool|string {
        if ((int)$event->get('new_status_id') === 0) {
            return 'Запрещено переводить абонента в этот статус';
        }
 
        return true;
    },
);
 
internal_event(
    CustomEvent::CUSTOMER_CARD_PRIMARY_CONTENT_RENDER,
    static function ($event): string {
        $customerId = (int)$event->get('customer_id');
 
        return '<div>Дополнительная информация по абоненту #' . $customerId . '</div>';
    },
);
</syntaxhighlight>
 
В обработчик всегда приходит один параметр <code>$event</code>.
 
Данные читаются без типовых оберток:
 
<syntaxhighlight lang="php">
$taskId = $event->get('task_id');
$customerId = $event->get('customer_id');
$data = $event->all();
</syntaxhighlight>
 
Если нужен конкретный тип, приводите значение в своем коде:
 
<syntaxhighlight lang="php">
$taskId = (int)$event->get('task_id');
$comment = (string)$event->get('comment');
</syntaxhighlight>
 
== Результат обработчика ==
 
Обработчик возвращает простое значение:
 
{| class="wikitable"
! Возврат
! Значение
|-
| <code>true</code>
| Обработчик выполнен успешно
|-
| <code>false</code>
| Для события <code>*.before</code> запретить штатную операцию
|-
| <code>null</code>
| Ничего не менять
|-
| строка
| Для <code>*.before</code> запретить операцию с текстом причины; для <code>*.render</code> вывести строку/HTML
|}
 
События <code>*.before</code> могут отменять штатную операцию. Остальные события не должны отменять уже выполненное действие.
 
== Пример запрета операции ==
 
<syntaxhighlight lang="php">
internal_event(
    CustomEvent::CUSTOMER_STATUS_CHANGE_BEFORE,
    static function ($event): bool|string {
        if ((int)$event->get('new_status_id') === 0) {
            return 'Запрещено переводить абонента в этот статус';
        }
 
        return true;
    },
);
</syntaxhighlight>
 
== Пример вывода HTML ==
 
<syntaxhighlight lang="php">
internal_event(
    CustomEvent::CUSTOMER_CARD_PRIMARY_CONTENT_RENDER,
    static function ($event): string {
        $customerId = (int)$event->get('customer_id');
 
        return '<div>Дополнительная информация по абоненту #' . $customerId . '</div>';
    },
);
</syntaxhighlight>
 
== Проверка, кеш и лог ==
 
При изменении <code>evolution/CustomEvents/v2/handlers.php</code> ERP проверяет синтаксис через <code>php -l</code>. Если синтаксис корректный, файл копируется в <code>var/cache/CustomEvents/handlers.v2.cache.php</code>.
 
Runtime-лог пишется в JSON Lines в <code>var/log/custom-events-v2.log</code>. Одна строка - один вызов обработчика, ошибка или факт отсутствия обработчиков.
 
Пример строки:
 
<syntaxhighlight lang="json">
{"ts":"2026-05-10T12:01:03+03:00","event":"task.created","has_handlers":true,"handlers":1,"handler_index":0,"duration_ms":2.341,"memory_kb":18,"result":"allow","message":null}
</syntaxhighlight>
 
Лог ротируется в:
 
<pre>var/log/custom-events-v2.log.1</pre>
 
== Ошибки ==
 
Если в обработчике возникнет ошибка, ERP запишет ее в лог и продолжит работу по правилам конкретного события.
 
Обработчик выполняется внутри того же PHP-процесса, что и ядро ERP. Через ядро проходят тысячи операций, и медленный или ошибочный клиентский код может остановить или дестабилизировать работу всей системы. Не используйте <code>exit</code>, <code>die</code>, бесконечные циклы, тяжелые SQL-запросы без ограничений и долгие внешние операции. Держите обработчики короткими и предсказуемыми.
 
== Рекомендации ==
 
* Используйте только константы <code>CustomEvent::...</code>.
* Не пишите строку события вручную.
* Делайте обработчики короткими.
* Не вызывайте HTTP/webhook из высокочастотных событий.
* Не используйте <code>exit</code>, <code>die</code>, бесконечные циклы и тяжелые выборки без ограничений.
* Возвращайте <code>true</code>, <code>false</code>, <code>null</code> или строку.
 
== Все события ==
 
Ниже перечислены все события v2. Полный технический список констант находится в <code>evolution/CustomEvents/Runtime/v2/CustomEvent.php</code>.
 
Поля <code>event</code> указаны как текущий контракт данных.
 
=== Здания ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::BUILDING_CREATED</code>
| После создания здания
| <code>building_id</code>, <code>data</code>
|-
| <code>CustomEvent::BUILDING_CHANGED</code>
| После редактирования здания
| <code>building_id</code>, <code>data</code>
|}
 
=== Коммутация ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::COMMUTATION_CREATE_BEFORE</code>
| Перед созданием коммутации между объектами
| <code>source_type</code>, <code>source_id</code>, <code>target_type</code>, <code>target_id</code>, <code>data</code>
|-
| <code>CustomEvent::COMMUTATION_DELETE_BEFORE</code>
| Перед удалением коммутации между объектами
| <code>source_type</code>, <code>source_id</code>, <code>target_type</code>, <code>target_id</code>, <code>data</code>
|}
 
=== Главная страница ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::DASHBOARD_TOP_CONTENT_RENDER</code>
| При выводе текста в начальной части главной страницы
| <code>employee_id</code>, <code>data</code>
|}
 
=== Оборудование ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::DEVICE_CHANGED</code>
| После редактирования оборудования
| <code>device_id</code>, <code>device_type</code>, <code>data</code>
|-
| <code>CustomEvent::DEVICE_NOTIFICATION_DOWN</code>
| При фиксации недоступности оборудования
| <code>device_id</code>, <code>device_type</code>, <code>ip</code>, <code>data</code>
|-
| <code>CustomEvent::DEVICE_NOTIFICATION_UP</code>
| При восстановлении доступности оборудования
| <code>device_id</code>, <code>device_type</code>, <code>ip</code>, <code>data</code>
|-
| <code>CustomEvent::DEVICE_INTERFACE_PORT_NUMBER_RENDER</code>
| При выводе номера порта в таблице интерфейсов
| <code>device_id</code>, <code>interface_id</code>, <code>port_number</code>, <code>data</code>
|-
| <code>CustomEvent::DEVICE_INTERFACE_ADDITIONAL_DATA_RENDER</code>
| При выводе дополнительного содержимого в карточке оборудования
| <code>device_id</code>, <code>data</code>
|}
 
=== Абоненты ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::CUSTOMER_CREATED</code>
| После создания абонента
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_CHANGE_BEFORE</code>
| Перед редактированием абонента
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_CHANGED</code>
| После редактирования абонента
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_MERGED</code>
| После объединения абонентов
| <code>customer_id</code>, <code>target_customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_STATUS_CHANGE_BEFORE</code>
| Перед изменением статуса абонента
| <code>customer_id</code>, <code>new_status_id</code>, <code>old_status_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_STATUS_CHANGED</code>
| После изменения статуса абонента
| <code>customer_id</code>, <code>new_status_id</code>, <code>old_status_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_TARIFF_CHANGE_BEFORE</code>
| Перед изменением тарифа абонента
| <code>customer_id</code>, <code>billing_id</code>, <code>new_tariff_id</code>, <code>old_tariff_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_TARIFF_CHANGED</code>
| После изменения тарифа абонента
| <code>customer_id</code>, <code>billing_id</code>, <code>new_tariff_id</code>, <code>old_tariff_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_BALANCE_CHANGED</code>
| При изменении баланса абонента
| <code>customer_id</code>, <code>amount</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_FORMER_TRANSFER_BEFORE</code>
| Перед переводом абонента в бывшие абоненты
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_FORMER_TRANSFERRED</code>
| После перевода абонента в бывшие абоненты
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_FORMER_RESTORE_BEFORE</code>
| Перед восстановлением абонента из бывших абонентов
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_FORMER_RESTORED</code>
| После восстановления абонента из бывших абонентов
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_DISCONNECT_PLAN_BEFORE</code>
| Перед постановкой абонента на отключение
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_DISCONNECT_PLANNED</code>
| После постановки абонента на отключение
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_DISCONNECT_BEFORE</code>
| Перед отключением абонента
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_DISCONNECTED</code>
| После отключения абонента
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_DISCONNECT_CANCEL_BEFORE</code>
| Перед отменой отключения абонента
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_DISCONNECT_CANCELLED</code>
| После отмены отключения абонента
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_SERVICE_ENABLE_BEFORE</code>
| Перед подключением услуги абоненту
| <code>customer_id</code>, <code>service_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_SERVICE_ENABLED</code>
| После подключения услуги абоненту
| <code>customer_id</code>, <code>service_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_SERVICE_DISABLE_BEFORE</code>
| Перед отключением услуги абоненту
| <code>customer_id</code>, <code>service_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_SERVICE_DISABLED</code>
| После отключения услуги абоненту
| <code>customer_id</code>, <code>service_id</code>, <code>data</code>
|}
 
==== IP/MAC абонента ====
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::CUSTOMER_IP_ADD_BEFORE</code>
| Перед добавлением IP/MAC-адреса
| <code>customer_id</code>, <code>ip</code>, <code>mac</code>, <code>subnet_property</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_IP_ADDED</code>
| После добавления IP/MAC-адреса
| <code>customer_id</code>, <code>ip</code>, <code>mac</code>, <code>subnet_property</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_IP_DELETE_BEFORE</code>
| Перед удалением IP/MAC-адреса
| <code>customer_id</code>, <code>ip</code>, <code>mac</code>, <code>subnet_property</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_IP_DELETED</code>
| После удаления IP/MAC-адреса
| <code>customer_id</code>, <code>ip</code>, <code>mac</code>, <code>subnet_property</code>, <code>data</code>
|}
 
==== Метки абонента ====
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::CUSTOMER_TAG_ADD_BEFORE</code>
| Перед добавлением метки абоненту
| <code>customer_id</code>, <code>tag_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_TAG_ADDED</code>
| После добавления метки абоненту
| <code>customer_id</code>, <code>tag_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_TAG_DELETE_BEFORE</code>
| Перед удалением метки абонента
| <code>customer_id</code>, <code>tag_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_TAG_DELETED</code>
| После удаления метки абонента
| <code>customer_id</code>, <code>tag_id</code>, <code>data</code>
|}
 
==== Личный кабинет абонента ====
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::CUSTOMER_PORTAL_REGISTRATION_BEFORE</code>
| Перед регистрацией абонента в личном кабинете
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_PORTAL_REGISTERED</code>
| После регистрации абонента в личном кабинете
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_PORTAL_LOGIN_BEFORE</code>
| Перед входом абонента в личный кабинет
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_PORTAL_DASHBOARD_RENDER</code>
| При выводе содержимого на главной странице личного кабинета
| <code>customer_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_PORTAL_PAGE_RENDER</code>
| При выводе содержимого на страницах личного кабинета
| <code>customer_id</code>, <code>page_mode</code>, <code>page_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_PORTAL_HEAD_RENDER</code>
| При выводе содержимого после <code>head</code> в личном кабинете
| <code>customer_id</code>, <code>data</code>
|}
 
==== Карточка абонента ====
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::CUSTOMER_CARD_PRIMARY_CONTENT_RENDER</code>
| При выводе основного содержимого в карточке абонента
| <code>customer_id</code>, <code>mode</code>, <code>employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_CARD_SECONDARY_CONTENT_RENDER</code>
| При выводе дополнительного содержимого в карточке абонента
| <code>customer_id</code>, <code>mode</code>, <code>employee_id</code>, <code>ip_mac</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_CARD_IP_MAC_CONTENT_RENDER</code>
| При выводе информации возле IP/MAC-адресов абонента
| <code>customer_id</code>, <code>ip</code>, <code>mac</code>, <code>data</code>
|-
| <code>CustomEvent::CUSTOMER_CARD_SMS_CONTENT_RENDER</code>
| При выводе содержимого отправки SMS в карточке абонента
| <code>customer_id</code>, <code>data</code>
|}
 
=== Сотрудники ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::EMPLOYEE_MESSAGE_CREATED</code>
| После создания сообщения сотруднику
| <code>receiver_employee_id</code>, <code>message_id</code>, <code>data</code>
|-
| <code>CustomEvent::EMPLOYEE_CHANGED</code>
| После редактирования сотрудника
| <code>employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::EMPLOYEE_PERSONAL_SECTION_CONTENT_RENDER</code>
| При выводе информации в персональном разделе сотрудника
| <code>employee_id</code>, <code>data</code>
|}
 
==== Табель работ ====
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::EMPLOYEE_TIMESHEET_PRINT_HEADER_RENDER</code>
| При выводе шапки печатного табеля
| <code>employee_id</code>, <code>period</code>, <code>data</code>
|-
| <code>CustomEvent::EMPLOYEE_TIMESHEET_PRINT_FOOTER_RENDER</code>
| При выводе подвала печатного табеля
| <code>employee_id</code>, <code>period</code>, <code>data</code>
|}
 
=== Склад ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::INVENTORY_TRANSFER_BEFORE</code>
| Перед перемещением ТМЦ
| <code>inventory_id</code>, <code>operation_id</code>, <code>data</code>
|-
| <code>CustomEvent::INVENTORY_TRANSFERRED</code>
| После перемещения ТМЦ
| <code>inventory_id</code>, <code>operation_id</code>, <code>data</code>
|}
 
=== Задания ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::TASK_CARD_RENDER</code>
| При выводе содержимого в карточке задания
| <code>task_type_id</code>, <code>task_id</code>, <code>employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_WORK_DATE_RENDER</code>
| При выводе содержимого возле даты работ
| <code>task_type_id</code>, <code>task_id</code>, <code>employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_CREATE_BEFORE</code>
| Перед созданием задания
| <code>task_type_id</code>, <code>author_employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_CREATED</code>
| После создания задания
| <code>task_id</code>, <code>task_type_id</code>, <code>author_employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_DELETE_BEFORE</code>
| Перед удалением задания
| <code>task_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_CHANGE_BEFORE</code>
| Перед редактированием задания
| <code>task_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_CHANGED</code>
| После редактирования задания
| <code>task_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_COMMENT_CREATE_BEFORE</code>
| Перед добавлением комментария к заданию
| <code>task_id</code>, <code>comment</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_COMMENT_CREATED</code>
| После добавления комментария к заданию
| <code>task_id</code>, <code>comment_id</code>, <code>comment</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_COMMENT_CHANGED</code>
| После редактирования комментария к заданию
| <code>task_id</code>, <code>comment_id</code>, <code>comment</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_COMMENT_TEXT_RENDER</code>
| При выводе текста комментария задания
| <code>task_id</code>, <code>comment_id</code>, <code>comment</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_DIVISION_ASSIGN_BEFORE</code>
| Перед добавлением подразделения к заданию
| <code>task_id</code>, <code>division_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_DIVISION_REMOVE_BEFORE</code>
| Перед исключением подразделения из задания
| <code>task_id</code>, <code>division_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_EMPLOYEE_ASSIGN_BEFORE</code>
| Перед добавлением исполнителя к заданию
| <code>task_id</code>, <code>employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_EMPLOYEE_REMOVE_BEFORE</code>
| Перед исключением исполнителя из задания
| <code>task_id</code>, <code>employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_WATCHER_DIVISION_ASSIGN_BEFORE</code>
| Перед добавлением подразделения наблюдателем
| <code>task_id</code>, <code>division_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_WATCHER_EMPLOYEE_ASSIGN_BEFORE</code>
| Перед добавлением сотрудника наблюдателем
| <code>task_id</code>, <code>employee_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_OBJECT_ATTACHED</code>
| После добавления объекта к заданию
| <code>task_id</code>, <code>object_type</code>, <code>object_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_OBJECT_DETACHED</code>
| После исключения объекта из задания
| <code>task_id</code>, <code>object_type</code>, <code>object_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_RETURNED_TO_AUTHOR</code>
| После возврата задания автору
| <code>task_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_STATE_CHANGE_BEFORE</code>
| Перед изменением статуса задания
| <code>task_id</code>, <code>old_state_id</code>, <code>new_state_id</code>, <code>data</code>
|-
| <code>CustomEvent::TASK_STATE_CHANGED</code>
| После изменения статуса задания
| <code>task_id</code>, <code>old_state_id</code>, <code>new_state_id</code>, <code>data</code>
|}
 
=== Сооружения связи ===
 
{| class="wikitable"
! Константа
! Когда вызывается
! Данные <code>$event</code>
|-
| <code>CustomEvent::NODE_CARD_ADDITIONAL_DATA_RENDER</code>
| При выводе дополнительного содержимого в карточке сооружения связи
| <code>node_id</code>, <code>data</code>
|-
| <code>CustomEvent::NODE_CARD_PRIMARY_CONTENT_RENDER</code>
| При выводе основного содержимого в карточке сооружения связи
| <code>node_id</code>, <code>data</code>
|}

Текущая версия от 17:59, 21 мая 2026

Custom Events v2 - это локальная система внутренних событий ERP. Она выполняет клиентский PHP-код внутри текущего PHP-процесса ERP без HTTP-запросов, веб-хуков и внешних очередей.

Старый механизм legacy/Config/custom_api.txt с функцией api_function(...) остается legacy-контрактом. В v2 старые названия используются только для карты совместимости. Новый клиентский код должен использовать константы CustomEvent::....

Файлы

Рабочий клиентский файл один:

evolution/CustomEvents/v2/handlers.php

Пример:

evolution/CustomEvents/v2/handlers.example.php

Список всех констант:

evolution/CustomEvents/Runtime/v2/CustomEvent.php

Карта старых имен в новые события:

evolution/CustomEvents/Runtime/v2/CustomEventLegacyEventMap.php

Кеш:

var/cache/CustomEvents/handlers.v2.cache.php

Лог:

var/log/custom-events-v2.log

Как писать обработчики

Используется одна функция регистрации: internal_event(...).

В одном файле можно зарегистрировать несколько обработчиков подряд:

internal_event(
    CustomEvent::TASK_CREATED,
    static function ($event): bool {
        $taskId = $event->get('task_id');

        return true;
    },
);

internal_event(
    CustomEvent::CUSTOMER_STATUS_CHANGE_BEFORE,
    static function ($event): bool|string {
        if ((int)$event->get('new_status_id') === 0) {
            return 'Запрещено переводить абонента в этот статус';
        }

        return true;
    },
);

internal_event(
    CustomEvent::CUSTOMER_CARD_PRIMARY_CONTENT_RENDER,
    static function ($event): string {
        $customerId = (int)$event->get('customer_id');

        return '<div>Дополнительная информация по абоненту #' . $customerId . '</div>';
    },
);

В обработчик всегда приходит один параметр $event.

Данные читаются без типовых оберток:

$taskId = $event->get('task_id');
$customerId = $event->get('customer_id');
$data = $event->all();

Если нужен конкретный тип, приводите значение в своем коде:

$taskId = (int)$event->get('task_id');
$comment = (string)$event->get('comment');

Результат обработчика

Обработчик возвращает простое значение:

Возврат Значение
true Обработчик выполнен успешно
false Для события *.before запретить штатную операцию
null Ничего не менять
строка Для *.before запретить операцию с текстом причины; для *.render вывести строку/HTML

События *.before могут отменять штатную операцию. Остальные события не должны отменять уже выполненное действие.

Пример запрета операции

internal_event(
    CustomEvent::CUSTOMER_STATUS_CHANGE_BEFORE,
    static function ($event): bool|string {
        if ((int)$event->get('new_status_id') === 0) {
            return 'Запрещено переводить абонента в этот статус';
        }

        return true;
    },
);

Пример вывода HTML

internal_event(
    CustomEvent::CUSTOMER_CARD_PRIMARY_CONTENT_RENDER,
    static function ($event): string {
        $customerId = (int)$event->get('customer_id');

        return '<div>Дополнительная информация по абоненту #' . $customerId . '</div>';
    },
);

Проверка, кеш и лог

При изменении evolution/CustomEvents/v2/handlers.php ERP проверяет синтаксис через php -l. Если синтаксис корректный, файл копируется в var/cache/CustomEvents/handlers.v2.cache.php.

Runtime-лог пишется в JSON Lines в var/log/custom-events-v2.log. Одна строка - один вызов обработчика, ошибка или факт отсутствия обработчиков.

Пример строки:

{"ts":"2026-05-10T12:01:03+03:00","event":"task.created","has_handlers":true,"handlers":1,"handler_index":0,"duration_ms":2.341,"memory_kb":18,"result":"allow","message":null}

Лог ротируется в:

var/log/custom-events-v2.log.1

Ошибки

Если в обработчике возникнет ошибка, ERP запишет ее в лог и продолжит работу по правилам конкретного события.

Обработчик выполняется внутри того же PHP-процесса, что и ядро ERP. Через ядро проходят тысячи операций, и медленный или ошибочный клиентский код может остановить или дестабилизировать работу всей системы. Не используйте exit, die, бесконечные циклы, тяжелые SQL-запросы без ограничений и долгие внешние операции. Держите обработчики короткими и предсказуемыми.

Рекомендации

  • Используйте только константы CustomEvent::....
  • Не пишите строку события вручную.
  • Делайте обработчики короткими.
  • Не вызывайте HTTP/webhook из высокочастотных событий.
  • Не используйте exit, die, бесконечные циклы и тяжелые выборки без ограничений.
  • Возвращайте true, false, null или строку.

Все события

Ниже перечислены все события v2. Полный технический список констант находится в evolution/CustomEvents/Runtime/v2/CustomEvent.php.

Поля event указаны как текущий контракт данных.

Здания

Константа Когда вызывается Данные $event
CustomEvent::BUILDING_CREATED После создания здания building_id, data
CustomEvent::BUILDING_CHANGED После редактирования здания building_id, data

Коммутация

Константа Когда вызывается Данные $event
CustomEvent::COMMUTATION_CREATE_BEFORE Перед созданием коммутации между объектами source_type, source_id, target_type, target_id, data
CustomEvent::COMMUTATION_DELETE_BEFORE Перед удалением коммутации между объектами source_type, source_id, target_type, target_id, data

Главная страница

Константа Когда вызывается Данные $event
CustomEvent::DASHBOARD_TOP_CONTENT_RENDER При выводе текста в начальной части главной страницы employee_id, data

Оборудование

Константа Когда вызывается Данные $event
CustomEvent::DEVICE_CHANGED После редактирования оборудования device_id, device_type, data
CustomEvent::DEVICE_NOTIFICATION_DOWN При фиксации недоступности оборудования device_id, device_type, ip, data
CustomEvent::DEVICE_NOTIFICATION_UP При восстановлении доступности оборудования device_id, device_type, ip, data
CustomEvent::DEVICE_INTERFACE_PORT_NUMBER_RENDER При выводе номера порта в таблице интерфейсов device_id, interface_id, port_number, data
CustomEvent::DEVICE_INTERFACE_ADDITIONAL_DATA_RENDER При выводе дополнительного содержимого в карточке оборудования device_id, data

Абоненты

Константа Когда вызывается Данные $event
CustomEvent::CUSTOMER_CREATED После создания абонента customer_id, data
CustomEvent::CUSTOMER_CHANGE_BEFORE Перед редактированием абонента customer_id, data
CustomEvent::CUSTOMER_CHANGED После редактирования абонента customer_id, data
CustomEvent::CUSTOMER_MERGED После объединения абонентов customer_id, target_customer_id, data
CustomEvent::CUSTOMER_STATUS_CHANGE_BEFORE Перед изменением статуса абонента customer_id, new_status_id, old_status_id, data
CustomEvent::CUSTOMER_STATUS_CHANGED После изменения статуса абонента customer_id, new_status_id, old_status_id, data
CustomEvent::CUSTOMER_TARIFF_CHANGE_BEFORE Перед изменением тарифа абонента customer_id, billing_id, new_tariff_id, old_tariff_id, data
CustomEvent::CUSTOMER_TARIFF_CHANGED После изменения тарифа абонента customer_id, billing_id, new_tariff_id, old_tariff_id, data
CustomEvent::CUSTOMER_BALANCE_CHANGED При изменении баланса абонента customer_id, amount, data
CustomEvent::CUSTOMER_FORMER_TRANSFER_BEFORE Перед переводом абонента в бывшие абоненты customer_id, data
CustomEvent::CUSTOMER_FORMER_TRANSFERRED После перевода абонента в бывшие абоненты customer_id, data
CustomEvent::CUSTOMER_FORMER_RESTORE_BEFORE Перед восстановлением абонента из бывших абонентов customer_id, data
CustomEvent::CUSTOMER_FORMER_RESTORED После восстановления абонента из бывших абонентов customer_id, data
CustomEvent::CUSTOMER_DISCONNECT_PLAN_BEFORE Перед постановкой абонента на отключение customer_id, data
CustomEvent::CUSTOMER_DISCONNECT_PLANNED После постановки абонента на отключение customer_id, data
CustomEvent::CUSTOMER_DISCONNECT_BEFORE Перед отключением абонента customer_id, data
CustomEvent::CUSTOMER_DISCONNECTED После отключения абонента customer_id, data
CustomEvent::CUSTOMER_DISCONNECT_CANCEL_BEFORE Перед отменой отключения абонента customer_id, data
CustomEvent::CUSTOMER_DISCONNECT_CANCELLED После отмены отключения абонента customer_id, data
CustomEvent::CUSTOMER_SERVICE_ENABLE_BEFORE Перед подключением услуги абоненту customer_id, service_id, data
CustomEvent::CUSTOMER_SERVICE_ENABLED После подключения услуги абоненту customer_id, service_id, data
CustomEvent::CUSTOMER_SERVICE_DISABLE_BEFORE Перед отключением услуги абоненту customer_id, service_id, data
CustomEvent::CUSTOMER_SERVICE_DISABLED После отключения услуги абоненту customer_id, service_id, data

IP/MAC абонента

Константа Когда вызывается Данные $event
CustomEvent::CUSTOMER_IP_ADD_BEFORE Перед добавлением IP/MAC-адреса customer_id, ip, mac, subnet_property, data
CustomEvent::CUSTOMER_IP_ADDED После добавления IP/MAC-адреса customer_id, ip, mac, subnet_property, data
CustomEvent::CUSTOMER_IP_DELETE_BEFORE Перед удалением IP/MAC-адреса customer_id, ip, mac, subnet_property, data
CustomEvent::CUSTOMER_IP_DELETED После удаления IP/MAC-адреса customer_id, ip, mac, subnet_property, data

Метки абонента

Константа Когда вызывается Данные $event
CustomEvent::CUSTOMER_TAG_ADD_BEFORE Перед добавлением метки абоненту customer_id, tag_id, data
CustomEvent::CUSTOMER_TAG_ADDED После добавления метки абоненту customer_id, tag_id, data
CustomEvent::CUSTOMER_TAG_DELETE_BEFORE Перед удалением метки абонента customer_id, tag_id, data
CustomEvent::CUSTOMER_TAG_DELETED После удаления метки абонента customer_id, tag_id, data

Личный кабинет абонента

Константа Когда вызывается Данные $event
CustomEvent::CUSTOMER_PORTAL_REGISTRATION_BEFORE Перед регистрацией абонента в личном кабинете customer_id, data
CustomEvent::CUSTOMER_PORTAL_REGISTERED После регистрации абонента в личном кабинете customer_id, data
CustomEvent::CUSTOMER_PORTAL_LOGIN_BEFORE Перед входом абонента в личный кабинет customer_id, data
CustomEvent::CUSTOMER_PORTAL_DASHBOARD_RENDER При выводе содержимого на главной странице личного кабинета customer_id, data
CustomEvent::CUSTOMER_PORTAL_PAGE_RENDER При выводе содержимого на страницах личного кабинета customer_id, page_mode, page_id, data
CustomEvent::CUSTOMER_PORTAL_HEAD_RENDER При выводе содержимого после head в личном кабинете customer_id, data

Карточка абонента

Константа Когда вызывается Данные $event
CustomEvent::CUSTOMER_CARD_PRIMARY_CONTENT_RENDER При выводе основного содержимого в карточке абонента customer_id, mode, employee_id, data
CustomEvent::CUSTOMER_CARD_SECONDARY_CONTENT_RENDER При выводе дополнительного содержимого в карточке абонента customer_id, mode, employee_id, ip_mac, data
CustomEvent::CUSTOMER_CARD_IP_MAC_CONTENT_RENDER При выводе информации возле IP/MAC-адресов абонента customer_id, ip, mac, data
CustomEvent::CUSTOMER_CARD_SMS_CONTENT_RENDER При выводе содержимого отправки SMS в карточке абонента customer_id, data

Сотрудники

Константа Когда вызывается Данные $event
CustomEvent::EMPLOYEE_MESSAGE_CREATED После создания сообщения сотруднику receiver_employee_id, message_id, data
CustomEvent::EMPLOYEE_CHANGED После редактирования сотрудника employee_id, data
CustomEvent::EMPLOYEE_PERSONAL_SECTION_CONTENT_RENDER При выводе информации в персональном разделе сотрудника employee_id, data

Табель работ

Константа Когда вызывается Данные $event
CustomEvent::EMPLOYEE_TIMESHEET_PRINT_HEADER_RENDER При выводе шапки печатного табеля employee_id, period, data
CustomEvent::EMPLOYEE_TIMESHEET_PRINT_FOOTER_RENDER При выводе подвала печатного табеля employee_id, period, data

Склад

Константа Когда вызывается Данные $event
CustomEvent::INVENTORY_TRANSFER_BEFORE Перед перемещением ТМЦ inventory_id, operation_id, data
CustomEvent::INVENTORY_TRANSFERRED После перемещения ТМЦ inventory_id, operation_id, data

Задания

Константа Когда вызывается Данные $event
CustomEvent::TASK_CARD_RENDER При выводе содержимого в карточке задания task_type_id, task_id, employee_id, data
CustomEvent::TASK_WORK_DATE_RENDER При выводе содержимого возле даты работ task_type_id, task_id, employee_id, data
CustomEvent::TASK_CREATE_BEFORE Перед созданием задания task_type_id, author_employee_id, data
CustomEvent::TASK_CREATED После создания задания task_id, task_type_id, author_employee_id, data
CustomEvent::TASK_DELETE_BEFORE Перед удалением задания task_id, data
CustomEvent::TASK_CHANGE_BEFORE Перед редактированием задания task_id, data
CustomEvent::TASK_CHANGED После редактирования задания task_id, data
CustomEvent::TASK_COMMENT_CREATE_BEFORE Перед добавлением комментария к заданию task_id, comment, data
CustomEvent::TASK_COMMENT_CREATED После добавления комментария к заданию task_id, comment_id, comment, data
CustomEvent::TASK_COMMENT_CHANGED После редактирования комментария к заданию task_id, comment_id, comment, data
CustomEvent::TASK_COMMENT_TEXT_RENDER При выводе текста комментария задания task_id, comment_id, comment, data
CustomEvent::TASK_DIVISION_ASSIGN_BEFORE Перед добавлением подразделения к заданию task_id, division_id, data
CustomEvent::TASK_DIVISION_REMOVE_BEFORE Перед исключением подразделения из задания task_id, division_id, data
CustomEvent::TASK_EMPLOYEE_ASSIGN_BEFORE Перед добавлением исполнителя к заданию task_id, employee_id, data
CustomEvent::TASK_EMPLOYEE_REMOVE_BEFORE Перед исключением исполнителя из задания task_id, employee_id, data
CustomEvent::TASK_WATCHER_DIVISION_ASSIGN_BEFORE Перед добавлением подразделения наблюдателем task_id, division_id, data
CustomEvent::TASK_WATCHER_EMPLOYEE_ASSIGN_BEFORE Перед добавлением сотрудника наблюдателем task_id, employee_id, data
CustomEvent::TASK_OBJECT_ATTACHED После добавления объекта к заданию task_id, object_type, object_id, data
CustomEvent::TASK_OBJECT_DETACHED После исключения объекта из задания task_id, object_type, object_id, data
CustomEvent::TASK_RETURNED_TO_AUTHOR После возврата задания автору task_id, data
CustomEvent::TASK_STATE_CHANGE_BEFORE Перед изменением статуса задания task_id, old_state_id, new_state_id, data
CustomEvent::TASK_STATE_CHANGED После изменения статуса задания task_id, old_state_id, new_state_id, data

Сооружения связи

Константа Когда вызывается Данные $event
CustomEvent::NODE_CARD_ADDITIONAL_DATA_RENDER При выводе дополнительного содержимого в карточке сооружения связи node_id, data
CustomEvent::NODE_CARD_PRIMARY_CONTENT_RENDER При выводе основного содержимого в карточке сооружения связи node_id, data