organizationModel = new OrganizationModel(); $this->userModel = new UserModel(); $this->subscriptionModel = new OrganizationSubscriptionModel(); $this->subscriptionService = service('moduleSubscription'); } /** * Дашборд суперадмина */ public function index() { $stats = [ 'total_users' => $this->userModel->countAll(), 'total_orgs' => $this->organizationModel->countAll(), 'active_today' => $this->userModel->where('created_at >=', date('Y-m-d'))->countAllResults(), 'total_modules' => count($this->subscriptionService->getAllModules()), ]; $recentOrgs = $this->organizationModel ->orderBy('created_at', 'DESC') ->findAll(5); $recentUsers = $this->userModel ->orderBy('created_at', 'DESC') ->findAll(5); return $this->renderTwig('superadmin/dashboard', compact('stats', 'recentOrgs', 'recentUsers')); } // ========================================================================= // УПРАВЛЕНИЕ МОДУЛЯМИ // ========================================================================= /** * Список модулей с ценами */ public function modules() { $modules = $this->subscriptionService->getAllModules(); return $this->renderTwig('superadmin/modules/index', compact('modules')); } /** * Обновление параметров модуля */ public function updateModule() { $moduleCode = $this->request->getPost('module_code'); $config = $this->subscriptionService->getModuleConfig($moduleCode); if (!$moduleCode || !$config) { return redirect()->back()->with('error', 'Модуль не найден'); } $this->subscriptionService->saveModuleSettings( $moduleCode, $this->request->getPost('name'), $this->request->getPost('description'), (int) $this->request->getPost('price_monthly'), (int) $this->request->getPost('price_yearly'), (int) $this->request->getPost('trial_days') ); return redirect()->to('/superadmin/modules')->with('success', 'Модуль успешно обновлён'); } // ========================================================================= // УПРАВЛЕНИЕ ПОДПИСКАМИ // ========================================================================= /** * Конфигурация таблицы подписок */ protected function getSubscriptionsTableConfig(): array { return [ 'id' => 'subscriptions-table', 'url' => '/superadmin/subscriptions/table', 'model' => $this->subscriptionModel, 'columns' => [ 'id' => ['label' => 'ID', 'width' => '60px'], 'organization_name' => ['label' => 'Организация'], 'module_code' => ['label' => 'Модуль', 'width' => '100px'], 'status' => ['label' => 'Статус', 'width' => '100px'], 'expires_at' => ['label' => 'Истекает', 'width' => '120px'], 'created_at' => ['label' => 'Создана', 'width' => '120px'], ], 'searchable' => ['id', 'organization_name', 'module_code'], 'sortable' => ['id', 'created_at', 'expires_at'], 'defaultSort' => 'created_at', 'order' => 'desc', 'fieldMap' => [ 'organization_name' => 'organizations.name', 'id' => 'organization_subscriptions.id', 'module_code' => 'organization_subscriptions.module_code', 'status' => 'organization_subscriptions.status', 'expires_at' => 'organization_subscriptions.expires_at', 'created_at' => 'organization_subscriptions.created_at', ], 'scope' => function ($builder) { $builder->from('organization_subscriptions') ->select('organization_subscriptions.*, organizations.name as organization_name') ->join('organizations', 'organizations.id = organization_subscriptions.organization_id'); }, 'actions' => ['label' => 'Действия', 'width' => '100px'], 'actionsConfig' => [ [ 'label' => '', 'url' => '/superadmin/subscriptions/delete/{id}', 'icon' => 'fa-solid fa-trash', 'class' => 'btn-outline-danger', 'title' => 'Удалить', 'confirm' => 'Удалить подписку?', ], ], 'emptyMessage' => 'Подписки не найдены', 'emptyIcon' => 'fa-solid fa-credit-card', ]; } /** * Список подписок */ public function subscriptions() { $config = $this->getSubscriptionsTableConfig(); $tableHtml = $this->renderTable($config); $modules = $this->subscriptionService->getAllModules(); $organizations = $this->organizationModel->findAll(); return $this->renderTwig('superadmin/subscriptions/index', [ 'tableHtml' => $tableHtml, 'config' => $config, 'modules' => $modules, 'organizations' => $organizations, ]); } /** * AJAX таблица подписок */ public function subscriptionsTable() { return parent::table($this->getSubscriptionsTableConfig(), '/superadmin/subscriptions'); } /** * Поиск организаций для autocomplete */ public function searchOrganizations() { $query = $this->request->getGet('q') ?? ''; $limit = 20; $builder = $this->organizationModel->db()->table('organizations'); $builder->select('organizations.*, users.email as owner_email') ->join('organization_users', 'organization_users.organization_id = organizations.id AND organization_users.role = "owner"') ->join('users', 'users.id = organization_users.user_id') ->groupStart() ->like('organizations.name', $query) ->orLike('organizations.id', $query) ->orLike('users.email', $query) ->groupEnd() ->limit($limit); $results = []; foreach ($builder->get()->getResultArray() as $org) { $results[] = [ 'id' => $org['id'], 'text' => $org['name'] . ' (ID: ' . $org['id'] . ') — ' . $org['owner_email'], ]; } return $this->response->setJSON(['results' => $results]); } /** * Добавление подписки (форма) */ public function createSubscription() { $organizations = $this->organizationModel->findAll(); $modules = $this->subscriptionService->getAllModules(); return $this->renderTwig('superadmin/subscriptions/create', compact('organizations', 'modules')); } /** * Сохранение подписки */ public function storeSubscription() { $organizationId = (int) $this->request->getPost('organization_id'); $moduleCode = $this->request->getPost('module_code'); $durationDays = (int) $this->request->getPost('duration_days'); $status = $this->request->getPost('status') ?? 'active'; // Валидация $organization = $this->organizationModel->find($organizationId); if (!$organization) { return redirect()->back()->withInput()->with('error', 'Организация не найдена'); } $moduleConfig = $this->subscriptionService->getModuleConfig($moduleCode); if (!$moduleCode || !$moduleConfig) { return redirect()->back()->withInput()->with('error', 'Модуль не найден'); } $this->subscriptionService->upsertSubscription( $organizationId, $moduleCode, $status, $durationDays ); return redirect()->to('/superadmin/subscriptions')->with('success', 'Подписка создана'); } /** * Удаление подписки */ public function deleteSubscription($id) { $this->subscriptionService->deleteSubscription($id); return redirect()->to('/superadmin/subscriptions')->with('success', 'Подписка удалена'); } // ========================================================================= // УПРАВЛЕНИЕ ОРГАНИЗАЦИЯМИ // ========================================================================= /** * Конфигурация таблицы организаций */ protected function getOrganizationsTableConfig(): array { return [ 'id' => 'organizations-table', 'url' => '/superadmin/organizations/table', 'model' => $this->organizationModel, 'columns' => [ 'id' => ['label' => 'ID', 'width' => '60px'], 'name' => ['label' => 'Название'], 'owner_login' => ['label' => 'Владелец', 'width' => '150px'], 'type' => ['label' => 'Тип', 'width' => '100px'], 'user_count' => ['label' => 'Пользователей', 'width' => '100px'], 'status' => ['label' => 'Статус', 'width' => '120px'], 'created_at' => ['label' => 'Дата', 'width' => '100px'], ], 'searchable' => ['name', 'id', 'owner_login'], 'sortable' => ['id', 'name', 'created_at'], 'defaultSort' => 'created_at', 'order' => 'desc', 'scope' => function ($builder) { $builder->resetQuery(); $builder->select('organizations.*, (SELECT COUNT(*) FROM organization_users WHERE organization_users.organization_id = organizations.id AND status = "active") as user_count, owner_users.email as owner_login') ->join('organization_users as ou', 'ou.organization_id = organizations.id AND ou.role = "owner"') ->join('users as owner_users', 'owner_users.id = ou.user_id', 'left'); }, 'actions' => ['label' => 'Действия', 'width' => '140px'], 'actionsConfig' => [ [ 'label' => '', 'url' => '/superadmin/organizations/view/{id}', 'icon' => 'fa-solid fa-eye', 'class' => 'btn-outline-primary', 'title' => 'Просмотр', ], [ 'label' => '', 'url' => '/superadmin/organizations/block/{id}', 'icon' => 'fa-solid fa-ban', 'class' => 'btn-outline-warning', 'title' => 'Заблокировать', 'confirm' => 'Заблокировать организацию?', ], [ 'label' => '', 'url' => '/superadmin/organizations/delete/{id}', 'icon' => 'fa-solid fa-trash', 'class' => 'btn-outline-danger', 'title' => 'Удалить', 'confirm' => 'Удалить организацию? Это действие нельзя отменить!', ], ], 'emptyMessage' => 'Организации не найдены', 'emptyIcon' => 'bi bi-building', ]; } /** * Список организаций */ public function organizations() { $config = $this->getOrganizationsTableConfig(); $tableHtml = $this->renderTable($config); return $this->renderTwig('superadmin/organizations/index', [ 'tableHtml' => $tableHtml, 'config' => $config, ]); } /** * AJAX таблица организаций */ public function organizationsTable() { $config = $this->getOrganizationsTableConfig(); return $this->table($config); } /** * Просмотр организации с её подписками */ public function viewOrganization($id) { $organization = $this->organizationModel->find($id); if (!$organization) { throw new \CodeIgniter\Exceptions\PageNotFoundException('Организация не найдена'); } $users = $this->getOrgUserModel()->getOrganizationUsers($id); $subscriptions = $this->subscriptionService->getOrganizationSubscriptions($id); $allModules = $this->subscriptionService->getAllModules(); return $this->renderTwig('superadmin/organizations/view', compact( 'organization', 'users', 'subscriptions', 'allModules' )); } /** * Быстрое добавление подписки организации из просмотра организации */ public function addOrganizationSubscription($organizationId) { $moduleCode = $this->request->getPost('module_code'); $durationDays = (int) $this->request->getPost('duration_days'); $status = $this->request->getPost('status') ?? 'active'; if (!$moduleCode) { return redirect()->back()->with('error', 'Модуль не выбран'); } $this->subscriptionService->upsertSubscription( $organizationId, $moduleCode, $status, $durationDays ); return redirect()->to("/superadmin/organizations/view/{$organizationId}")->with('success', 'Подписка добавлена'); } /** * Удаление подписки организации */ public function removeOrganizationSubscription($organizationId, $subscriptionId) { $this->subscriptionService->deleteSubscription($subscriptionId); return redirect()->to("/superadmin/organizations/view/{$organizationId}")->with('success', 'Подписка удалена'); } /** * Блокировка организации */ public function blockOrganization($id) { $this->organizationModel->update($id, ['status' => 'blocked']); return redirect()->to("/superadmin/organizations/view/{$id}")->with('success', 'Организация заблокирована'); } /** * Разблокировка организации */ public function unblockOrganization($id) { $this->organizationModel->update($id, ['status' => 'active']); return redirect()->to("/superadmin/organizations/view/{$id}")->with('success', 'Организация разблокирована'); } /** * Удаление организации */ public function deleteOrganization($id) { $this->organizationModel->delete($id, true); return redirect()->to('/superadmin/organizations')->with('success', 'Организация удалена'); } // ========================================================================= // УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ // ========================================================================= /** * Конфигурация таблицы пользователей */ protected function getUsersTableConfig(): array { return [ 'id' => 'users-table', 'url' => '/superadmin/users/table', 'model' => $this->userModel, 'columns' => [ 'id' => ['label' => 'ID', 'width' => '60px'], 'name' => ['label' => 'Имя'], 'email' => ['label' => 'Email'], 'system_role' => ['label' => 'Роль', 'width' => '140px'], 'org_count' => ['label' => 'Организаций', 'width' => '100px'], 'status' => ['label' => 'Статус', 'width' => '120px'], 'created_at' => ['label' => 'Дата', 'width' => '100px'], ], 'searchable' => ['name', 'email', 'id'], 'sortable' => ['id', 'name', 'email', 'created_at'], 'defaultSort' => 'created_at', 'order' => 'desc', 'scope' => function ($builder) { $builder->from('users') ->select('users.*, (SELECT COUNT(*) FROM organization_users WHERE organization_users.user_id = users.id) as org_count'); }, 'actions' => ['label' => 'Действия', 'width' => '140px'], 'actionsConfig' => [ [ 'label' => '', 'url' => '/superadmin/users/block/{id}', 'icon' => 'fa-solid fa-ban', 'class' => 'btn-outline-warning', 'title' => 'Заблокировать', 'confirm' => 'Заблокировать пользователя?', ], [ 'label' => '', 'url' => '/superadmin/users/delete/{id}', 'icon' => 'fa-solid fa-trash', 'class' => 'btn-outline-danger', 'title' => 'Удалить', 'confirm' => 'Удалить пользователя? Это действие нельзя отменить!', ], ], 'emptyMessage' => 'Пользователи не найдены', 'emptyIcon' => 'bi bi-people', ]; } /** * Список пользователей */ public function users() { $config = $this->getUsersTableConfig(); $tableHtml = $this->renderTable($config); return $this->renderTwig('superadmin/users/index', [ 'tableHtml' => $tableHtml, 'config' => $config, ]); } /** * AJAX таблица пользователей */ public function usersTable() { $config = $this->getUsersTableConfig(); return $this->table($config); } /** * Изменение системной роли пользователя */ public function updateUserRole($id) { $newRole = $this->request->getPost('system_role'); $allowedRoles = ['user', 'admin', 'superadmin']; if (!in_array($newRole, $allowedRoles)) { return redirect()->back()->with('error', 'Недопустимая роль'); } $this->userModel->update($id, ['system_role' => $newRole]); return redirect()->back()->with('success', 'Роль пользователя обновлена'); } /** * Блокировка пользователя */ public function blockUser($id) { $this->userModel->update($id, ['status' => 'blocked']); return redirect()->back()->with('success', 'Пользователь заблокирован'); } /** * Разблокировка пользователя */ public function unblockUser($id) { $this->userModel->update($id, ['status' => 'active']); return redirect()->back()->with('success', 'Пользователь разблокирован'); } /** * Удаление пользователя */ public function deleteUser($id) { $this->userModel->delete($id, true); return redirect()->to('/superadmin/users')->with('success', 'Пользователь удалён'); } // ========================================================================= // СТАТИСТИКА // ========================================================================= /** * Статистика использования */ public function statistics() { $dailyStats = []; for ($i = 29; $i >= 0; $i--) { $date = date('Y-m-d', strtotime("-{$i} days")); $dailyStats[] = [ 'date' => $date, 'users' => $this->userModel->where('DATE(created_at)', $date)->countAllResults(), 'orgs' => $this->organizationModel->where('DATE(created_at)', $date)->countAllResults(), ]; } $moduleStats = $this->subscriptionService->getModuleStats(); return $this->renderTwig('superadmin/statistics', compact('dailyStats', 'moduleStats')); } }