| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- <?php
- /*
- * CarController — CRUD-контроллер для управления автомобилями в административной панели.
- *
- * Создан: 2026-05-06
- * Маршруты: resource /admin/cars (index, create, store, edit, update, destroy)
- * Защита: middleware 'admin' → EnsureUserIsAdmin — только авторизованные администраторы
- * Зависимости: модель Car (app/Models/Car.php), DictSection (справочники для форм)
- */
- namespace App\Http\Controllers\Admin;
- use App\Http\Controllers\Controller;
- use App\Models\Car;
- use App\Models\DictSection;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\Storage;
- class CarController extends Controller
- {
- // Список с фильтрацией: текст (марка/модель/VIN/заголовок) + поля (статус, марка и др.)
- // paginate(20)->withQueryString() — сохраняет фильтры в ссылках страниц
- // Вьюха: resources/views/admin/cars/index.blade.php
- public function index(Request $request)
- {
- $query = Car::query()->orderByDesc('id');
- if ($request->filled('q')) {
- $q = '%'.$request->q.'%';
- $query->where(function ($qb) use ($q) {
- $qb->where('make', 'like', $q)
- ->orWhere('model', 'like', $q)
- ->orWhere('title', 'like', $q)
- ->orWhere('vin', 'like', $q);
- });
- }
- foreach (['status', 'condition', 'make', 'engine_type', 'transmission', 'drive', 'platform'] as $field) {
- if ($request->filled($field)) {
- $query->where($field, $request->input($field));
- }
- }
- $cars = $query->paginate(20)->withQueryString();
- $makes = Car::select('make')->distinct()->orderBy('make')->pluck('make');
- // Площадки строго из справочника — чтобы отображались все, даже без привязанных авто
- $platformSection = DictSection::where('code', 'platforms')->with('values')->first();
- $platforms = $platformSection ? $platformSection->values->pluck('value') : collect();
- return view('admin.cars.index', compact('cars', 'makes', 'platforms'));
- }
- // Форма создания нового автомобиля
- // keyBy('code') — индексирует коллекцию по полю code для удобного доступа в шаблоне: $dictSections->get('makes')
- // Вьюха: resources/views/admin/cars/form.blade.php (общая для create и edit)
- public function create()
- {
- $dictSections = DictSection::with('values')->orderBy('sort_order')->get()->keyBy('code');
- return view('admin.cars.form', compact('dictSections'));
- }
- // Сохранение нового автомобиля: validated() → Car::create() → handlePhotos() → редирект
- public function store(Request $request)
- {
- $data = $this->validated($request);
- $car = Car::create($data);
- $this->handlePhotos($request, $car);
- Cache::flush(); // каталог с фильтрами — сбрасываем весь кеш при изменении авто
- return redirect()->route('admin.cars.index')->with('success', 'Автомобиль добавлен.');
- }
- // Форма редактирования: $car передаётся через route model binding по {car} в URL
- // Та же вьюха form.blade.php — внутри проверяется isset($car)
- public function edit(Car $car)
- {
- $dictSections = DictSection::with('values')->orderBy('sort_order')->get()->keyBy('code');
- return view('admin.cars.form', compact('car', 'dictSections'));
- }
- // Обновление данных автомобиля: логика аналогична store()
- public function update(Request $request, Car $car)
- {
- $data = $this->validated($request);
- $car->update($data);
- $this->handlePhotos($request, $car);
- Cache::flush(); // каталог с фильтрами — сбрасываем весь кеш при изменении авто
- return redirect()->route('admin.cars.index')->with('success', 'Автомобиль обновлён.');
- }
- // Удаление: сначала удаляет файлы с диска, затем запись из БД
- // photo_main и photos_gallery (JSON-массив) удаляются из Storage::disk('public')
- public function destroy(Car $car)
- {
- if ($car->photo_main) {
- Storage::disk('public')->delete($car->photo_main);
- }
- if ($car->photos_gallery) {
- foreach ($car->photos_gallery as $path) {
- Storage::disk('public')->delete($path);
- }
- }
- $car->delete();
- Cache::flush();
- return redirect()->route('admin.cars.index')->with('success', 'Автомобиль удалён.');
- }
- // Правила валидации для формы автомобиля — вынесены в приватный метод
- // чтобы не дублировать между store() и update(); фото-поля здесь не включены
- private function validated(Request $request): array
- {
- return $request->validate([
- 'status' => 'required|in:active,sold,draft',
- 'condition' => 'required|in:new,used',
- 'make' => 'required|string|max:64',
- 'model' => 'required|string|max:64',
- 'generation' => 'nullable|string|max:64',
- 'year' => 'required|integer|min:1900|max:2100',
- 'vin' => 'nullable|string|max:17',
- 'plate' => 'nullable|string|max:20',
- 'body_type' => 'nullable|string|max:32',
- 'doors' => 'nullable|integer|min:1|max:10',
- 'color_exterior' => 'nullable|string|max:32',
- 'color_interior' => 'nullable|string|max:32',
- 'engine_type' => 'nullable|in:petrol,diesel,hybrid,electric,gas,other',
- 'engine_volume' => 'nullable|numeric|min:0|max:99',
- 'engine_power_hp' => 'nullable|integer|min:0|max:5000',
- 'transmission' => 'nullable|in:manual,automatic,robot,variator,electric',
- 'drive' => 'nullable|in:FWD,RWD,AWD,4WD',
- 'mileage_km' => 'nullable|integer|min:0',
- 'steering' => 'required|in:left,right',
- 'owners_count' => 'nullable|integer|min:0',
- 'customs_cleared' => 'nullable|boolean',
- 'pts' => 'nullable|in:original,duplicate,electronic',
- 'accident_free' => 'nullable|boolean',
- 'price_usd' => 'nullable|integer|min:0',
- 'price_rub' => 'nullable|integer|min:0',
- 'price_vladivostok' => 'nullable|integer|min:0',
- 'price_moscow' => 'nullable|integer|min:0',
- 'price_negotiable' => 'nullable|boolean',
- 'country_origin' => 'nullable|string|max:64',
- 'city' => 'nullable|string|max:64',
- 'platform' => 'required|string|max:64',
- 'options' => 'nullable|array',
- 'title' => 'nullable|string|max:128',
- 'description' => 'nullable|string',
- ]);
- }
- // Загрузка и обновление фото: вызывается из store() и update() после сохранения записи
- // 1. photo_main — старый файл удаляется, новый → storage/app/public/cars/{id}/main/
- // 2. photos_gallery — новые фото добавляются к JSON-массиву → cars/{id}/gallery/
- // 3. delete_gallery — пути из формы → удаление файла + фильтрация массива + array_values()
- // Доступ через web: /storage/cars/... (требует: php artisan storage:link)
- private function handlePhotos(Request $request, Car $car): void
- {
- if ($request->hasFile('photo_main') && $request->file('photo_main')->isValid()) {
- if ($car->photo_main) {
- Storage::disk('public')->delete($car->photo_main);
- }
- $path = $request->file('photo_main')->store("cars/{$car->id}/main", 'public');
- $car->update(['photo_main' => $path]);
- }
- if ($request->hasFile('photos_gallery')) {
- $gallery = $car->photos_gallery ?? [];
- foreach ($request->file('photos_gallery') as $file) {
- if ($file->isValid()) {
- $gallery[] = $file->store("cars/{$car->id}/gallery", 'public');
- }
- }
- $car->update(['photos_gallery' => $gallery]);
- }
- if ($request->filled('delete_gallery')) {
- $toDelete = (array) $request->input('delete_gallery');
- $gallery = $car->photos_gallery ?? [];
- foreach ($toDelete as $path) {
- Storage::disk('public')->delete($path);
- $gallery = array_filter($gallery, fn ($p) => $p !== $path);
- }
- $car->update(['photos_gallery' => array_values($gallery)]);
- }
- }
- }
|