'catalog', 'title' => 'Каталог', 'id' => 'products', 'header' => [ 'image' => 'Картинка', 'id' => 'ID', 'article' => 'Артикул', 'nomenclature_number' => 'Номер номенклатуры', 'manufacturer' => 'Производитель', 'unit' => 'Ед. изм.', 'name_tz' => 'Наименование ТЗ', 'type_tz' => 'Тип по ТЗ', 'type' => 'Тип', 'manufacturer_name' => 'Наименование производителя', 'sizes' => 'Размеры', 'product_price_txt' => 'Цена товара', 'installation_price_txt' => 'Цена установки', 'total_price_txt' => 'Итоговая цена', 'note' => 'Примечания', 'created_at' => 'Дата создания', 'certificate_id' => 'Сертификат', 'passport_name' => 'Наименование по паспорту', 'statement_name' => 'Наименование в ведомости', 'service_life' => 'Срок службы', 'certificate_number' => 'Номер сертификата', 'certificate_date' => 'Дата сертификата', 'certificate_issuer' => 'Орган сертификации', 'certificate_type' => 'Вид сертификации', 'weight' => 'Вес', 'volume' => 'Объем', 'places' => 'Мест', ], 'searchFields' => [ 'nomenclature_number', 'article', 'name_tz', 'manufacturer_name', 'note', ], ]; public function index(Request $request, FieldAccessService $fieldAccess) { session(['gp_products' => $request->query()]); $nav = $this->startNavigationContext($request); $model = new Product; $readableFields = $this->readableCatalogFields($request, $fieldAccess); $request = $this->sanitizeCatalogListRequest($request, $readableFields); $this->data['header'] = $fieldAccess->visibleHeaders($request->user(), 'catalog', $this->data['header']); $this->data['searchFields'] = array_values(array_filter( $this->data['searchFields'], fn (string $field): bool => in_array($field, $readableFields, true) )); // fill filters $this->createFilters($model, ...array_values(array_intersect(['type_tz', 'type', 'certificate_id'], $readableFields))); $this->createRangeFilters($model, ...array_values(array_intersect(['nomenclature_number', 'product_price', 'installation_price', 'total_price'], $readableFields))); $this->createDateFilters($model, ...array_values(array_intersect(['certificate_date', 'created_at'], $readableFields))); $this->data['filters'] = $this->filterCatalogControls($this->data['filters'] ?? [], $readableFields); $this->data['ranges'] = $this->filterCatalogControls($this->data['ranges'] ?? [], $readableFields); $this->data['dates'] = $this->filterCatalogControls($this->data['dates'] ?? [], $readableFields); // create request $q = $model::query(); $this->acceptFilters($q, $request); $this->acceptSearch($q, $request); $this->setSortAndOrderBy($model, $request); $this->applyStableSorting($q); $this->data['products'] = $q->paginate($this->data['per_page'])->withQueryString(); $this->data['nav'] = $nav; return view('catalog.index', $this->data); } public function show(Request $request, int $product, FieldAccessService $fieldAccess) { $nav = $this->resolveNavToken($request); $this->rememberNavigation($request, $nav); $this->data['nav'] = $nav; $this->data['back_url'] = $this->navigationBackUrl( $request, $nav, route('catalog.index', session('gp_products')) ); $this->data['product'] = Product::query()->withoutGlobalScope(\App\Models\Scopes\YearScope::class)->find($product); $this->prepareCatalogFieldAccess($request, $fieldAccess); return view('catalog.edit', $this->data); } public function create(Request $request, FieldAccessService $fieldAccess) { $nav = $this->resolveNavToken($request); $this->rememberNavigation($request, $nav); $this->data['nav'] = $nav; $this->data['back_url'] = $this->navigationBackUrl( $request, $nav, route('catalog.index', session('gp_products')) ); $this->data['product'] = null; $this->prepareCatalogFieldAccess($request, $fieldAccess); return view('catalog.edit', $this->data); } public function store(StoreProductRequest $request, FieldAccessService $fieldAccess) { Product::create($fieldAccess->filterValidatedPayload($request->user(), 'catalog', $request->validated())); $nav = $this->resolveNavToken($request); $backUrl = $this->navigationParentUrl( $nav, route('catalog.index', session('gp_products')) ); return redirect()->to($backUrl); } public function update(StoreProductRequest $request, Product $product, FieldAccessService $fieldAccess) { $product->update($fieldAccess->filterValidatedPayload($request->user(), 'catalog', $request->validated())); $nav = $this->resolveNavToken($request); $backUrl = $this->navigationParentUrl( $nav, route('catalog.index', session('gp_products')) ); return redirect()->to($backUrl); } public function delete(Product $product) { $product->delete(); return redirect()->route('catalog.index', session('gp_products')); } public function export(Request $request) { $request->validate([ 'withFilter' => 'nullable', 'filters' => 'nullable|array', 's' => 'nullable|string', ]); $filters = []; if ($request->boolean('withFilter')) { $filters = $request->filters ?? []; if ($request->filled('s')) { $filters['s'] = $request->string('s')->toString(); } } $filters['year'] = year(); ExportCatalog::dispatch($filters, $request->user()->id); Log::info('ExportCatalog job created!'); return redirect()->route('catalog.index', session('gp_products'))->with(['success' => 'Задача экспорта успешно создана!']); } /** * ajax * @param Request $request * @return array */ public function search(Request $request): array { $s = $request->get('s'); $searchFields = app(FieldAccessService::class)->filterReadableFields($request->user(), 'catalog', $this->data['searchFields']); $ret = []; if (!$searchFields) { return $ret; } if($s) { $result = Product::query()->where(function ($query) use ($searchFields, $s) { foreach ($searchFields as $searchField) { $query->orWhere($searchField, 'LIKE', '%' . $s . '%'); } }); foreach ($result->get() as $p) { $ret[$p->id] = $p->common_name; } } return $ret; } public function uploadCertificate(Request $request, Product $product, FileService $fileService) { $data = $request->validate([ 'certificate' => 'file', ]); try { $f = $fileService->saveUploadedFile('products/' . $product->id . '/certificate', $data['certificate']); $product->update(['certificate_id' => $f->id]); } catch (Throwable $e) { report($e); return $this->redirectToCatalogShow($request, $product) ->with(['error' => 'Ошибка загрузки сертификата. Проверьте имя файла и повторите попытку.']); } return $this->redirectToCatalogShow($request, $product) ->with(['success' => 'Сертификат успешно загружен!']); } public function deleteCertificate(Request $request, Product $product, File $file) { $product->update(['certificate_id' => null]); Storage::disk('public')->delete($file->path); $file->delete(); return $this->redirectToCatalogShow($request, $product); } public function uploadThumbnail(Request $request, Product $product): RedirectResponse { $request->validate([ 'thumbnail' => 'required|file|mimes:jpg,jpeg|max:5120', ]); $file = $request->file('thumbnail'); $filename = $product->article . '.0000.0000.jpg'; $destinationPath = public_path('images/main'); // Удаляем старый файл если существует $oldFilePath = $destinationPath . '/' . $filename; if (file_exists($oldFilePath)) { unlink($oldFilePath); } // Сохраняем новый файл $file->move($destinationPath, $filename); return $this->redirectToCatalogShow($request, $product) ->with('success', 'Миниатюра успешно загружена'); } private function redirectToCatalogShow(Request $request, Product $product): RedirectResponse { $nav = $this->resolveNavToken($request); return redirect()->route('catalog.show', $this->withNav(['product' => $product], $nav)); } private function prepareCatalogFieldAccess(Request $request, FieldAccessService $fieldAccess): void { $fields = array_keys(config('access.catalog.fields', [])); $this->data['catalogReadableFields'] = array_fill_keys( $fieldAccess->filterReadableFields($request->user(), 'catalog', $fields), true ); $this->data['catalogWritableFields'] = []; foreach ($fields as $field) { $this->data['catalogWritableFields'][$field] = $request->user()->canUpdateField('catalog', $field); } } private function readableCatalogFields(Request $request, FieldAccessService $fieldAccess): array { return $fieldAccess->filterReadableFields( $request->user(), 'catalog', array_keys(config('access.catalog.fields', [])) ); } private function sanitizeCatalogListRequest(Request $request, array $readableFields): Request { if ($request->filled('sortBy') && !in_array($this->normalizeCatalogField($request->string('sortBy')->toString()), $readableFields, true)) { $request->merge(['sortBy' => Product::DEFAULT_SORT_BY]); } if ($request->has('filters') && is_array($request->input('filters'))) { $request->merge([ 'filters' => array_filter( $request->input('filters'), fn ($value, string $field): bool => in_array($this->normalizeCatalogField($field), $readableFields, true), ARRAY_FILTER_USE_BOTH ), ]); } return $request; } private function filterCatalogControls(array $controls, array $readableFields): array { return array_filter( $controls, fn (string $field): bool => in_array($this->normalizeCatalogField($field), $readableFields, true), ARRAY_FILTER_USE_KEY ); } private function normalizeCatalogField(string $field): string { $field = preg_replace('/_(from|to)$/', '', $field) ?: $field; return str_ends_with($field, '_txt') ? substr($field, 0, -4) : $field; } }