'common_catalog', 'title' => 'Каталог общий', 'id' => 'common_catalog_items', 'header' => [ 'id' => 'ID', 'image' => 'Внешний вид', 'article' => 'Артикул', 'calculator_name' => 'Наименование', 'kind' => 'Вид', 'dimension_length' => 'Габариты: длина', 'dimension_width' => 'Габариты: ширина', 'dimension_height' => 'Габариты: высота', 'site_length' => 'Участок: длина', 'site_width' => 'Участок: ширина', 'fall_height' => 'Высота падения', 'additional_info' => 'Дополнительные сведения', 'dimension_unit' => 'Единица измерения габаритов', 'weight' => 'Вес, кг.', 'volume' => 'Объем, м3', 'places' => 'Места', 'composition' => 'Состав', 'age_group' => 'Возрастная группа', 'max_users' => 'Макс. кол-во пользователей', 'unit' => 'Ед.', 'series' => 'Серия', 'trademark' => 'ТМ', 'calculator_enabled_txt' => 'Калькулятор', 'builders_price_txt' => 'строители', 'wholesale_price_txt' => 'опт', 'recommended_price_txt' => 'рек', 'retail_price_txt' => 'розница', 'project_price_txt' => 'проект', 'project_with_installation_price_txt' => 'проект+м', 'pik_price_txt' => 'пик', 'recommended_plus_10_price_txt' => 'рек+10', ], 'searchFields' => [ 'article', 'calculator_name', 'kind', 'dimension_length', 'dimension_width', 'dimension_height', 'site_length', 'site_width', 'additional_info', 'composition', 'series', 'trademark', ], ]; public function index(Request $request, FieldAccessService $fieldAccess): View { session(['gp_common_catalog' => $request->query()]); $nav = $this->startNavigationContext($request); $model = new CommonCatalogItem; $readableFields = $this->readableFields($request, $fieldAccess); $request = $this->sanitizeListRequest($request, $readableFields); $this->data['header'] = $fieldAccess->visibleHeaders( $request->user(), 'common-catalog', $this->data['header'], ); $this->data['searchFields'] = array_values(array_intersect( $this->data['searchFields'], $readableFields, )); $this->createFilters( $model, ...array_values(array_intersect([ 'kind', 'dimension_unit', 'age_group', 'unit', 'series', 'trademark', ], $readableFields)), ); $this->createRangeFilters( $model, ...array_values(array_intersect([ 'fall_height', 'weight', 'volume', 'places', 'max_users', 'builders_price', 'wholesale_price', 'recommended_price', 'retail_price', 'project_price', 'project_with_installation_price', 'pik_price', 'recommended_plus_10_price', ], $readableFields)), ); $query = CommonCatalogItem::query()->with('imageFile'); $this->acceptFilters($query, $request); $this->acceptSearch($query, $request); $this->setSortAndOrderBy($model, $request); $this->applyStableSorting($query); $this->data['items'] = $query->paginate($this->data['per_page'])->withQueryString(); $this->data['nav'] = $nav; return view('common_catalog.index', $this->data); } public function create(Request $request, FieldAccessService $fieldAccess): View { return $this->itemView($request, $fieldAccess); } public function show( Request $request, CommonCatalogItem $commonCatalogItem, FieldAccessService $fieldAccess, ): View { return $this->itemView( $request, $fieldAccess, $commonCatalogItem->load(['imageFile', 'documents']), ); } public function store( StoreCommonCatalogItemRequest $request, FieldAccessService $fieldAccess, ): RedirectResponse { $item = CommonCatalogItem::query()->create( $fieldAccess->filterValidatedPayload( $request->user(), 'common-catalog', $request->validated(), ), ); return redirect() ->route('common-catalog.show', $this->withNav( ['commonCatalogItem' => $item], $this->resolveNavToken($request), )) ->with('success', 'Позиция общего каталога создана.'); } public function update( StoreCommonCatalogItemRequest $request, CommonCatalogItem $commonCatalogItem, FieldAccessService $fieldAccess, ): RedirectResponse { $commonCatalogItem->update($fieldAccess->filterValidatedPayload( $request->user(), 'common-catalog', $request->validated(), )); return $this->redirectToItem($request, $commonCatalogItem) ->with('success', 'Позиция общего каталога обновлена.'); } public function destroy( Request $request, CommonCatalogItem $commonCatalogItem, FileService $fileService, ): RedirectResponse { $commonCatalogItem->load(['imageFile', 'documents']); $files = $commonCatalogItem->documents; if ($commonCatalogItem->imageFile) { $files->push($commonCatalogItem->imageFile); } $commonCatalogItem->documents()->detach(); $commonCatalogItem->update(['image_file_id' => null]); $commonCatalogItem->delete(); foreach ($files->unique('id') as $file) { $fileService->deleteFileWithThumbnail($file); $file->delete(); } return redirect() ->route('common-catalog.index', session('gp_common_catalog')) ->with('success', 'Позиция общего каталога удалена.'); } public function uploadImage( Request $request, CommonCatalogItem $commonCatalogItem, FileService $fileService, ): RedirectResponse { $data = $request->validate([ 'image' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:10240'], ]); try { $oldImage = $commonCatalogItem->imageFile; $image = $fileService->saveUploadedFile( "common_catalog/items/{$commonCatalogItem->id}/image", $data['image'], ); $commonCatalogItem->update(['image_file_id' => $image->id]); if ($oldImage) { $fileService->deleteFileWithThumbnail($oldImage); $oldImage->delete(); } } catch (Throwable $exception) { report($exception); return $this->redirectToItem($request, $commonCatalogItem) ->with('error', 'Не удалось загрузить изображение.'); } return $this->redirectToItem($request, $commonCatalogItem) ->with('success', 'Изображение загружено.'); } public function deleteImage( Request $request, CommonCatalogItem $commonCatalogItem, FileService $fileService, ): RedirectResponse { $image = $commonCatalogItem->imageFile; if ($image) { $commonCatalogItem->update(['image_file_id' => null]); $fileService->deleteFileWithThumbnail($image); $image->delete(); } return $this->redirectToItem($request, $commonCatalogItem) ->with('success', 'Изображение удалено.'); } public function uploadDocument( Request $request, CommonCatalogItem $commonCatalogItem, FileService $fileService, ): RedirectResponse { $data = $request->validate([ 'document' => ['required', 'file', 'max:20480'], ]); try { $document = $fileService->saveUploadedFile( "common_catalog/items/{$commonCatalogItem->id}/documents", $data['document'], ); $commonCatalogItem->documents()->attach($document); } catch (Throwable $exception) { report($exception); return $this->redirectToItem($request, $commonCatalogItem) ->with('error', 'Не удалось загрузить документ.'); } return $this->redirectToItem($request, $commonCatalogItem) ->with('success', 'Документ загружен.'); } public function deleteDocument( Request $request, CommonCatalogItem $commonCatalogItem, File $file, FileService $fileService, ): RedirectResponse { abort_unless($commonCatalogItem->documents()->whereKey($file->getKey())->exists(), 404); $commonCatalogItem->documents()->detach($file); $fileService->deleteFileWithThumbnail($file); $file->delete(); return $this->redirectToItem($request, $commonCatalogItem) ->with('success', 'Документ удалён.'); } public function export(Request $request): RedirectResponse { ExportCommonCatalogJob::dispatch((int) $request->user()->getKey()); return redirect() ->route('common-catalog.index', session('gp_common_catalog')) ->with('success', 'Задача экспорта общего каталога создана.'); } public function import(Request $request): RedirectResponse { $data = $request->validate([ 'import_file' => ['required', 'file', 'mimes:xlsx', 'max:20480'], ]); $path = Str::random(2).'/'.Str::uuid().'.xlsx'; Storage::disk('upload')->put($path, $data['import_file']->getContent()); $import = Import::query()->create([ 'type' => 'common_catalog', 'year' => null, 'filename' => $path, 'status' => 'new', 'user_id' => $request->user()->getKey(), 'original_filename' => $data['import_file']->getClientOriginalName(), ]); ImportJob::dispatch($import, (int) $request->user()->getKey()); return redirect() ->route('common-catalog.index', session('gp_common_catalog')) ->with('success', 'Задача импорта общего каталога создана.'); } private function itemView( Request $request, FieldAccessService $fieldAccess, ?CommonCatalogItem $item = null, ): View { $nav = $this->resolveNavToken($request); $this->rememberNavigation($request, $nav); $this->data['nav'] = $nav; $this->data['back_url'] = $this->navigationBackUrl( $request, $nav, route('common-catalog.index', session('gp_common_catalog')), ); $this->data['item'] = $item; $fields = array_keys(config('access.common-catalog.fields', [])); $this->data['commonCatalogReadableFields'] = array_fill_keys( $fieldAccess->filterReadableFields($request->user(), 'common-catalog', $fields), true, ); $this->data['commonCatalogWritableFields'] = []; foreach ($fields as $field) { $this->data['commonCatalogWritableFields'][$field] = $request->user() ->canUpdateField('common-catalog', $field); } return view('common_catalog.edit', $this->data); } private function readableFields(Request $request, FieldAccessService $fieldAccess): array { return $fieldAccess->filterReadableFields( $request->user(), 'common-catalog', array_keys(config('access.common-catalog.fields', [])), ); } private function sanitizeListRequest(Request $request, array $readableFields): Request { if ( $request->filled('sortBy') && ! in_array($this->normalizeField($request->string('sortBy')->toString()), $readableFields, true) ) { $request->merge(['sortBy' => CommonCatalogItem::DEFAULT_SORT_BY]); } if ($request->has('filters') && is_array($request->input('filters'))) { $request->merge([ 'filters' => array_filter( $request->input('filters'), fn (mixed $value, string $field): bool => in_array( $this->normalizeField($field), $readableFields, true, ), ARRAY_FILTER_USE_BOTH, ), ]); } return $request; } private function normalizeField(string $field): string { $field = preg_replace('/_(from|to)$/', '', $field) ?: $field; return str_ends_with($field, '_txt') ? substr($field, 0, -4) : $field; } private function redirectToItem( Request $request, CommonCatalogItem $commonCatalogItem, ): RedirectResponse { return redirect()->route('common-catalog.show', $this->withNav( ['commonCatalogItem' => $commonCatalogItem], $this->resolveNavToken($request), )); } }