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