contactModel = new ContactModel(); $this->clientModel = new ClientModel(); } /** * Конфигурация таблицы контактов */ protected function getContactsTableConfig(): array { $organizationId = $this->requireActiveOrg(); return [ 'id' => 'contacts-table', 'url' => '/crm/contacts/table', 'model' => $this->contactModel, 'columns' => [ 'id' => ['label' => 'ID', 'width' => '60px'], 'name' => ['label' => 'Имя'], 'email' => ['label' => 'Email', 'width' => '180px'], 'phone' => ['label' => 'Телефон', 'width' => '140px'], 'position' => ['label' => 'Должность', 'width' => '150px'], 'customer_name' => ['label' => 'Клиент'], 'created_at' => ['label' => 'Дата', 'width' => '100px'], ], 'searchable' => ['name', 'email', 'phone', 'position', 'customer_name'], 'sortable' => ['id', 'name', 'created_at'], 'defaultSort' => 'created_at', 'order' => 'desc', 'fieldMap' => [ 'customer_name' => 'customers.name', 'name' => 'contacts.name', 'email' => 'contacts.email', 'phone' => 'contacts.phone', 'position' => 'contacts.position', 'created_at' => 'contacts.created_at', 'id' => 'contacts.id', ], 'scope' => function($builder) use ($organizationId) { $builder->from('contacts') ->select('contacts.id, contacts.name, contacts.email, contacts.phone, contacts.position, contacts.created_at, contacts.deleted_at, customers.name as customer_name') ->join('organizations_clients customers', 'customers.id = contacts.customer_id', 'left') ->where('contacts.organization_id', $organizationId) ->where('contacts.deleted_at', null); }, 'actions' => ['label' => 'Действия', 'width' => '120px'], 'actionsConfig' => [ [ 'label' => '', 'url' => '/crm/contacts/{id}/edit', 'icon' => 'fa-solid fa-pen', 'class' => 'btn-outline-primary', 'title' => 'Редактировать', ], ], 'emptyMessage' => 'Контактов пока нет', 'emptyIcon' => 'fa-solid fa-users', ]; } /** * Список контактов */ public function index() { $config = $this->getContactsTableConfig(); $tableHtml = $this->renderTable($config); return $this->renderTwig('@CRM/contacts/index', [ 'title' => 'Контакты', 'tableHtml' => $tableHtml, 'config' => $config, ]); } /** * AJAX таблица контактов */ public function contactsTable() { return parent::table($this->getContactsTableConfig(), '/crm/contacts'); } /** * Форма создания контакта */ public function create() { $organizationId = $this->requireActiveOrg(); $clients = $this->clientModel ->where('organization_id', $organizationId) ->findAll(); return $this->renderTwig('@CRM/contacts/form', [ 'title' => 'Новый контакт', 'actionUrl' => '/crm/contacts', 'clients' => $clients, ]); } /** * Сохранить новый контакт */ public function store() { $organizationId = $this->requireActiveOrg(); $data = [ 'organization_id' => $organizationId, 'customer_id' => $this->request->getPost('customer_id') ?: null, 'name' => $this->request->getPost('name'), 'email' => $this->request->getPost('email') ?: null, 'phone' => $this->request->getPost('phone') ?: null, 'position' => $this->request->getPost('position') ?: null, 'is_primary' => $this->request->getPost('is_primary') ? 1 : 0, 'notes' => $this->request->getPost('notes') ?: null, ]; $this->contactModel->save($data); $contactId = $this->contactModel->getInsertID(); if ($contactId) { return redirect()->to('/crm/contacts')->with('success', 'Контакт успешно создан'); } return redirect()->back()->with('error', 'Ошибка при создании контакта')->withInput(); } /** * Форма редактирования контакта */ public function edit(int $id) { $organizationId = $this->requireActiveOrg(); $contact = $this->contactModel->find($id); if (!$contact || $contact->organization_id !== $organizationId) { return redirect()->to('/crm/contacts')->with('error', 'Контакт не найден'); } $clients = $this->clientModel ->where('organization_id', $organizationId) ->findAll(); return $this->renderTwig('@CRM/contacts/form', [ 'title' => 'Редактирование контакта', 'actionUrl' => "/crm/contacts/{$id}", 'contact' => $contact, 'clients' => $clients, ]); } /** * Обновить контакт */ public function update(int $id) { $organizationId = $this->requireActiveOrg(); $contact = $this->contactModel->find($id); if (!$contact || $contact->organization_id !== $organizationId) { return redirect()->to('/crm/contacts')->with('error', 'Контакт не найден'); } $data = [ 'customer_id' => $this->request->getPost('customer_id') ?: null, 'name' => $this->request->getPost('name'), 'email' => $this->request->getPost('email') ?: null, 'phone' => $this->request->getPost('phone') ?: null, 'position' => $this->request->getPost('position') ?: null, 'is_primary' => $this->request->getPost('is_primary') ? 1 : 0, 'notes' => $this->request->getPost('notes') ?: null, ]; $this->contactModel->update($id, $data); return redirect()->to('/crm/contacts')->with('success', 'Контакт обновлён'); } /** * Удалить контакт */ public function destroy(int $id) { $organizationId = $this->requireActiveOrg(); $contact = $this->contactModel->find($id); if (!$contact || $contact->organization_id !== $organizationId) { return redirect()->to('/crm/contacts')->with('error', 'Контакт не найден'); } $this->contactModel->delete($id); return redirect()->to('/crm/contacts')->with('success', 'Контакт удалён'); } // ========================================================================= // AJAX API для inline-редактирования в модуле Clients // ========================================================================= /** * Получить список контактов клиента (AJAX) * GET /crm/contacts/list/{clientId} */ public function ajaxList(int $clientId) { $organizationId = $this->requireActiveOrg(); // Проверяем что клиент принадлежит организации $client = $this->clientModel->forCurrentOrg()->find($clientId); if (!$client) { return $this->response->setJSON([ 'success' => false, 'message' => 'Клиент не найден', ]); } $contacts = $this->contactModel ->where('organization_id', $organizationId) ->where('customer_id', $clientId) ->orderBy('name', 'ASC') ->findAll(); $items = array_map(function ($contact) { return [ 'id' => $contact->id, 'name' => $contact->name, 'email' => $contact->email, 'phone' => $contact->phone, 'position' => $contact->position, ]; }, $contacts); return $this->response ->setHeader('X-CSRF-TOKEN', csrf_hash()) ->setHeader('X-CSRF-HASH', csrf_token()) ->setJSON([ 'success' => true, 'items' => $items, 'total' => count($items), ]); } /** * Создать контакт (AJAX) * POST /crm/contacts/store */ public function ajaxStore() { $organizationId = $this->requireActiveOrg(); // Поддержка JSON и form-urlencoded данных $jsonData = $this->request->getJSON(true); $rawInput = $jsonData ?? $this->request->getPost(); $customerId = $rawInput['customer_id'] ?? null; // Проверяем клиента если указан if ($customerId) { $client = $this->clientModel->forCurrentOrg()->find($customerId); if (!$client) { return $this->response->setJSON([ 'success' => false, 'message' => 'Клиент не найден', ])->setStatusCode(422); } } $data = [ 'organization_id' => $organizationId, 'customer_id' => $customerId ?: null, 'name' => $rawInput['name'] ?? '', 'email' => $rawInput['email'] ?? null, 'phone' => $rawInput['phone'] ?? null, 'position' => $rawInput['position'] ?? null, ]; // Валидация if (empty($data['name'])) { return $this->response->setJSON([ 'success' => false, 'message' => 'Имя контакта обязательно', 'errors' => ['name' => 'Имя контакта обязательно'], ])->setStatusCode(422); } $contactId = $this->contactModel->insert($data); if (!$contactId) { return $this->response->setJSON([ 'success' => false, 'message' => 'Ошибка при создании контакта', 'errors' => $this->contactModel->errors(), ])->setStatusCode(422); } return $this->response ->setHeader('X-CSRF-TOKEN', csrf_hash()) ->setHeader('X-CSRF-HASH', csrf_token()) ->setJSON([ 'success' => true, 'message' => 'Контакт создан', 'item' => [ 'id' => $contactId, 'name' => $data['name'], 'email' => $data['email'], 'phone' => $data['phone'], 'position' => $data['position'], ], ]); } /** * Обновить контакт (AJAX) * POST /crm/contacts/update/{id} */ public function ajaxUpdate(int $id) { $organizationId = $this->requireActiveOrg(); $contact = $this->contactModel->find($id); if (!$contact || $contact->organization_id !== $organizationId) { return $this->response->setJSON([ 'success' => false, 'message' => 'Контакт не найден', ])->setStatusCode(404); } // Поддержка JSON и form-urlencoded данных $jsonData = $this->request->getJSON(true); $rawInput = $jsonData ?? $this->request->getPost(); $data = [ 'name' => $rawInput['name'] ?? '', 'email' => $rawInput['email'] ?? null, 'phone' => $rawInput['phone'] ?? null, 'position' => $rawInput['position'] ?? null, ]; // Валидация if (empty($data['name'])) { return $this->response->setJSON([ 'success' => false, 'message' => 'Имя контакта обязательно', 'errors' => ['name' => 'Имя контакта обязательно'], ])->setStatusCode(422); } $result = $this->contactModel->update($id, $data); if (!$result) { return $this->response->setJSON([ 'success' => false, 'message' => 'Ошибка при обновлении контакта', 'errors' => $this->contactModel->errors(), ])->setStatusCode(422); } return $this->response ->setHeader('X-CSRF-TOKEN', csrf_hash()) ->setHeader('X-CSRF-HASH', csrf_token()) ->setJSON([ 'success' => true, 'message' => 'Контакт обновлён', 'item' => [ 'id' => $id, 'name' => $data['name'], 'email' => $data['email'], 'phone' => $data['phone'], 'position' => $data['position'], ], ]); } /** * Удалить контакт (AJAX) * POST /crm/contacts/delete/{id} */ public function ajaxDelete(int $id) { $organizationId = $this->requireActiveOrg(); $contact = $this->contactModel->find($id); if (!$contact || $contact->organization_id !== $organizationId) { return $this->response->setJSON([ 'success' => false, 'message' => 'Контакт не найден', ])->setStatusCode(404); } $this->contactModel->delete($id); return $this->response ->setHeader('X-CSRF-TOKEN', csrf_hash()) ->setHeader('X-CSRF-HASH', csrf_token()) ->setJSON([ 'success' => true, 'message' => 'Контакт удалён', ]); } }