getCurrentUserId(); // Получаем организации пользователя через связующую таблицу $userOrgLinks = $this->getOrgUserModel()->where('user_id', $userId)->findAll(); // Нам нужно получить сами данные организаций $orgIds = array_column($userOrgLinks, 'organization_id'); $organizations = []; if (!empty($orgIds)) { $organizations = $orgModel->whereIn('id', $orgIds)->findAll(); } // Если больше 1 или 0, показываем список return $this->renderTwig('organizations/index', [ 'organizations' => $organizations, 'count' => count($organizations) ]); } public function create() { if ($this->request->getMethod() === 'POST') { $orgModel = new OrganizationModel(); $rules = [ 'name' => 'required|min_length[2]', ]; if (!$this->validate($rules)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } // Собираем реквизиты в JSON $requisites = [ 'inn' => trim($this->request->getPost('inn') ?? ''), 'ogrn' => trim($this->request->getPost('ogrn') ?? ''), 'kpp' => trim($this->request->getPost('kpp') ?? ''), 'legal_address' => trim($this->request->getPost('legal_address') ?? ''), 'actual_address' => trim($this->request->getPost('actual_address') ?? ''), 'phone' => trim($this->request->getPost('phone') ?? ''), 'email' => trim($this->request->getPost('email') ?? ''), 'website' => trim($this->request->getPost('website') ?? ''), 'bank_name' => trim($this->request->getPost('bank_name') ?? ''), 'bank_bik' => trim($this->request->getPost('bank_bik') ?? ''), 'checking_account' => trim($this->request->getPost('checking_account') ?? ''), 'correspondent_account' => trim($this->request->getPost('correspondent_account') ?? ''), ]; // Создаем организацию $orgId = $orgModel->insert([ 'owner_id' => $this->getCurrentUserId(), 'name' => $this->request->getPost('name'), 'type' => 'business', 'requisites' => json_encode($requisites), 'settings' => json_encode([]), ]); // Привязываем владельца $this->getOrgUserModel()->insert([ 'organization_id' => $orgId, 'user_id' => $this->getCurrentUserId(), 'role' => 'owner', 'status' => 'active', 'joined_at' => date('Y-m-d H:i:s'), ]); // Сразу переключаемся на неё $this->session->set('active_org_id', $orgId); $this->session->setFlashdata('success', 'Организация успешно создана!'); return redirect()->to('/'); } // GET запрос - форму создания return $this->renderTwig('organizations/create'); } /** * Дашборд управления организацией */ public function dashboard($orgId) { $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } // Получаем данные организации $orgModel = new OrganizationModel(); $organization = $orgModel->find($orgId); if (!$organization) { return $this->redirectWithError('Организация не найдена', '/organizations'); } // Получаем статистику организации $stats = [ 'users_total' => $this->getOrgUserModel()->where('organization_id', $orgId)->countAllResults(), 'users_active' => $this->getOrgUserModel()->where('organization_id', $orgId)->where('status', 'active')->countAllResults(), 'users_blocked' => $this->getOrgUserModel()->where('organization_id', $orgId)->where('status', 'blocked')->countAllResults(), ]; // Проверяем права для отображения пунктов меню $canManageUsers = $this->access->canManageUsers(); $canEditOrg = true; // Все члены организации могут видеть настройки (редактировать могут только admin+) return $this->renderTwig('organizations/dashboard', [ 'organization' => $organization, 'organization_id' => $orgId, 'stats' => $stats, 'current_role' => $membership['role'], 'can_manage_users' => $canManageUsers, 'can_edit_org' => $canEditOrg, ]); } /** * Редактирование организации */ public function edit($orgId) { // Проверяем доступ через AccessService $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } // Проверяем права на редактирование (все роли могут редактировать) $orgModel = new OrganizationModel(); $organization = $orgModel->find($orgId); if (!$organization) { return $this->redirectWithError('Организация не найдена', '/organizations'); } // Декодируем requisites для формы $requisites = json_decode($organization['requisites'] ?? '{}', true); // Если это POST запрос — обновляем данные if ($this->request->getMethod() === 'POST') { $rules = [ 'name' => 'required|min_length[2]', ]; if (!$this->validate($rules)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } // Собираем обновлённые реквизиты $newRequisites = [ 'inn' => trim($this->request->getPost('inn') ?? ''), 'ogrn' => trim($this->request->getPost('ogrn') ?? ''), 'kpp' => trim($this->request->getPost('kpp') ?? ''), 'legal_address' => trim($this->request->getPost('legal_address') ?? ''), 'actual_address' => trim($this->request->getPost('actual_address') ?? ''), 'phone' => trim($this->request->getPost('phone') ?? ''), 'email' => trim($this->request->getPost('email') ?? ''), 'website' => trim($this->request->getPost('website') ?? ''), 'bank_name' => trim($this->request->getPost('bank_name') ?? ''), 'bank_bik' => trim($this->request->getPost('bank_bik') ?? ''), 'checking_account' => trim($this->request->getPost('checking_account') ?? ''), 'correspondent_account' => trim($this->request->getPost('correspondent_account') ?? ''), ]; // Обновляем организацию $orgModel->update($orgId, [ 'name' => $this->request->getPost('name'), 'requisites' => json_encode($newRequisites), ]); $this->session->setFlashdata('success', 'Организация успешно обновлена!'); return redirect()->to('/organizations'); } // GET запрос — форма редактирования return $this->renderTwig('organizations/edit', [ 'organization' => $organization, 'requisites' => $requisites ]); } /** * Удаление организации */ public function delete($orgId) { $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } // Проверяем права: только владелец может удалить if (!$this->access->canDeleteOrganization()) { return $this->redirectWithError('Только владелец может удалить организацию', '/organizations'); } $orgModel = new OrganizationModel(); $organization = $orgModel->find($orgId); if (!$organization) { return $this->redirectWithError('Организация не найдена', '/organizations'); } // Если это POST с подтверждением — удаляем if ($this->request->getMethod() === 'POST') { // Удаляем связи с пользователями через forCurrentOrg() $this->getOrgUserModel()->forCurrentOrg()->delete(); // Мягкое удаление организации $orgModel->delete($orgId); // Если удаляли активную организацию — очищаем if ($this->session->get('active_org_id') == $orgId) { $this->session->remove('active_org_id'); } $this->session->setFlashdata('success', 'Организация "' . $organization['name'] . '" удалена'); return redirect()->to('/organizations'); } // GET запрос — страница подтверждения удаления return $this->renderTwig('organizations/delete', [ 'organization' => $organization ]); } public function switch($orgId) { $userId = $this->getCurrentUserId(); $orgId = (int) $orgId; // Проверяем доступ $membership = $this->getOrgUserModel() ->where('organization_id', $orgId) ->where('user_id', $userId) ->first(); if ($membership) { // Сбрасываем кэш AccessService при смене организации $this->access->resetCache(); $this->session->set('active_org_id', $orgId); $this->session->setFlashdata('success', 'Организация изменена'); return redirect()->to('/'); } else { $this->session->setFlashdata('error', 'Доступ запрещен'); return redirect()->to('/organizations'); } } // ======================================== // Управление пользователями организации // ======================================== /** * Список пользователей организации */ public function users($orgId) { $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } // Получаем организацию $orgModel = new OrganizationModel(); $organization = $orgModel->find($orgId); if (!$organization) { return $this->redirectWithError('Организация не найдена', '/organizations'); } // Проверяем права через единое место в AccessService (включает проверку типа организации) if (!$this->access->canManageUsers()) { return $this->redirectWithError('У вас нет прав для управления пользователями', '/organizations/' . $orgId . '/dashboard'); } // Рендерим таблицу через универсальный компонент $tableHtml = $this->renderTable($this->getUsersTableConfig($orgId)); // Получаем данные пользователей для статистики $users = $this->getOrgUserModel()->getOrganizationUsers($orgId); return $this->renderTwig('organizations/users', [ 'organization' => $organization, 'organization_id' => $orgId, 'tableHtml' => $tableHtml, 'users' => $users, 'current_user_id' => $this->getCurrentUserId(), 'can_manage_users' => $this->access->canManageUsers(), 'current_role' => $membership['role'], ]); } /** * Конфигурация таблицы пользователей */ protected function getUsersTableConfig(int $orgId): array { // Проверяем права для кнопок действий $canManage = $this->access->canManageUsers(); return [ 'id' => 'users-table', 'url' => '/organizations/' . $orgId . '/users/table', 'model' => $this->getOrgUserModel(), 'columns' => [ 'user_email' => [ 'label' => 'Пользователь', 'width' => '35%', 'type' => 'user_display', ], 'role' => [ 'label' => 'Роль', 'width' => '15%', 'type' => 'role_badge', ], 'status' => [ 'label' => 'Статус', 'width' => '15%', 'type' => 'status_badge', ], 'joined_at' => [ 'label' => 'Дата входа', 'width' => '20%', 'type' => 'datetime', 'default' => '—', ], ], 'searchable' => ['user_email', 'user_name'], 'sortable' => ['joined_at', 'role', 'status'], 'defaultSort' => 'joined_at', 'order' => 'desc', 'actions' => true, // Включаем колонку действий 'actionsConfig' => [ // Изменение роли [ 'label' => 'Изменить роль', 'url' => '/organizations/users/' . $orgId . '/role/{user_id}', 'icon' => 'fa-solid fa-user-gear', 'class' => 'btn-outline-primary btn-sm', 'type' => 'edit', ], // Блокировка [ 'label' => 'Заблокировать', 'url' => '/organizations/users/' . $orgId . '/block/{user_id}', 'icon' => 'fa-solid fa-ban', 'class' => 'btn-outline-warning btn-sm', 'type' => 'block', ], // Разблокировка [ 'label' => 'Разблокировать', 'url' => '/organizations/users/' . $orgId . '/unblock/{user_id}', 'icon' => 'fa-solid fa-check', 'class' => 'btn-outline-success btn-sm', 'type' => 'unblock', ], // Удаление [ 'label' => 'Удалить', 'url' => '/organizations/users/' . $orgId . '/remove/{user_id}', 'icon' => 'fa-solid fa-user-xmark', 'class' => 'btn-outline-danger btn-sm', 'type' => 'delete', ], ], 'can_edit' => $canManage, 'can_delete' => $canManage, 'emptyMessage' => 'В организации пока нет участников', 'emptyIcon' => 'fa-solid fa-users', 'emptyActionUrl' => '', 'emptyActionLabel'=> '', 'emptyActionIcon' => '', 'scope' => function ($builder) use ($orgId) { $builder->select('ou.*, u.email as user_email, u.name as user_name, u.avatar as user_avatar') ->from('organization_users ou') ->join('users u', 'u.id = ou.user_id', 'left') ->where('ou.organization_id', $orgId); }, // Ключи фильтров (совпадают с ключами columns) 'searchable' => ['user_email', 'user_name'], // Маппинг ключей фильтров к реальным колонкам БД 'fieldMap' => [ 'user_email' => 'u.email', 'user_name' => 'u.name', ], ]; } /** * Таблица пользователей (AJAX) */ public function usersTable($orgId) { $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->forbiddenResponse('Доступ запрещён'); } // Проверяем права через единое место в AccessService (включает проверку типа организации) if (!$this->access->canManageUsers()) { return $this->forbiddenResponse('Управление пользователями недоступно'); } return $this->table( $this->getUsersTableConfig($orgId), '/organizations/' . $orgId . '/users' // URL для редиректа ); } /** * Приглашение пользователя (AJAX) */ public function inviteUser($orgId) { $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->response->setJSON([ 'success' => false, 'message' => 'Доступ запрещен', ]); } // Проверяем права на управление пользователями if (!$this->access->canManageUsers()) { return $this->response->setJSON([ 'success' => false, 'message' => 'У вас нет прав для приглашения пользователей', ]); } if (!$this->request->isAJAX()) { return redirect()->to("/organizations/users/{$orgId}"); } $email = $this->request->getPost('email'); $role = $this->request->getPost('role'); // Валидация if (empty($email) || empty($role)) { return $this->response->setJSON([ 'success' => false, 'message' => 'Email и роль обязательны', ]); } // Проверяем валидность роли $availableRoles = $this->access->getAvailableRolesForAssignment($membership['role']); if (!in_array($role, $availableRoles)) { return $this->response->setJSON([ 'success' => false, 'message' => 'Недопустимая роль', ]); } // Создаем приглашение через сервис $invitationService = new \App\Services\InvitationService(); $result = $invitationService->createInvitation( $orgId, $email, $role, $this->getCurrentUserId() ); return $this->response->setJSON($result); } /** * Блокировка пользователя */ public function blockUser($orgId, $userId) { $orgId = (int) $orgId; $userId = (int) $userId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } if (!$this->access->canManageUsers()) { return $this->redirectWithError('У вас нет прав для блокировки', "/organizations/users/{$orgId}"); } // Нельзя заблокировать владельца $targetMembership = $this->getOrgUserModel() ->where('organization_id', $orgId) ->where('user_id', $userId) ->first(); if (!$targetMembership) { return $this->redirectWithError('Пользователь не найден', "/organizations/users/{$orgId}"); } if ($targetMembership['role'] === 'owner') { return $this->redirectWithError('Нельзя заблокировать владельца', "/organizations/users/{$orgId}"); } $this->getOrgUserModel()->blockUser($targetMembership['id']); $this->session->setFlashdata('success', 'Пользователь заблокирован'); return redirect()->to("/organizations/users/{$orgId}"); } /** * Разблокировка пользователя */ public function unblockUser($orgId, $userId) { $orgId = (int) $orgId; $userId = (int) $userId; $membership = $this->getMembership($orgId); if (!$membership || !$this->access->canManageUsers()) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } $targetMembership = $this->getOrgUserModel() ->where('organization_id', $orgId) ->where('user_id', $userId) ->first(); if (!$targetMembership) { return $this->redirectWithError('Пользователь не найден', "/organizations/users/{$orgId}"); } $this->getOrgUserModel()->unblockUser($targetMembership['id']); $this->session->setFlashdata('success', 'Пользователь разблокирован'); return redirect()->to("/organizations/users/{$orgId}"); } /** * Выход пользователя из организации */ public function leaveOrganization($orgId) { $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->redirectWithError('Вы не состоите в этой организации', '/organizations'); } // Владелец не может покинуть организацию if ($membership['role'] === 'owner') { return $this->redirectWithError('Владелец не может покинуть организацию. Передайте права другому администратору.', "/organizations/users/{$orgId}"); } // Удаляем из организации $this->getOrgUserModel()->delete($membership['id']); // Если это была активная организация - переключаем на другую if ($this->session->get('active_org_id') == $orgId) { $userId = $this->getCurrentUserId(); $otherOrgs = $this->getOrgUserModel()->where('user_id', $userId)->where('status', 'active')->findAll(); if (!empty($otherOrgs)) { $this->session->set('active_org_id', $otherOrgs[0]['organization_id']); } else { $this->session->remove('active_org_id'); } } $this->session->setFlashdata('success', 'Вы покинули организацию'); return redirect()->to('/organizations'); } /** * Повторная отправка приглашения */ public function resendInvite($orgId, $invitationId) { $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership || !$this->access->canManageUsers()) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } $invitationService = new \App\Services\InvitationService(); $result = $invitationService->resendInvitation($invitationId, $orgId); if ($result['success']) { $this->session->setFlashdata('success', 'Приглашение отправлено повторно'); } else { $this->session->setFlashdata('error', $result['message']); } return redirect()->to("/organizations/users/{$orgId}"); } /** * Отзыв приглашения */ public function cancelInvite($orgId, $invitationId) { $orgId = (int) $orgId; $membership = $this->getMembership($orgId); if (!$membership || !$this->access->canManageUsers()) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } $invitationService = new \App\Services\InvitationService(); $result = $invitationService->cancelInvitation($invitationId, $orgId); if ($result['success']) { $this->session->setFlashdata('success', 'Приглашение отозвано'); } else { $this->session->setFlashdata('error', $result['message']); } return redirect()->to("/organizations/users/{$orgId}"); } /** * Изменение роли пользователя */ public function updateUserRole($orgId, $userId) { $orgId = (int) $orgId; $userId = (int) $userId; $membership = $this->getMembership($orgId); if (!$membership) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } // Проверяем права if (!$this->access->canManageUsers()) { return $this->redirectWithError('У вас нет прав для изменения ролей', "/organizations/users/{$orgId}"); } // Нельзя изменить роль владельца $targetMembership = $this->getOrgUserModel() ->where('organization_id', $orgId) ->where('user_id', $userId) ->first(); if (!$targetMembership) { return $this->redirectWithError('Пользователь не найден в организации', "/organizations/users/{$orgId}"); } if ($targetMembership['role'] === 'owner') { return $this->redirectWithError('Нельзя изменить роль владельца', "/organizations/users/{$orgId}"); } if ($this->request->getMethod() === 'POST') { $newRole = $this->request->getPost('role'); // Проверяем валидность роли $availableRoles = $this->access->getAvailableRolesForAssignment($membership['role']); if (!in_array($newRole, $availableRoles)) { return redirect()->back()->withInput()->with('error', 'Недопустимая роль'); } $this->getOrgUserModel()->update($targetMembership['id'], [ 'role' => $newRole, ]); $this->session->setFlashdata('success', 'Роль изменена'); return redirect()->to("/organizations/users/{$orgId}"); } $userModel = new UserModel(); $user = $userModel->find($userId); return $this->renderTwig('organizations/edit_user_role', [ 'organization_id' => $orgId, 'user' => $user, 'current_role' => $targetMembership['role'], 'available_roles' => $availableRoles, ]); } /** * Удаление пользователя из организации */ public function removeUser($orgId, $userId) { $orgId = (int) $orgId; $userId = (int) $userId; $currentUserId = $this->getCurrentUserId(); $membership = $this->getMembership($orgId); if (!$membership) { return $this->redirectWithError('Доступ запрещен', '/organizations'); } // Проверяем права if (!$this->access->canManageUsers()) { return $this->redirectWithError('У вас нет прав для удаления пользователей', "/organizations/users/{$orgId}"); } // Нельзя удалить владельца $targetMembership = $this->getOrgUserModel() ->where('organization_id', $orgId) ->where('user_id', $userId) ->first(); if (!$targetMembership) { return $this->redirectWithError('Пользователь не найден', "/organizations/users/{$orgId}"); } if ($targetMembership['role'] === 'owner') { return $this->redirectWithError('Нельзя удалить владельца организации', "/organizations/users/{$orgId}"); } // Нельзя удалить самого себя (если ты admin) if ($userId === $currentUserId) { return $this->redirectWithError('Нельзя удалить себя из организации', "/organizations/users/{$orgId}"); } // Удаляем пользователя из организации $this->getOrgUserModel()->delete($targetMembership['id']); $this->session->setFlashdata('success', 'Пользователь удалён из организации'); return redirect()->to("/organizations/users/{$orgId}"); } }