query('search', '')); $kind = trim((string) $request->query('kind', '')); $status = trim((string) $request->query('status', '')); $perPage = $this->perPage($request); $items = CommonCatalogItem::query() ->whereHas('stockOrders') ->with(['imageFile', 'latestStockOrder']) ->withSum([ 'stockOrders as physical_quantity' => fn (Builder $query) => $query->where('status', StockOrder::STATUS_IN_STOCK), ], 'available_quantity') ->withSum([ 'stockReservations as reserved_quantity' => fn (Builder $query) => $query->active(), ], 'quantity') ->withSum([ 'stockOrders as ordered_stock_quantity' => fn (Builder $query) => $query->where('status', StockOrder::STATUS_ORDERED), ], 'ordered_quantity') ->when($search !== '', function (Builder $query) use ($search): void { $query->where(function (Builder $query) use ($search): void { $query->where('article', 'like', "%{$search}%") ->orWhere('calculator_name', 'like', "%{$search}%") ->orWhere('kind', 'like', "%{$search}%"); }); }) ->when($kind !== '', fn (Builder $query) => $query->where('kind', $kind)) ->when(array_key_exists($status, StockOrder::STATUS_NAMES), fn (Builder $query) => $query ->whereHas('stockOrders', fn (Builder $query) => $query->where('status', $status))) ->orderBy('article') ->paginate($perPage) ->withQueryString(); return view('stock.index', [ 'active' => 'stock', 'items' => $items, 'search' => $search, 'kind' => $kind, 'status' => $status, 'kinds' => CommonCatalogItem::query() ->whereHas('stockOrders') ->whereNotNull('kind') ->where('kind', '<>', '') ->distinct() ->orderBy('kind') ->pluck('kind'), 'statuses' => StockOrder::STATUS_NAMES, 'per_page' => $perPage, ]); } public function show(CommonCatalogItem $item): View { $item->load([ 'imageFile', 'stockOrders' => fn ($query) => $query ->withSum(['reservations as active_reservations_sum_quantity' => fn ($query) => $query->active()], 'quantity') ->orderByDesc('created_at'), 'stockReservations' => fn ($query) => $query ->with(['manager', 'order']) ->orderByDesc('created_at'), 'stockMovements' => fn ($query) => $query ->with(['user', 'order']) ->orderByDesc('created_at'), ]); return view('stock.show', [ 'active' => 'stock', 'item' => $item, 'summary' => $this->inventoryService->summary($item), 'managers' => User::query()->orderBy('name')->get(), ]); } public function reserve(ReserveStockRequest $request, CommonCatalogItem $item): RedirectResponse { /** @var User $actor */ $actor = $request->user(); $manager = User::query()->findOrFail((int) $request->validated('manager_id')); try { $this->inventoryService->reserve( $item, $manager, (int) $request->validated('quantity'), $request->validated('note'), $actor, ); return back()->with('success', 'Позиция забронирована.'); } catch (DomainException $exception) { return back()->withErrors(['quantity' => $exception->getMessage()])->withInput(); } } public function issue(Request $request, StockReservation $reservation): RedirectResponse { /** @var User $actor */ $actor = $request->user(); try { $this->inventoryService->issue($reservation, $actor, $request->string('note')->toString()); return back()->with('success', 'Бронь списана.'); } catch (DomainException $exception) { return back()->with('error', $exception->getMessage()); } } public function cancel(Request $request, StockReservation $reservation): RedirectResponse { /** @var User $actor */ $actor = $request->user(); try { $this->inventoryService->cancel($reservation, $actor, $request->string('note')->toString()); return back()->with('success', 'Бронь отменена.'); } catch (DomainException $exception) { return back()->with('error', $exception->getMessage()); } } private function perPage(Request $request): int { $allowed = [50, 100, 200, 500, 2000]; $perPage = (int) $request->query('per_page', session('per_page', 50)); $perPage = in_array($perPage, $allowed, true) ? $perPage : 50; session(['per_page' => $perPage]); return $perPage; } }