clientModel = new ClientModel(); } public function index() { // Проверка права на просмотр if (!$this->access->canView('clients')) { return $this->forbiddenResponse('У вас нет прав для просмотра клиентов'); } $config = $this->getTableConfig(); return $this->renderTwig('@Clients/index', [ 'title' => 'Клиенты', 'tableHtml' => $this->renderTable($config), 'can_create' => $this->access->canCreate('clients'), 'can_edit' => $this->access->canEdit('clients'), 'can_delete' => $this->access->canDelete('clients'), ]); } /** * Конфигурация таблицы клиентов */ protected function getTableConfig(): array { return [ 'id' => 'clients-table', 'url' => '/clients/table', 'model' => $this->clientModel, 'columns' => [ 'name' => ['label' => 'Имя / Название', 'width' => '40%'], 'email' => ['label' => 'Email', 'width' => '25%'], 'phone' => ['label' => 'Телефон', 'width' => '20%'], ], 'searchable' => ['name', 'email', 'phone'], 'sortable' => ['name', 'email', 'phone', 'created_at'], 'defaultSort' => 'name', 'order' => 'asc', 'actions' => ['label' => 'Действия', 'width' => '15%'], 'actionsConfig' => [ [ 'label' => '', 'url' => '/clients/edit/{id}', 'icon' => 'fa-solid fa-pen', 'class' => 'btn-outline-primary', 'title' => 'Редактировать', 'type' => 'edit', ], [ 'label' => '', 'url' => '/clients/delete/{id}', 'icon' => 'fa-solid fa-trash', 'class' => 'btn-outline-danger', 'title' => 'Удалить', 'type' => 'delete', ] ], 'onRowClick' => 'viewClient', // Функция для открытия карточки клиента 'emptyMessage' => 'Клиентов пока нет', 'emptyIcon' => 'fa-solid fa-users', 'emptyActionUrl' => base_url('/clients/new'), 'emptyActionLabel'=> 'Добавить клиента', 'emptyActionIcon' => 'fa-solid fa-plus', 'can_edit' => $this->access->canEdit('clients'), 'can_delete' => $this->access->canDelete('clients'), ]; } public function table(?array $config = null, ?string $pageUrl = null) { // Проверка права на просмотр if (!$this->access->canView('clients')) { return $this->forbiddenResponse('У вас нет прав для просмотра клиентов'); } return parent::table($config, '/clients'); } public function new() { // Проверка права на создание if (!$this->access->canCreate('clients')) { return $this->forbiddenResponse('У вас нет прав для создания клиентов'); } $data = [ 'title' => 'Добавить клиента', 'client' => null, ]; return $this->renderTwig('@Clients/form', $data); } public function create() { // Проверка права на создание if (!$this->access->canCreate('clients')) { return $this->forbiddenResponse('У вас нет прав для создания клиентов'); } $organizationId = session()->get('active_org_id'); $rules = [ 'name' => 'required|min_length[2]|max_length[255]', 'email' => 'permit_empty|valid_email', 'phone' => 'permit_empty|max_length[50]', ]; if (!$this->validate($rules)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } $this->clientModel->insert([ 'organization_id' => $organizationId, 'name' => $this->request->getPost('name'), 'email' => $this->request->getPost('email') ?? null, 'phone' => $this->request->getPost('phone') ?? null, 'notes' => $this->request->getPost('notes') ?? null, ]); if ($this->clientModel->errors()) { return redirect()->back()->withInput()->with('error', 'Ошибка при создании клиента'); } session()->setFlashdata('success', 'Клиент успешно добавлен'); return redirect()->to('/clients'); } public function edit($id) { // Проверка права на редактирование if (!$this->access->canEdit('clients')) { return $this->forbiddenResponse('У вас нет прав для редактирования клиентов'); } $client = $this->clientModel->forCurrentOrg()->find($id); if (!$client) { throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Клиент не найден'); } $data = [ 'title' => 'Редактировать клиента', 'client' => $client, ]; return $this->renderTwig('@Clients/form', $data); } public function update($id) { // Проверка права на редактирование if (!$this->access->canEdit('clients')) { return $this->forbiddenResponse('У вас нет прав для редактирования клиентов'); } // Проверяем что клиент принадлежит организации через forCurrentOrg() $client = $this->clientModel->forCurrentOrg()->find($id); if (!$client) { throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Клиент не найден'); } $rules = [ 'name' => 'required|min_length[2]|max_length[255]', 'email' => 'permit_empty|valid_email', 'phone' => 'permit_empty|max_length[50]', ]; if (!$this->validate($rules)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } $this->clientModel->update($id, [ 'name' => $this->request->getPost('name'), 'email' => $this->request->getPost('email') ?? null, 'phone' => $this->request->getPost('phone') ?? null, 'notes' => $this->request->getPost('notes') ?? null, ]); if ($this->clientModel->errors()) { return redirect()->back()->withInput()->with('error', 'Ошибка при обновлении клиента'); } session()->setFlashdata('success', 'Клиент успешно обновлён'); return redirect()->to('/clients'); } public function delete($id) { // Проверка права на удаление if (!$this->access->canDelete('clients')) { return $this->forbiddenResponse('У вас нет прав для удаления клиентов'); } // Проверяем что клиент принадлежит организации через forCurrentOrg() $client = $this->clientModel->forCurrentOrg()->find($id); if (!$client) { throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Клиент не найден'); } $this->clientModel->delete($id); session()->setFlashdata('success', 'Клиент удалён'); return redirect()->to('/clients'); } /** * Возврат ответа "Доступ запрещён" * * @param string $message * @return ResponseInterface */ protected function forbiddenResponse(string $message = 'Доступ запрещён') { if ($this->request->isAJAX()) { return service('response') ->setStatusCode(403) ->setJSON(['error' => $message]); } session()->setFlashdata('error', $message); return redirect()->to('/'); } // ======================================== // API: Просмотр, Экспорт, Импорт // ======================================== /** * API: Получение данных клиента для модального окна */ public function view($id) { if (!$this->access->canView('clients')) { return $this->response->setJSON([ 'success' => false, 'error' => 'Доступ запрещён' ])->setStatusCode(403); } $client = $this->clientModel->forCurrentOrg()->find($id); if (!$client) { return $this->response->setJSON([ 'success' => false, 'error' => 'Клиент не найден' ])->setStatusCode(404); } // Формируем данные для ответа $data = [ 'id' => $client['id'], 'name' => $client['name'], 'email' => $client['email'] ?? '', 'phone' => $client['phone'] ?? '', 'notes' => $client['notes'] ?? '', 'status' => $client['status'] ?? 'active', 'created_at' => $client['created_at'] ? date('d.m.Y H:i', strtotime($client['created_at'])) : '', 'updated_at' => $client['updated_at'] ? date('d.m.Y H:i', strtotime($client['updated_at'])) : '', ]; return $this->response->setJSON([ 'success' => true, 'data' => $data ]); } /** * Экспорт клиентов */ public function export() { if (!$this->access->canView('clients')) { return $this->forbiddenResponse('Доступ запрещён'); } $format = $this->request->getGet('format') ?? 'csv'; // Получаем всех клиентов организации $clients = $this->clientModel->forCurrentOrg()->findAll(); // Устанавливаем заголовки для скачивания if ($format === 'xlsx') { $filename = 'clients_' . date('Y-m-d') . '.xlsx'; $this->exportToXlsx($clients, $filename); } else { $filename = 'clients_' . date('Y-m-d') . '.csv'; $this->exportToCsv($clients, $filename); } } /** * Экспорт в CSV */ protected function exportToCsv(array $clients, string $filename) { header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '"'); $output = fopen('php://output', 'w'); // Заголовок CSV fputcsv($output, ['ID', 'Имя', 'Email', 'Телефон', 'Статус', 'Создан', 'Обновлён'], ';'); // Данные foreach ($clients as $client) { fputcsv($output, [ $client['id'], $client['name'], $client['email'] ?? '', $client['phone'] ?? '', $client['status'] ?? 'active', $client['created_at'] ?? '', $client['updated_at'] ?? '', ], ';'); } fclose($output); exit; } /** * Экспорт в XLSX (упрощённый через HTML table) */ protected function exportToXlsx(array $clients, string $filename) { // Для упрощения используем HTML table с правильными заголовками Excel // В продакшене рекомендуется использовать PhpSpreadsheet header('Content-Type: application/vnd.ms-excel'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Cache-Control: max-age=0'); echo '
| ID | Имя | Телефон | Статус | Создан | Обновлён | |
|---|---|---|---|---|---|---|
| ' . $client['id'] . ' | '; echo '' . htmlspecialchars($client['name']) . ' | '; echo '' . htmlspecialchars($client['email'] ?? '') . ' | '; echo '' . htmlspecialchars($client['phone'] ?? '') . ' | '; echo '' . ($client['status'] ?? 'active') . ' | '; echo '' . ($client['created_at'] ?? '') . ' | '; echo '' . ($client['updated_at'] ?? '') . ' | '; echo '