bp/app/Controllers/Organizations.php

776 lines
31 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Controllers;
use App\Models\OrganizationModel;
use App\Models\UserModel;
use App\Services\AccessService;
class Organizations extends BaseController
{
public function index()
{
$orgModel = new OrganizationModel();
$userId = $this->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', 'Организация изменена');
$referer = $this->request->getHeader('Referer');
if ($referer && strpos($referer->getValue(), '/organizations/switch') === false) {
return redirect()->to($referer->getValue());
}
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) {
if ($this->request->isAJAX()) {
return $this->response->setJSON([
'success' => false,
'message' => 'Вы не состоите в этой организации',
]);
}
return $this->redirectWithError('Вы не состоите в этой организации', '/organizations');
}
// Владелец не может покинуть организацию
if ($membership['role'] === 'owner') {
if ($this->request->isAJAX()) {
return $this->response->setJSON([
'success' => false,
'message' => 'Владелец не может покинуть организацию. Передайте права другому администратору.',
]);
}
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');
}
}
// Сбрасываем кэш AccessService
$this->access->resetCache();
if ($this->request->isAJAX()) {
return $this->response->setJSON([
'success' => true,
'message' => 'Вы покинули организацию',
]);
}
$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}");
}
}