'reclamations', 'title' => 'Рекламации', 'id' => 'reclamations', 'header' => [ 'id' => 'ID', 'user_name' => 'Менеджер', 'status_name' => 'Статус', 'district_name' => 'Округ', 'area_name' => 'Район', 'object_address' => 'Адрес объекта', 'create_date' => 'Дата создания', 'finish_date' => 'Дата завершения', 'start_work_date' => 'Дата начала работ', 'work_days' => 'Срок работ, дней', 'brigadier_name' => 'Бригадир', 'reason' => 'Причина', 'guarantee' => 'Гарантии', 'whats_done' => 'Что сделано', 'comment' => 'Комментарий', ], 'searchFields' => [ 'reason', 'guarantee', 'whats_done', 'comment', ], 'ranges' => [], ]; public function __construct() { $this->data['users'] = User::query()->whereIn('role', [Role::MANAGER, Role::ADMIN])->get()->pluck('name', 'id'); $this->data['statuses'] = ReclamationStatus::query()->get()->pluck('name', 'id'); } public function index(Request $request) { session(['gp_reclamations' => $request->all()]); $model = new ReclamationView(); // fill filters $this->createFilters($model, 'user_name', 'status_name'); $this->createDateFilters($model, 'create_date', 'finish_date'); $q = $model::query(); $this->acceptFilters($q, $request); $this->acceptSearch($q, $request); $this->setSortAndOrderBy($model, $request); $q->orderBy($this->data['sortBy'], $this->data['orderBy']); $this->data['reclamations'] = $q->paginate(session('per_page', config('pagination.per_page')))->withQueryString(); return view('reclamations.index', $this->data); } public function create(CreateReclamationRequest $request, Order $order) { $reclamation = Reclamation::query()->create([ 'order_id' => $order->id, 'user_id' => $request->user()->id, 'status_id' => Reclamation::STATUS_NEW, 'create_date' => Carbon::now(), 'finish_date' => Carbon::now()->addDays(30), ]); $skus = $request->validated('skus'); $reclamation->skus()->attach($skus); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => url()->previous()]); } public function show(Request $request, Reclamation $reclamation) { $this->data['brigadiers'] = User::query()->where('role', Role::BRIGADIER)->get()->pluck('name', 'id'); $this->data['reclamation'] = $reclamation; $this->data['previous_url'] = $request->get('previous_url'); return view('reclamations.edit', $this->data); } public function update(StoreReclamationRequest $request, Reclamation $reclamation) { $data = $request->validated(); $reclamation->update($data); return redirect()->route('reclamations.show', $reclamation->id); } public function delete(Reclamation $reclamation) { $reclamation->delete(); return redirect()->route('reclamations.index'); } public function uploadPhotoBefore(Request $request, Reclamation $reclamation, FileService $fileService) { $data = $request->validate([ 'photo.*' => 'mimes:jpeg,jpg,png|max:8192', ]); $f = []; foreach ($data['photo'] as $photo) { $f[] = $fileService->saveUploadedFile('reclamations/' . $reclamation->id . '/photo_before', $photo); } $reclamation->photos_before()->syncWithoutDetaching($f); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function uploadPhotoAfter(Request $request, Reclamation $reclamation, FileService $fileService) { $data = $request->validate([ 'photo.*' => 'mimes:jpeg,jpg,png|max:8192', ]); $f = []; foreach ($data['photo'] as $photo) { $f[] = $fileService->saveUploadedFile('reclamations/' . $reclamation->id . '/photo_after', $photo); } $reclamation->photos_after()->syncWithoutDetaching($f); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function deletePhotoBefore(Request $request, Reclamation $reclamation, File $file, FileService $fileService) { $reclamation->photos_before()->detach($file); Storage::disk('public')->delete($file->path); $file->delete(); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function deletePhotoAfter(Request $request, Reclamation $reclamation, File $file, FileService $fileService) { $reclamation->photos_after()->detach($file); Storage::disk('public')->delete($file->path); $file->delete(); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function uploadDocument(Request $request, Reclamation $reclamation, FileService $fileService) { $data = $request->validate([ 'document.*' => 'file', ]); $f = []; $i = 0; foreach ($data['document'] as $document) { if($i++ >= 5) break; $f[] = $fileService->saveUploadedFile('reclamations/' . $reclamation->id . '/document', $document); } $reclamation->documents()->syncWithoutDetaching($f); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function deleteDocument(Request $request, Reclamation $reclamation, File $file) { $reclamation->documents()->detach($file); Storage::disk('public')->delete($file->path); $file->delete(); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function uploadAct(Request $request, Reclamation $reclamation, FileService $fileService) { $data = $request->validate([ 'acts.*' => 'file', ]); $f = []; $i = 0; foreach ($data['acts'] as $document) { if($i++ >= 5) break; $f[] = $fileService->saveUploadedFile('reclamations/' . $reclamation->id . '/act', $document); } $reclamation->acts()->syncWithoutDetaching($f); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function deleteAct(Request $request, Reclamation $reclamation, File $file) { $reclamation->acts()->detach($file); Storage::disk('public')->delete($file->path); $file->delete(); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function updateDetails(StoreReclamationDetailsRequest $request, Reclamation $reclamation) { $names = $request->validated('name'); $quantity = $request->validated('quantity'); $withDocuments = $request->validated('with_documents'); $reservationService = app(SparePartReservationService::class); foreach ($names as $key => $name) { if (!$name) continue; if ((int)$quantity[$key] >= 1) { // Проверяем, является ли это запчастью $sparePart = \App\Models\SparePart::where('article', $name)->first(); if ($sparePart) { // Резервирование вместо прямого списания $withDocs = isset($withDocuments[$key]) && $withDocuments[$key]; $qty = (int)$quantity[$key]; // Получаем текущее количество в pivot $currentPivot = $reclamation->spareParts()->find($sparePart->id); $currentQty = $currentPivot?->pivot->quantity ?? 0; $diff = $qty - $currentQty; if ($diff > 0) { // Нужно зарезервировать дополнительное количество $result = $reservationService->reserve( $sparePart->id, $diff, $withDocs, $reclamation->id ); // Обновляем pivot с учётом результата $reclamation->spareParts()->syncWithoutDetaching([ $sparePart->id => [ 'quantity' => $qty, 'with_documents' => $withDocs, 'status' => $result->isFullyReserved() ? 'reserved' : 'pending', 'reserved_qty' => $currentQty + $result->reserved, ] ]); } elseif ($diff < 0) { // Уменьшение — отменяем часть резерва $reservationService->adjustReservation( $reclamation->id, $sparePart->id, $withDocs, $qty ); $reclamation->spareParts()->syncWithoutDetaching([ $sparePart->id => [ 'quantity' => $qty, 'with_documents' => $withDocs, 'reserved_qty' => $qty, ] ]); } else { // Количество не изменилось, возможно изменился with_documents $reclamation->spareParts()->syncWithoutDetaching([ $sparePart->id => [ 'quantity' => $qty, 'with_documents' => $withDocs, ] ]); } } else { // Обычная деталь ReclamationDetail::query()->updateOrCreate( ['reclamation_id' => $reclamation->id, 'name' => $name], ['quantity' => $quantity[$key]] ); } } else { // Удаление // Проверяем, является ли это запчастью — отменяем резервы $sparePartToRemove = \App\Models\SparePart::where('article', $name)->first(); if ($sparePartToRemove) { // Отменяем все резервы для этой запчасти в рекламации $reservationService->cancelForReclamation( $reclamation->id, $sparePartToRemove->id ); // Удаляем связь $reclamation->spareParts()->detach($sparePartToRemove->id); } else { // Обычная деталь ReclamationDetail::query() ->where('reclamation_id', $reclamation->id) ->where('name', $name) ->delete(); } } } return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function updateSpareParts(StoreReclamationSparePartsRequest $request, Reclamation $reclamation) { $rows = $request->validated('rows') ?? []; $reservationService = app(SparePartReservationService::class); // Получаем текущие привязки для сравнения $currentSpareParts = $reclamation->spareParts->keyBy('id'); // Определяем какие запчасти были удалены $newSparePartIds = collect($rows)->pluck('spare_part_id')->filter()->toArray(); $removedIds = $currentSpareParts->keys()->diff($newSparePartIds); // Отменяем резервы для удалённых запчастей foreach ($removedIds as $removedId) { $current = $currentSpareParts->get($removedId); if ($current) { $reservationService->cancelForReclamation( $reclamation->id, $removedId, $current->pivot->with_documents ); } } // Собираем новые привязки $newSpareParts = []; foreach ($rows as $row) { $sparePartId = $row['spare_part_id'] ?? null; if (empty($sparePartId)) continue; $quantity = (int)($row['quantity'] ?? 0); if ($quantity < 1) continue; $withDocs = !empty($row['with_documents']) && $row['with_documents'] != '0'; // Проверяем, изменилось ли количество $currentQty = $currentSpareParts->get($sparePartId)?->pivot->quantity ?? 0; $currentReserved = $currentSpareParts->get($sparePartId)?->pivot->reserved_qty ?? 0; $diff = $quantity - $currentQty; $status = 'pending'; $reservedQty = $currentReserved; if ($diff > 0) { // Нужно зарезервировать дополнительное количество $result = $reservationService->reserve( $sparePartId, $diff, $withDocs, $reclamation->id ); $reservedQty = $currentReserved + $result->reserved; $status = $reservedQty >= $quantity ? 'reserved' : 'pending'; } elseif ($diff < 0) { // Уменьшение — отменяем часть резерва $reservationService->adjustReservation( $reclamation->id, $sparePartId, $withDocs, $quantity ); $reservedQty = $quantity; $status = 'reserved'; } else { // Количество не изменилось $status = $currentReserved >= $quantity ? 'reserved' : 'pending'; } $newSpareParts[$sparePartId] = [ 'quantity' => $quantity, 'with_documents' => $withDocs, 'status' => $status, 'reserved_qty' => $reservedQty, ]; } // Синхронизируем (заменяем все старые привязки новыми) $reclamation->spareParts()->sync($newSpareParts); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]); } public function generateReclamationPack(Request $request, Reclamation $reclamation) { GenerateReclamationPack::dispatch($reclamation, auth()->user()->id); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]) ->with(['success' => 'Задача генерации документов создана!']); } public function generatePhotosBeforePack(Request $request, Reclamation $reclamation) { GenerateFilesPack::dispatch($reclamation, $reclamation->photos_before, auth()->user()->id, 'Фото проблемы'); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]) ->with(['success' => 'Задача архивации создана!']); } public function generatePhotosAfterPack(Request $request, Reclamation $reclamation) { GenerateFilesPack::dispatch($reclamation, $reclamation->photos_after, auth()->user()->id, 'Фото после'); return redirect()->route('reclamations.show', ['reclamation' => $reclamation, 'previous_url' => $request->get('previous_url')]) ->with(['success' => 'Задача архивации создана!']); } }