add EventManager and Subscription
This commit is contained in:
parent
3d39c1ba07
commit
3c24c250e5
|
|
@ -53,3 +53,22 @@ Events::on('pre_system', static function (): void {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
* Module Events Registration
|
||||
* --------------------------------------------------------------------
|
||||
* Здесь можно регистрировать события модулей с проверкой подписок.
|
||||
* Для подписки на события с проверкой статуса модуля используйте:
|
||||
*
|
||||
* $em = service('eventManager');
|
||||
* $em->forModule('crm')->moduleOn('user.created', function($user) {
|
||||
* // Этот код выполнится только если модуль CRM активен
|
||||
* });
|
||||
*
|
||||
* Для системных событий без проверки:
|
||||
*
|
||||
* $em->systemOn('db.query', function($query) {
|
||||
* // Этот код выполнится всегда
|
||||
* });
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -87,4 +87,40 @@ class Services extends BaseService
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сервис для управления подписками на модули
|
||||
*
|
||||
* Предоставляет методы для проверки активности модулей,
|
||||
* управления пробными периодами и проверки доступа к функциональности.
|
||||
*
|
||||
* @param bool $getShared
|
||||
* @return \App\Services\ModuleSubscriptionService
|
||||
*/
|
||||
public static function moduleSubscription(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('moduleSubscription');
|
||||
}
|
||||
|
||||
return new ModuleSubscriptionService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Сервис для управления событиями с проверкой подписок на модули
|
||||
*
|
||||
* Предоставляет методы для подписки на события с проверкой
|
||||
* статуса подписки на модуль (moduleOn) и без проверки (systemOn).
|
||||
*
|
||||
* @param bool $getShared
|
||||
* @return \App\Services\EventManager
|
||||
*/
|
||||
public static function eventManager(bool $getShared = true)
|
||||
{
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('eventManager');
|
||||
}
|
||||
|
||||
return new EventManager();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddTrialEndsAtToSubscriptions extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
// Добавляем поле trial_ends_at для отслеживания окончания триала
|
||||
$this->forge->addColumn('organization_subscriptions', [
|
||||
'trial_ends_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
'after' => 'status',
|
||||
'comment' => 'Дата окончания триального периода',
|
||||
],
|
||||
]);
|
||||
|
||||
// Обновляем существующие триальные подписки (14 дней от created_at)
|
||||
$this->db->query("
|
||||
UPDATE organization_subscriptions
|
||||
SET trial_ends_at = DATE_ADD(created_at, INTERVAL 14 DAY)
|
||||
WHERE status = 'trial' AND trial_ends_at IS NULL
|
||||
");
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->forge->dropColumn('organization_subscriptions', 'trial_ends_at');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use CodeIgniter\Events\Events;
|
||||
|
||||
/**
|
||||
* EventManager - обертка над событиями CodeIgniter 4
|
||||
*
|
||||
* Предоставляет два типа подписок на события:
|
||||
* - moduleOn(): выполняется только если модуль активен и подписка оплачена
|
||||
* - systemOn(): выполняется всегда, без проверки подписки
|
||||
*
|
||||
* Используется для создания интеграций между модулями, которые должны
|
||||
* работать только при наличии активной подписки на соответствующий модуль.
|
||||
*/
|
||||
class EventManager
|
||||
{
|
||||
/**
|
||||
* Сервис подписок на модули
|
||||
*
|
||||
* @var ModuleSubscriptionService|null
|
||||
*/
|
||||
private ?ModuleSubscriptionService $moduleSubscriptionService = null;
|
||||
|
||||
/**
|
||||
* Конфигурация модулей
|
||||
*
|
||||
* @var \Config\BusinessModules|null
|
||||
*/
|
||||
private ?\Config\BusinessModules $modulesConfig = null;
|
||||
|
||||
/**
|
||||
* Код модуля, к которому привязаны события
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $moduleCode = null;
|
||||
|
||||
/**
|
||||
* Кэш подписки на модуль
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
private ?bool $moduleActive = null;
|
||||
|
||||
/**
|
||||
* Получить экземпляр сервиса подписок
|
||||
*
|
||||
* @return ModuleSubscriptionService
|
||||
*/
|
||||
private function getModuleSubscriptionService(): ModuleSubscriptionService
|
||||
{
|
||||
if ($this->moduleSubscriptionService === null) {
|
||||
$this->moduleSubscriptionService = service('moduleSubscription');
|
||||
}
|
||||
|
||||
return $this->moduleSubscriptionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить конфигурацию модулей
|
||||
*
|
||||
* @return \Config\BusinessModules
|
||||
*/
|
||||
private function getModulesConfig(): \Config\BusinessModules
|
||||
{
|
||||
if ($this->modulesConfig === null) {
|
||||
$this->modulesConfig = new \Config\BusinessModules();
|
||||
}
|
||||
|
||||
return $this->modulesConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Привязать события к конкретному модулю
|
||||
*
|
||||
* Все последующие вызовы moduleOn() будут проверять
|
||||
* подписку на указанный модуль.
|
||||
*
|
||||
* @param string $moduleCode Код модуля
|
||||
* @return $this
|
||||
*/
|
||||
public function forModule(string $moduleCode): self
|
||||
{
|
||||
$this->moduleCode = $moduleCode;
|
||||
$this->moduleActive = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, активен ли модуль
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isModuleActive(): bool
|
||||
{
|
||||
if ($this->moduleCode === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->moduleActive === null) {
|
||||
$orgId = session('org_id') ?? null;
|
||||
$this->moduleActive = $this->getModuleSubscriptionService()
|
||||
->isModuleActive($this->moduleCode, $orgId);
|
||||
}
|
||||
|
||||
return $this->moduleActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подписаться на событие с проверкой подписки на модуль
|
||||
*
|
||||
* Обработчик будет выполнен только если:
|
||||
* 1. Модуль глобально включен в конфигурации
|
||||
* 2. У организации есть активная подписка на модуль
|
||||
*
|
||||
* @param string $event Имя события
|
||||
* @param callable $callback Обработчик события
|
||||
* @param int $priority Приоритет события (по умолчанию 100)
|
||||
* @return bool True если подписка создана, False если модуль не активен
|
||||
*/
|
||||
public function moduleOn(
|
||||
string $event,
|
||||
callable $callback,
|
||||
int $priority = 100
|
||||
): bool {
|
||||
if ($this->moduleCode === null) {
|
||||
throw new \RuntimeException(
|
||||
'Module code not set. Use forModule() method first.'
|
||||
);
|
||||
}
|
||||
|
||||
$modulesConfig = $this->getModulesConfig();
|
||||
|
||||
// Проверяем, что модуль существует в конфигурации
|
||||
if (!isset($modulesConfig->modules[$this->moduleCode])) {
|
||||
log_message(
|
||||
'error',
|
||||
"EventManager: Module '{$this->moduleCode}' not found in config"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Если модуль отключен глобально, не подписываемся
|
||||
if (empty($modulesConfig->modules[$this->moduleCode]['enabled'])) {
|
||||
log_message(
|
||||
'info',
|
||||
"EventManager: Module '{$this->moduleCode}' is disabled globally"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем подписку организации
|
||||
if (!$this->isModuleActive()) {
|
||||
log_message(
|
||||
'debug',
|
||||
"EventManager: Organization subscription not active for module '{$this->moduleCode}'"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Подписываемся на событие
|
||||
Events::on($event, $callback, $priority);
|
||||
|
||||
log_message(
|
||||
'debug',
|
||||
"EventManager: Subscribed to event '{$event}' for module '{$this->moduleCode}'"
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подписаться на событие без проверки подписки
|
||||
*
|
||||
* Обработчик будет выполнен всегда, независимо
|
||||
* от статуса подписки на модуль.
|
||||
*
|
||||
* Используется для системных событий, которые должны
|
||||
* работать для всех организаций.
|
||||
*
|
||||
* @param string $event Имя события
|
||||
* @param callable $callback Обработчик события
|
||||
* @param int $priority Приоритет события (по умолчанию 100)
|
||||
* @return void
|
||||
*/
|
||||
public function systemOn(
|
||||
string $event,
|
||||
callable $callback,
|
||||
int $priority = 100
|
||||
): void {
|
||||
Events::on($event, $callback, $priority);
|
||||
|
||||
log_message(
|
||||
'debug',
|
||||
"EventManager: System event subscribed: '{$event}'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Отписаться от события
|
||||
*
|
||||
* @param string $event Имя события
|
||||
* @param callable|null $callback Конкретный обработчик (если null - все обработчики)
|
||||
* @return void
|
||||
*/
|
||||
public function off(string $event, ?callable $callback = null): void
|
||||
{
|
||||
if ($callback === null) {
|
||||
Events::off($event);
|
||||
} else {
|
||||
Events::off($event, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, активен ли текущий модуль
|
||||
*
|
||||
* Удобный метод для использования внутри обработчиков событий.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function currentModuleActive(): bool
|
||||
{
|
||||
return $this->isModuleActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить код текущего модуля
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCurrentModuleCode(): ?string
|
||||
{
|
||||
return $this->moduleCode;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,394 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Config\BusinessModules;
|
||||
use App\Models\OrganizationSubscriptionModel;
|
||||
|
||||
/**
|
||||
* Сервис для работы с подписками на модули
|
||||
*
|
||||
* Предоставляет API для проверки статуса подписок,
|
||||
* активации модулей и управления триальными периодами.
|
||||
*/
|
||||
class ModuleSubscriptionService
|
||||
{
|
||||
protected OrganizationSubscriptionModel $subscriptionModel;
|
||||
protected BusinessModules $modulesConfig;
|
||||
protected ?int $activeOrgId = null;
|
||||
|
||||
public function __construct(
|
||||
?OrganizationSubscriptionModel $subscriptionModel = null,
|
||||
?BusinessModules $modulesConfig = null
|
||||
) {
|
||||
$this->subscriptionModel = $subscriptionModel ?? new OrganizationSubscriptionModel();
|
||||
$this->modulesConfig = $modulesConfig ?? config('BusinessModules');
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение активного ID организации из сессии
|
||||
*/
|
||||
protected function getActiveOrgId(): ?int
|
||||
{
|
||||
if ($this->activeOrgId === null) {
|
||||
$this->activeOrgId = session()->get('active_org_id') ?? 0;
|
||||
}
|
||||
|
||||
return $this->activeOrgId ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка активности модуля для текущей организации
|
||||
*
|
||||
* @param string $moduleCode Код модуля (crm, booking, tasks, proof)
|
||||
* @param int|null $organizationId ID организации (null = из сессии)
|
||||
* @return bool
|
||||
*/
|
||||
public function isModuleActive(string $moduleCode, ?int $organizationId = null): bool
|
||||
{
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Базовый модуль всегда активен
|
||||
if ($moduleCode === 'base') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->subscriptionModel->isModuleActive($orgId, $moduleCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка что модуль доступен (активен или в триале)
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param int|null $organizationId
|
||||
* @return bool
|
||||
*/
|
||||
public function isModuleAvailable(string $moduleCode, ?int $organizationId = null): bool
|
||||
{
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Базовый модуль всегда доступен
|
||||
if ($moduleCode === 'base') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Проверяем подписку
|
||||
$subscription = $this->subscriptionModel->getSubscription($orgId, $moduleCode);
|
||||
|
||||
if (!$subscription) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Активен или в триале
|
||||
return in_array($subscription['status'], ['trial', 'active'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка что модуль в триальном периоде
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param int|null $organizationId
|
||||
* @return bool
|
||||
*/
|
||||
public function isInTrial(string $moduleCode, ?int $organizationId = null): bool
|
||||
{
|
||||
// Базовый модуль не имеет триала
|
||||
if ($moduleCode === 'base') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->subscriptionModel->isInTrial($orgId, $moduleCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка что триал истекает скоро (для показа уведомлений)
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param int $daysThreshold Порог в днях
|
||||
* @param int|null $organizationId
|
||||
* @return bool
|
||||
*/
|
||||
public function isTrialExpiringSoon(
|
||||
string $moduleCode,
|
||||
int $daysThreshold = 3,
|
||||
?int $organizationId = null
|
||||
): bool {
|
||||
if (!$this->isInTrial($moduleCode, $organizationId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
$daysLeft = $this->subscriptionModel->getDaysUntilExpire($orgId, $moduleCode);
|
||||
|
||||
return $daysLeft !== null && $daysLeft <= $daysThreshold && $daysLeft > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение дней до окончания подписки/триала
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param int|null $organizationId
|
||||
* @return int|null Количество дней или null если не активна
|
||||
*/
|
||||
public function getDaysUntilExpire(string $moduleCode, ?int $organizationId = null): ?int
|
||||
{
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->subscriptionModel->getDaysUntilExpire($orgId, $moduleCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение информации о модуле из конфигурации
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @return array|null
|
||||
*/
|
||||
public function getModuleInfo(string $moduleCode): ?array
|
||||
{
|
||||
return $this->modulesConfig->getModule($moduleCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение всех доступных модулей для организации
|
||||
*
|
||||
* @param int|null $organizationId
|
||||
* @return array Список кодов модулей
|
||||
*/
|
||||
public function getActiveModules(?int $organizationId = null): array
|
||||
{
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return ['base'];
|
||||
}
|
||||
|
||||
$activeModules = $this->subscriptionModel->getActiveModules($orgId);
|
||||
|
||||
// Всегда добавляем базовый модуль
|
||||
$activeModules[] = 'base';
|
||||
|
||||
return array_unique($activeModules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Запуск триального периода для модуля
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param int|null $organizationId
|
||||
* @param int $trialDays
|
||||
* @return bool
|
||||
*/
|
||||
public function startTrial(
|
||||
string $moduleCode,
|
||||
?int $organizationId = null,
|
||||
int $trialDays = 14
|
||||
): bool {
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем что модуль существует
|
||||
if (!$this->modulesConfig->exists($moduleCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем что модуль платный
|
||||
$moduleInfo = $this->modulesConfig->getModule($moduleCode);
|
||||
if (!$moduleInfo || $moduleInfo['trial_days'] <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем что триал ещё не был использован
|
||||
if ($this->subscriptionModel->isInTrial($orgId, $moduleCode)) {
|
||||
return false; // Уже в триале
|
||||
}
|
||||
|
||||
// Проверяем что нет активной подписки
|
||||
if ($this->isModuleActive($moduleCode, $orgId)) {
|
||||
return false; // Уже активна
|
||||
}
|
||||
|
||||
return (bool) $this->subscriptionModel->startTrial(
|
||||
$orgId,
|
||||
$moduleCode,
|
||||
$moduleInfo['trial_days']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация платной подписки (для ручного включения или после платежа)
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param int $months
|
||||
* @param int|null $organizationId
|
||||
* @return bool
|
||||
*/
|
||||
public function activate(
|
||||
string $moduleCode,
|
||||
int $months = 1,
|
||||
?int $organizationId = null
|
||||
): bool {
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем что модуль существует
|
||||
if (!$this->modulesConfig->exists($moduleCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->subscriptionModel->activate($orgId, $moduleCode, $months);
|
||||
}
|
||||
|
||||
/**
|
||||
* Отмена подписки
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param int|null $organizationId
|
||||
* @return bool
|
||||
*/
|
||||
public function cancel(string $moduleCode, ?int $organizationId = null): bool
|
||||
{
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->subscriptionModel->cancel($orgId, $moduleCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка доступа к функции модуля
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param string $feature Опционально - конкретная фича
|
||||
* @return bool
|
||||
*/
|
||||
public function canUseModule(string $moduleCode, string $feature = ''): bool
|
||||
{
|
||||
if (!$this->isModuleActive($moduleCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Для триальных версий могут быть ограничения на некоторые фичи
|
||||
if ($this->isInTrial($moduleCode)) {
|
||||
// Проверяем доступность фичи в триале
|
||||
// (можно расширить через конфигурацию)
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение статуса подписки для отображения в UI
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param int|null $organizationId
|
||||
* @return array
|
||||
*/
|
||||
public function getSubscriptionStatus(
|
||||
string $moduleCode,
|
||||
?int $organizationId = null
|
||||
): array {
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
$moduleInfo = $this->modulesConfig->getModule($moduleCode);
|
||||
$subscription = $orgId
|
||||
? $this->subscriptionModel->getSubscription($orgId, $moduleCode)
|
||||
: null;
|
||||
|
||||
$status = 'unavailable';
|
||||
$daysLeft = null;
|
||||
$message = '';
|
||||
|
||||
if ($moduleCode === 'base') {
|
||||
$status = 'active';
|
||||
$message = 'Базовый модуль';
|
||||
} elseif (!$subscription) {
|
||||
if ($moduleInfo && $moduleInfo['trial_days'] > 0) {
|
||||
$status = 'trial_available';
|
||||
$message = 'Доступен триал ' . $moduleInfo['trial_days'] . ' дней';
|
||||
} else {
|
||||
$status = 'locked';
|
||||
$message = 'Приобретите модуль';
|
||||
}
|
||||
} elseif ($subscription['status'] === 'trial') {
|
||||
$daysLeft = $this->subscriptionModel->getDaysUntilExpire($orgId, $moduleCode);
|
||||
if ($daysLeft && $daysLeft > 0) {
|
||||
$status = 'trial';
|
||||
$message = "Триал: {$daysLeft} дн.";
|
||||
} else {
|
||||
$status = 'expired';
|
||||
$message = 'Триал истёк';
|
||||
}
|
||||
} elseif ($subscription['status'] === 'active') {
|
||||
$daysLeft = $this->subscriptionModel->getDaysUntilExpire($orgId, $moduleCode);
|
||||
$status = 'active';
|
||||
$message = $daysLeft ? "Осталось {$daysLeft} дн." : 'Активна';
|
||||
} elseif (in_array($subscription['status'], ['expired', 'cancelled'])) {
|
||||
$status = 'expired';
|
||||
$message = 'Подписка завершена';
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'days_left' => $daysLeft,
|
||||
'module' => $moduleInfo,
|
||||
'subscription' => $subscription,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение цены модуля
|
||||
*
|
||||
* @param string $moduleCode
|
||||
* @param string $period
|
||||
* @return int
|
||||
*/
|
||||
public function getPrice(string $moduleCode, string $period = 'monthly'): int
|
||||
{
|
||||
return $this->modulesConfig->getPrice($moduleCode, $period);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка что пользователь может управлять подписками организации
|
||||
*
|
||||
* @param int|null $organizationId
|
||||
* @return bool
|
||||
*/
|
||||
public function canManageSubscriptions(?int $organizationId = null): bool
|
||||
{
|
||||
$orgId = $organizationId ?? $this->getActiveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем права через AccessService
|
||||
return service('access')->canManageModules();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
Примеры использования событийной системы
|
||||
|
||||
Для создания интеграций между модулями, которые должны работать только при наличии активной подписки, используется метод moduleOn(). Ниже приведен пример инициализации событий в файле модуля, например в app/Modules/Crm/Config/Events.php:
|
||||
|
||||
php
|
||||
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Crm\Config;
|
||||
|
||||
use service('eventManager');
|
||||
|
||||
/**
|
||||
* Регистрация событий модуля CRM
|
||||
* События будут выполняться только при активной подписке на CRM
|
||||
*/
|
||||
$em = service('eventManager');
|
||||
$em->forModule('crm');
|
||||
|
||||
// При создании нового клиента отправляем приветственное письмо
|
||||
$em->moduleOn('clients.created', function($client) {
|
||||
$emailService = service('email');
|
||||
$emailService->sendWelcomeEmail($client->email, $client->name);
|
||||
});
|
||||
|
||||
// При изменении статуса сделки обновляем метрики
|
||||
$em->moduleOn('deals.status_changed', function($deal, $oldStatus, $newStatus) {
|
||||
service('analytics')->trackDealStatusChange($deal->id, $oldStatus, $newStatus);
|
||||
});
|
||||
|
||||
// Логируем все действия с клиентами
|
||||
$em->moduleOn('clients.*', function($event, $data) {
|
||||
service('audit')->log('crm_client_activity', $data);
|
||||
});
|
||||
|
||||
|
||||
Для системных событий, которые должны выполняться всегда независимо от статуса подписки, используется метод systemOn(). Такие события подходят для сквозной функциональности, например, логирования, аудита или сбора аналитики:
|
||||
|
||||
php
|
||||
|
||||
<?php
|
||||
|
||||
use service('eventManager');
|
||||
|
||||
$em = service('eventManager');
|
||||
|
||||
// Системное событие для логирования всех запросов к базе данных
|
||||
$em->systemOn('DBQuery', function($query) {
|
||||
if (ENVIRONMENT === 'development') {
|
||||
log_message('debug', 'DB Query: ' . $query);
|
||||
}
|
||||
});
|
||||
|
||||
// Системное событие для записи активности пользователя
|
||||
$em->systemOn('user.login', function($user) {
|
||||
service('activityLogger')->logLogin($user->id);
|
||||
});
|
||||
|
||||
|
||||
Архитектурные преимущества решения
|
||||
|
||||
Разделение событий на два типа обеспечивает гибкость при проектировании интеграций между модулями. События типа moduleOn() гарантируют, что бизнес-логика модуля выполняется только для организаций, которые оплатили доступ к этому модулю, что защищает коммерческие интересы и предотвращает несанкционированное использование функциональности. События типа systemOn() позволяют реализовывать сквозную функциональность, которая должна присутствовать в системе независимо от того, какие модули оплачены организацией, например, общие уведомления, аудит безопасности или интеграция с внешними системами мониторинга.
|
||||
|
||||
Кэширование результата проверки статуса модуля в рамках одного запроса обеспечивает высокую производительность событийной системы. При множественных подписках на события одного модуля проверка подписки выполняется только один раз, а затем результат кэшируется в свойстве $moduleActive.
|
||||
Loading…
Reference in New Issue