'spare_parts', 'title' => 'Заказы деталей', 'id' => 'spare_part_orders', 'header' => [ 'id' => 'ID', 'year' => 'Год', 'article' => 'Артикул', 'source_text' => 'Источник заказа', 'status' => 'Статус', 'ordered_quantity' => 'Заказано', 'available_qty' => 'Остаток', 'with_documents' => 'С документами', 'note' => 'Примечание', 'user_name' => 'Менеджер', 'created_at' => 'Дата создания', ], 'searchFields' => [ 'article', 'source_text', 'note', 'user_name', ], 'routeName' => 'spare_part_orders.show', ]; public function __construct( protected SparePartIssueService $issueService ) {} public function index(Request $request) { session(['gp_spare_part_orders' => $request->query()]); $model = new SparePartOrdersView(); // Фильтры $this->createFilters($model, 'year', 'article', 'status', 'with_documents'); $this->createRangeFilters($model, 'ordered_quantity', 'available_qty'); $this->createDateFilters($model, 'created_at'); // Запрос $q = $model::query(); // Специальная обработка фильтров из клика по каталогу if ($request->has('spare_part_id')) { $sparePart = SparePart::find($request->get('spare_part_id')); if ($sparePart) { $q->where('spare_part_id', $sparePart->id); $this->data['filter_spare_part'] = $sparePart; } } if ($request->has('with_documents')) { $withDocs = filter_var($request->get('with_documents'), FILTER_VALIDATE_BOOLEAN); $q->where('with_documents', $withDocs); } if ($request->has('status')) { $q->where('status', $request->get('status')); } if ($request->has('available_qty_min')) { $q->where('available_qty', '>=', $request->get('available_qty_min')); } $this->acceptFilters($q, $request); $this->acceptSearch($q, $request); $this->setSortAndOrderBy($model, $request); $q->orderBy($this->data['sortBy'], $this->data['orderBy']); $this->data['spare_part_orders'] = $q->paginate(session('per_page', config('pagination.per_page')))->withQueryString(); $this->data['strings'] = $this->data['spare_part_orders']; $this->data['tab'] = 'orders'; return view('spare_parts.index', $this->data); } public function show(Request $request, SparePartOrder $sparePartOrder) { $this->data['previous_url'] = $request->get('previous_url'); $this->data['spare_part_order'] = $sparePartOrder->load([ 'sparePart', 'movements.user', 'reservations.reclamation', ]); $this->data['spare_parts'] = SparePart::orderBy('article')->get(); return view('spare_part_orders.edit', $this->data); } public function create() { $this->data['spare_part_order'] = null; $this->data['spare_parts'] = SparePart::orderBy('article')->get(); return view('spare_part_orders.edit', $this->data); } public function store(StoreSparePartOrderRequest $request): RedirectResponse { $data = $request->validated(); $data['user_id'] = auth()->id(); // Observer автоматически обработает дефициты при создании партии со статусом in_stock SparePartOrder::create($data); $previous_url = $request->get('previous_url') ?? route('spare_part_orders.index', session('gp_spare_part_orders')); return redirect()->to($previous_url)->with(['success' => 'Заказ детали успешно создан!']); } public function update(StoreSparePartOrderRequest $request, SparePartOrder $sparePartOrder): RedirectResponse { // Observer автоматически обработает дефициты при смене статуса на in_stock $sparePartOrder->update($request->validated()); $previous_url = $request->get('previous_url') ?? route('spare_part_orders.index', session('gp_spare_part_orders')); return redirect()->to($previous_url)->with(['success' => 'Заказ детали успешно обновлён!']); } public function destroy(SparePartOrder $sparePartOrder): RedirectResponse { // Проверяем, есть ли активные резервы if ($sparePartOrder->reservations()->where('status', 'active')->exists()) { return redirect()->route('spare_part_orders.show', $sparePartOrder) ->with(['error' => 'Невозможно удалить заказ с активными резервами!']); } $sparePartOrder->delete(); return redirect()->route('spare_part_orders.index', session('gp_spare_part_orders')) ->with(['success' => 'Заказ детали успешно удалён!']); } /** * Прямое списание (отгрузка) без резерва */ public function ship(ShipSparePartOrderRequest $request, SparePartOrder $sparePartOrder): RedirectResponse { $validated = $request->validated(); try { $this->issueService->directIssue( $sparePartOrder, $validated['quantity'], $validated['note'] ); return redirect()->route('spare_part_orders.show', $sparePartOrder) ->with(['success' => 'Отгрузка успешно выполнена!']); } catch (\InvalidArgumentException $e) { return redirect()->route('spare_part_orders.show', $sparePartOrder) ->with(['error' => $e->getMessage()]); } } /** * Изменить статус на "На складе" */ public function setInStock(SparePartOrder $sparePartOrder): RedirectResponse { // Observer автоматически обработает дефициты при смене статуса $sparePartOrder->update(['status' => SparePartOrder::STATUS_IN_STOCK]); return redirect()->route('spare_part_orders.show', $sparePartOrder) ->with(['success' => 'Статус изменён на "На складе"!']); } /** * Коррекция остатка (инвентаризация) */ public function correct(Request $request, SparePartOrder $sparePartOrder): RedirectResponse { $validated = $request->validate([ 'new_quantity' => 'required|integer|min:0', 'reason' => 'required|string|max:500', ]); try { $this->issueService->correctInventory( $sparePartOrder, $validated['new_quantity'], $validated['reason'] ); return redirect()->route('spare_part_orders.show', $sparePartOrder) ->with(['success' => 'Коррекция выполнена!']); } catch (\InvalidArgumentException $e) { return redirect()->route('spare_part_orders.show', $sparePartOrder) ->with(['error' => $e->getMessage()]); } } }