refactoring

This commit is contained in:
root
2026-01-12 18:15:12 +03:00
parent 077b79b8f7
commit c55264cf42
5 changed files with 274 additions and 422 deletions
-260
View File
@@ -70,7 +70,6 @@ class Clients extends BaseController
'type' => 'delete',
]
],
'onRowClick' => 'viewClient', // Функция для открытия карточки клиента
'emptyMessage' => 'Клиентов пока нет',
'emptyIcon' => 'fa-solid fa-users',
'emptyActionUrl' => base_url('/clients/new'),
@@ -220,263 +219,4 @@ class Clients extends BaseController
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 '<table border="1">';
echo '<tr><th>ID</th><th>Имя</th><th>Email</th><th>Телефон</th><th>Статус</th><th>Создан</th><th>Обновлён</th></tr>';
foreach ($clients as $client) {
echo '<tr>';
echo '<td>' . $client['id'] . '</td>';
echo '<td>' . htmlspecialchars($client['name']) . '</td>';
echo '<td>' . htmlspecialchars($client['email'] ?? '') . '</td>';
echo '<td>' . htmlspecialchars($client['phone'] ?? '') . '</td>';
echo '<td>' . ($client['status'] ?? 'active') . '</td>';
echo '<td>' . ($client['created_at'] ?? '') . '</td>';
echo '<td>' . ($client['updated_at'] ?? '') . '</td>';
echo '</tr>';
}
echo '</table>';
exit;
}
/**
* Страница импорта клиентов (форма)
*/
public function importPage()
{
if (!$this->access->canCreate('clients')) {
return $this->forbiddenResponse('Доступ запрещён');
}
return $this->renderTwig('@Clients/import');
}
/**
* Импорт клиентов из файла
*/
public function import()
{
if (!$this->access->canCreate('clients')) {
return $this->forbiddenResponse('Доступ запрещён');
}
$file = $this->request->getFile('file');
if (!$file->isValid()) {
return $this->response->setJSON([
'success' => false,
'message' => 'Файл не загружен'
]);
}
$extension = strtolower($file->getClientExtension());
if (!in_array($extension, ['csv', 'xlsx', 'xls'])) {
return $this->response->setJSON([
'success' => false,
'message' => 'Неподдерживаемый формат файла. Используйте CSV или XLSX.'
]);
}
$organizationId = session()->get('active_org_id');
$imported = 0;
$errors = [];
// Парсим CSV файл
if ($extension === 'csv') {
$handle = fopen($file->getTempName(), 'r');
// Пропускаем заголовок
fgetcsv($handle, 0, ';');
$row = 1;
while (($data = fgetcsv($handle, 0, ';')) !== false) {
$row++;
$name = trim($data[1] ?? '');
$email = trim($data[2] ?? '');
// Валидация
if (empty($name)) {
$errors[] = "Строка $row: Имя обязательно";
continue;
}
if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Строка $row: Некорректный email";
continue;
}
// Проверка на дубликат email
if (!empty($email)) {
$exists = $this->clientModel->where('organization_id', $organizationId)
->where('email', $email)
->countAllResults();
if ($exists > 0) {
$errors[] = "Строка $row: Клиент с email $email уже существует";
continue;
}
}
// Вставка
$this->clientModel->insert([
'organization_id' => $organizationId,
'name' => $name,
'email' => $email ?: null,
'phone' => trim($data[3] ?? '') ?: null,
'status' => 'active',
]);
$imported++;
}
fclose($handle);
} else {
// Для XLSX в продакшене использовать PhpSpreadsheet
return $this->response->setJSON([
'success' => false,
'message' => 'Импорт XLSX файлов временно недоступен. Пожалуйста, используйте CSV формат.'
]);
}
$message = "Импортировано клиентов: $imported";
if (!empty($errors)) {
$message .= '. Ошибок: ' . count($errors);
}
return $this->response->setJSON([
'success' => true,
'message' => $message,
'imported' => $imported,
'errors' => $errors
]);
}
}