| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- <?php
- /*
- * ManualController — управление справочниками (разделы и значения).
- *
- * Создан: 2026-05-06
- * Маршруты: /admin/manuals/... (не resource, ручные маршруты в routes/web.php)
- * Защита: middleware 'admin' (EnsureUserIsAdmin)
- *
- * Справочники — двухуровневая структура:
- * DictSection (разделы): например "Марки и модели", "Типы кузова"
- * DictValue (значения): Toyota → Camry, Corolla и т.д.
- * Поддерживает иерархические (is_hierarchical=true) и плоские (false) разделы.
- */
- namespace App\Http\Controllers\Admin;
- use App\Http\Controllers\Controller;
- use App\Models\DictSection;
- use App\Models\DictValue;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\Storage;
- class ManualController extends Controller
- {
- // ── Разделы справочников ────────────────────────────────────
- // Список всех разделов справочников, отсортированных по sort_order
- // Вьюха: resources/views/admin/manuals/index.blade.php
- public function index()
- {
- $sections = DictSection::orderBy('sort_order')->get();
- return view('admin.manuals.index', compact('sections'));
- }
- // Форма создания нового раздела справочника
- // Вьюха: resources/views/admin/manuals/section_form.blade.php
- public function sectionCreate()
- {
- return view('admin.manuals.section_form');
- }
- // Сохранение нового раздела; is_system всегда false — системные разделы создаются только через сидер
- public function sectionStore(Request $request)
- {
- $request->validate([
- 'code' => 'required|string|max:64|unique:dict_sections,code',
- 'label' => 'required|string|max:128',
- 'is_hierarchical' => 'nullable|boolean',
- 'sort_order' => 'nullable|integer',
- ]);
- DictSection::create([
- 'code' => $request->code,
- 'label' => $request->label,
- 'is_system' => false,
- 'is_hierarchical' => (bool) $request->input('is_hierarchical', false),
- 'sort_order' => (int) $request->input('sort_order', 0),
- ]);
- return redirect()->route('admin.manuals.index')->with('success', 'Раздел добавлен.');
- }
- // Форма редактирования раздела справочника (та же вьюха, что и sectionCreate)
- public function sectionEdit(DictSection $section)
- {
- return view('admin.manuals.section_form', compact('section'));
- }
- // Обновление раздела; unique-правило исключает текущую запись: unique:dict_sections,code,{id}
- public function sectionUpdate(Request $request, DictSection $section)
- {
- $request->validate([
- 'code' => 'required|string|max:64|unique:dict_sections,code,'.$section->id,
- 'label' => 'required|string|max:128',
- 'is_hierarchical' => 'nullable|boolean',
- 'sort_order' => 'nullable|integer',
- ]);
- $section->update([
- 'code' => $request->code,
- 'label' => $request->label,
- 'is_hierarchical' => (bool) $request->input('is_hierarchical', false),
- 'sort_order' => (int) $request->input('sort_order', 0),
- ]);
- return redirect()->route('admin.manuals.index')->with('success', 'Раздел обновлён.');
- }
- // Удаление раздела; системные (is_system=true) удалять нельзя — защита от разрушения структуры
- // При удалении раздела cascade удаляет все его значения (настроено в миграции FK)
- public function sectionDestroy(DictSection $section)
- {
- if ($section->is_system) {
- return back()->with('error', 'Системный раздел нельзя удалить.');
- }
- $section->delete();
- return redirect()->route('admin.manuals.index')->with('success', 'Раздел удалён.');
- }
- // ── Значения справочника ────────────────────────────────────
- // Список значений раздела; иерархические — с children (eager load); плоские — простой список
- // Вьюха: resources/views/admin/manuals/values.blade.php (показывает оба варианта через @if)
- public function values(DictSection $section)
- {
- if ($section->is_hierarchical) {
- $items = DictValue::where('section_id', $section->id)
- ->whereNull('parent_id')
- ->with('children')
- ->orderBy('sort_order')
- ->get();
- } else {
- $items = DictValue::where('section_id', $section->id)
- ->orderBy('sort_order')
- ->get();
- }
- return view('admin.manuals.values', compact('section', 'items'));
- }
- // Форма добавления значения; если передан parent_id в URL → предзаполняет родителя
- // Вьюха: resources/views/admin/manuals/value_form.blade.php
- public function valueCreate(DictSection $section, Request $request)
- {
- $parent = null;
- if ($request->filled('parent_id')) {
- $parent = DictValue::findOrFail($request->parent_id);
- }
- return view('admin.manuals.value_form', compact('section', 'parent'));
- }
- // Сохранение нового значения; parent_id = null для верхнего уровня иерархии
- public function valueStore(DictSection $section, Request $request)
- {
- $request->validate([
- 'value' => 'required|string|max:255',
- 'flag' => 'nullable|string|max:10',
- 'parent_id' => 'nullable|integer|exists:dict_values,id',
- 'sort_order' => 'nullable|integer',
- 'logo' => 'nullable|image|max:2048',
- ]);
- $logoPath = null;
- if ($request->hasFile('logo') && $request->file('logo')->isValid()) {
- $logoPath = $request->file('logo')->store('makes/logos', 'public');
- }
- DictValue::create([
- 'section_id' => $section->id,
- 'parent_id' => $request->input('parent_id') ?: null,
- 'value' => $request->value,
- 'flag' => $request->input('flag', ''),
- 'logo' => $logoPath,
- 'sort_order' => (int) $request->input('sort_order', 0),
- ]);
- return redirect()->route('admin.manuals.values', $section)->with('success', 'Значение добавлено.');
- }
- // Форма редактирования значения; загружает текущего родителя для отображения в форме
- public function valueEdit(DictSection $section, DictValue $value)
- {
- $parent = $value->parent_id ? DictValue::find($value->parent_id) : null;
- return view('admin.manuals.value_form', compact('section', 'value', 'parent'));
- }
- // Обновление значения; parent_id обнуляется если пустой (ternary: '' → null)
- public function valueUpdate(Request $request, DictSection $section, DictValue $value)
- {
- $request->validate([
- 'value' => 'required|string|max:255',
- 'flag' => 'nullable|string|max:10',
- 'parent_id' => 'nullable|integer|exists:dict_values,id',
- 'sort_order' => 'nullable|integer',
- 'logo' => 'nullable|image|max:2048',
- 'remove_logo' => 'nullable|boolean',
- ]);
- $data = [
- 'parent_id' => $request->input('parent_id') ?: null,
- 'value' => $request->value,
- 'flag' => $request->input('flag', ''),
- 'sort_order' => (int) $request->input('sort_order', 0),
- ];
- if ($request->hasFile('logo') && $request->file('logo')->isValid()) {
- // Удаляем старый логотип перед заменой
- if ($value->logo) {
- Storage::disk('public')->delete($value->logo);
- }
- $data['logo'] = $request->file('logo')->store('makes/logos', 'public');
- } elseif ($request->boolean('remove_logo') && $value->logo) {
- Storage::disk('public')->delete($value->logo);
- $data['logo'] = null;
- }
- $value->update($data);
- return redirect()->route('admin.manuals.values', $section)->with('success', 'Значение обновлено.');
- }
- // Удаление значения; cascade из FK удалит и все дочерние значения (если иерархия)
- public function valueDestroy(DictSection $section, DictValue $value)
- {
- $value->delete();
- return redirect()->route('admin.manuals.values', $section)->with('success', 'Значение удалено.');
- }
- }
|