change ratelimiter
This commit is contained in:
@@ -70,6 +70,7 @@ class Clients extends BaseController
|
||||
'type' => 'delete',
|
||||
]
|
||||
],
|
||||
'onRowClick' => 'viewClient', // Функция для открытия карточки клиента
|
||||
'emptyMessage' => 'Клиентов пока нет',
|
||||
'emptyIcon' => 'fa-solid fa-users',
|
||||
'emptyActionUrl' => base_url('/clients/new'),
|
||||
@@ -237,4 +238,245 @@ class Clients extends BaseController
|
||||
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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user