user('web'); abort_unless($actor instanceof User, 403); return $actor; } private function currentActor(Request $request, PurchasingRole $role): User { $actor = $this->actor($request); app(ConflictGuard::class)->lockCompany($actor); return app(CurrentActor::class)->lock($actor, [$role]); } public function index(Request $request): Response { abort_unless($this->actor($request)->hasFunctions([PurchasingRole::Admin, PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]), 403); $filters = $request->validate(['search' => 'nullable|string|max:100']); $query = Supplier::query(); if (! empty($filters['search'])) { $query->where(fn ($q) => $q->where('name', 'like', '%'.$filters['search'].'%')->orWhere('nuit', 'like', '%'.$filters['search'].'%')); } return Inertia::render('suppliers/index', ['suppliers' => $query->orderBy('name')->paginate(15)->withQueryString(), 'filters' => $filters, 'canCreate' => app(SupplierParticipation::class)->canPrepare($this->actor($request)), 'canReview' => app(SupplierParticipation::class)->canReviewRole($this->actor($request))]); } public function store(SaveSupplierRequest $request, RequisitionWorkflow $workflow): RedirectResponse { $data = $request->validated(); DB::transaction(function () use ($request, $data, $workflow) { $actor = $this->currentActor($request, PurchasingRole::Procurement); abort_unless(app(SupplierParticipation::class)->canPrepare($actor), 403); $supplier = Supplier::query()->create([...$data, 'tenant_id' => $actor->tenant_id, 'created_by' => $actor->id, 'last_changed_by' => $actor->id, 'status' => 'pending']); $workflow->audit($actor, 'supplier.created', null, null, $supplier->toArray()); }); return back(); } public function show(Request $request, Supplier $supplier): HttpResponse { Gate::authorize('view', $supplier); $canDocuments = $this->actor($request)->can('viewDocuments', $supplier); Inertia::encryptHistory(); $response = Inertia::render('suppliers/show', [ 'supplier' => $supplier, 'documents' => $canDocuments ? $supplier->documents()->latest('id')->get() : [], 'canEdit' => $this->actor($request)->can('update', $supplier), 'canDocuments' => $canDocuments, 'canReview' => app(SupplierParticipation::class)->canReview($this->actor($request), $supplier), ])->toResponse($request); $response->headers->set('Cache-Control', 'private, no-store, max-age=0'); return $response; } public function edit(Supplier $supplier): Response { Gate::authorize('update', $supplier); return Inertia::render('suppliers/edit', ['supplier' => $supplier]); } public function update(SaveSupplierRequest $request, Supplier $supplier, RequisitionWorkflow $workflow): RedirectResponse { Gate::authorize('update', $supplier); $data = $request->validated(); DB::transaction(function () use ($request, $supplier, $workflow, $data) { $actor = $this->currentActor($request, PurchasingRole::Procurement); abort_unless(app(SupplierParticipation::class)->canPrepare($actor), 403); $supplier = Supplier::query()->lockForUpdate()->findOrFail($supplier->id); if ($supplier->version !== (int) $data['version']) { throw ValidationException::withMessages(['version' => 'A ficha foi alterada por outro utilizador. Reabra a edição para carregar os dados actuais.']); } $before = $supplier->toArray(); unset($data['version']); $supplier->fill($data); if ($supplier->isDirty()) { $supplier->fill(['status' => 'pending', 'reviewed_by' => null, 'review_comment' => null, 'last_changed_by' => $actor->id, 'version' => $supplier->version + 1])->save(); $workflow->audit($actor, 'supplier.updated', null, $before, $supplier->toArray()); } }); return to_route('suppliers.show', $supplier); } public function review(Request $request, Supplier $supplier, RequisitionWorkflow $workflow): RedirectResponse { abort_unless($this->actor($request)->hasFunctions([PurchasingRole::Finance]), 403); $data = $request->validate(['status' => ['required', Rule::in(['active', 'suspended'])], 'comment' => 'required|string|min:10|max:2000', 'version' => 'required|integer|min:1']); DB::transaction(function () use ($request, $supplier, $data, $workflow) { $actor = $this->currentActor($request, PurchasingRole::Finance); $supplier = Supplier::query()->lockForUpdate()->findOrFail($supplier->id); abort_unless(app(SupplierParticipation::class)->canReview($actor, $supplier), 403); if ($supplier->version !== (int) $data['version']) { throw ValidationException::withMessages(['version' => 'A ficha ou os documentos foram alterados. Reabra a ficha antes de validar.']); } if ($data['status'] === 'active' && ($supplier->blocked || $supplier->next_review < today())) { throw ValidationException::withMessages(['status' => 'Fornecedor impedido ou com revisão vencida. Não pode ser activado.']); } $before = $supplier->toArray(); $supplier->update(['status' => $data['status'], 'review_comment' => $data['comment'], 'reviewed_by' => $actor->id, 'version' => $supplier->version + 1]); $workflow->audit($actor, 'supplier.reviewed', null, $before, $supplier->toArray()); }); return back(); } }