validate(['search' => 'nullable|string|max:100', 'status' => ['nullable', Rule::in(['draft', 'pending', 'approved', 'rejected', 'cancelled'])], 'view' => ['nullable', Rule::in(['all', 'mine', 'approvals', 'decided'])]]); $query = Requisition::query()->visibleTo($request->user())->with(['requester:id,name', 'department:id,name', 'steps']); if (($filters['view'] ?? 'all') === 'mine') { $query->where('requester_id', $request->user()->id); } elseif (($filters['view'] ?? 'all') === 'approvals') { $eligible = (clone $query)->where('status', 'pending')->get() ->filter(fn (Requisition $requisition) => $requisition->canDecide($request->user()))->modelKeys(); $query->whereIn('id', $eligible); } elseif (($filters['view'] ?? 'all') === 'decided') { $query->whereHas('steps', fn ($steps) => $steps->where('approver_id', $request->user()->id)->whereIn('decision', ['approved', 'rejected'])); } if (! empty($filters['search'])) { $query->where('number', 'like', '%'.$filters['search'].'%'); } if (! empty($filters['status'])) { $query->where('status', $filters['status']); } return Inertia::render('requisitions/index', [ 'requisitions' => $query->latest()->paginate(15)->withQueryString(), 'filters' => $filters, ]); } public function create(Request $request): Response { abort_unless(app(OrganizationAccess::class)->canRequest($request->user()), 403); return Inertia::render('requisitions/form', [ 'costCenters' => CostCenter::query()->where('department_id', $request->user()->department_id)->orderBy('code')->get(), 'requisition' => null, 'defaultTaxRate' => (string) (Tenant::query()->findOrFail($request->user()->tenant_id)->default_tax_rate ?? '16.00'), ...$this->equipmentOptions($request), 'catalogue' => $this->catalogueOptions($request), ]); } public function store(StoreRequisitionRequest $request, RequisitionWorkflow $workflow): RedirectResponse { $requisition = $workflow->save($request->user(), $request->validated()); return to_route('requisitions.show', $requisition); } public function show(Request $request, Requisition $requisition): Response { Requisition::query()->visibleTo($request->user())->findOrFail($requisition->id); $requisition->load(['items', 'steps', 'requester:id,name', 'department:id,name', 'costCenter']); return Inertia::render('requisitions/show', [ 'requisition' => $requisition, 'approvalRouting' => app(ApprovalRouting::class)->current($requisition, $request->user()), 'canRefreshRouting' => $requisition->status === 'pending' && $request->user()->hasFunctions(['admin', 'procurement']) && ! app(OrganizationAccess::class)->maintenanceOnly($request->user()) && Schema::hasTable('workflow_notices'), 'replenishmentSource' => app(SupplySuggestions::class)->origin($requisition), 'comparisonPolicy' => app(ComparisonPolicy::class)->forRequest($requisition), 'canEdit' => app(OrganizationAccess::class)->canRequest($request->user()) && $requisition->requester_id === $request->user()->id && $requisition->status === 'draft', 'canDecide' => $requisition->canDecide($request->user()), 'conflictBlocked' => app(ConflictGuard::class)->participationBlocked($request->user(), $requisition), 'audit' => AuditLog::query()->where('requisition_id', $requisition->id)->orderBy('id')->get(['id', 'action', 'user_id', 'created_at']), 'technical' => [ 'canReview' => app(EquipmentWorkflow::class)->canReview($request->user(), $requisition), 'basis_hash' => app(EquipmentWorkflow::class)->basis($requisition), 'reviews' => TechnicalReview::query()->where('requisition_id', $requisition->id)->orderBy('id')->get(), ], ]); } public function edit(Request $request, Requisition $requisition): Response { abort_unless(app(OrganizationAccess::class)->canRequest($request->user()), 403); abort_unless($requisition->requester_id === $request->user()->id && $requisition->status === 'draft', 403); return Inertia::render('requisitions/form', [ 'requisition' => $requisition->load('items'), 'defaultTaxRate' => (string) (Tenant::query()->findOrFail($request->user()->tenant_id)->default_tax_rate ?? '16.00'), 'costCenters' => CostCenter::query()->where('department_id', $request->user()->department_id)->orderBy('code')->get(), ...$this->equipmentOptions($request), 'catalogue' => $this->catalogueOptions($request), ]); } /** @return array */ private function equipmentOptions(Request $request): array { $equipment = Equipment::query()->where('department_id', $request->user()->department_id)->orderBy('code')->get(['id', 'code', 'description']); $parts = SparePart::query()->whereIn('id', EquipmentSparePart::query()->whereIn('equipment_id', $equipment->pluck('id'))->select('spare_part_id'))->get(['id', 'stock_item_id']); return ['equipment' => $equipment, 'spareParts' => $parts, 'stockItems' => StockItem::query()->whereIn('id', $parts->pluck('stock_item_id'))->get(['id', 'code', 'name', 'unit'])]; } /** @return array */ private function catalogueOptions(Request $request): array { $search = $request->validate(['catalogo' => 'nullable|string|max:100'])['catalogo'] ?? ''; return app(RequisitionCatalogue::class)->options($request->user(), $search); } public function technical(Request $request, Requisition $requisition): RedirectResponse { $data = $request->validate(['basis_hash' => 'required|string|size:64', 'decision' => ['required', Rule::in(['approved', 'rejected'])], 'reason' => 'required|string|max:4000']); app(EquipmentWorkflow::class)->review($request->user(), $requisition, $data['basis_hash'], $data['decision'], $data['reason']); return back(); } public function update(StoreRequisitionRequest $request, Requisition $requisition, RequisitionWorkflow $workflow): RedirectResponse { $workflow->save($request->user(), $request->validated(), $requisition); return to_route('requisitions.show', $requisition); } public function submit(Request $request, Requisition $requisition, RequisitionWorkflow $workflow): RedirectResponse { $command = $request->validate(['operation_key' => 'nullable|uuid', 'process_version' => 'nullable|integer|min:1']); $workflow->submit($request->user(), $requisition, $command['operation_key'] ?? null, $command['process_version'] ?? null); return back(); } public function decide(Request $request, Requisition $requisition, RequisitionWorkflow $workflow): RedirectResponse { $data = $request->validate([ 'decision' => ['required', Rule::in(['approved', 'rejected'])], 'comment' => ['nullable', 'required_if:decision,rejected', 'string', 'max:2000'], 'operation_key' => 'nullable|uuid', 'process_version' => 'nullable|integer|min:1', ]); $workflow->decide($request->user(), $requisition, $data['decision'], $data['comment'] ?? null, $data['operation_key'] ?? null, $data['process_version'] ?? null); return back(); } public function dashboard(Request $request): Response { $actor = $request->user('web'); abort_unless($actor instanceof User, 403); $contexts = app(WorkContext::class); if ($contexts->enforced($actor)) { return Inertia::render('work-context', ['context' => $contexts->shared($request)]); } $visible = Requisition::query()->visibleTo($request->user()); $pending = (clone $visible)->where('status', 'pending')->with('steps')->get(); return Inertia::render('dashboard', [ 'company' => $request->user()->tenant_id ? Tenant::query()->find($request->user()->tenant_id) : null, 'stats' => [ 'total' => (clone $visible)->count(), 'pending' => $pending->count(), 'approved' => (clone $visible)->where('status', 'approved')->count(), 'approvedMinor' => (clone $visible)->where('status', 'approved')->sum('total_minor'), 'awaitingMe' => $pending->filter(fn (Requisition $r) => $r->canDecide($request->user()))->count(), ], 'recent' => (clone $visible)->with(['requester:id,name', 'department:id,name'])->latest()->limit(6)->get(), 'approvals' => $pending->filter(fn (Requisition $r) => $r->canDecide($request->user()))->take(5)->values(), ]); } }