*/ private function payload(Request $request): array { return $request->validate([ 'payload' => 'required|array:title,nature,scope,period_start,period_end,order_ids,criteria,conclusion,critical_occurrences,critical_evidence', 'payload.title' => 'required|string|min:3|max:200', 'payload.nature' => 'required|in:goods,services', 'payload.scope' => 'nullable|string|max:10000', 'payload.period_start' => 'nullable|date_format:Y-m-d', 'payload.period_end' => 'nullable|date_format:Y-m-d', 'payload.order_ids' => 'present|array|max:50', 'payload.order_ids.*' => 'integer|distinct|min:1', 'payload.criteria' => 'present|array|max:30', 'payload.criteria.*' => 'required|array:label,result,reason,evidence', 'payload.criteria.*.label' => 'nullable|string|max:300', 'payload.criteria.*.result' => 'required|in:pending,observed,insufficient,not_applicable', 'payload.criteria.*.reason' => 'nullable|string|max:4000', 'payload.criteria.*.evidence' => 'nullable|string|max:4000', 'payload.conclusion' => 'nullable|string|max:10000', 'payload.critical_occurrences' => 'nullable|string|max:4000', 'payload.critical_evidence' => 'nullable|string|max:4000', 'tenant_id' => 'prohibited', 'created_by' => 'prohibited', 'status' => 'prohibited', 'contributors' => 'prohibited', 'supplier_snapshot' => 'prohibited', ])['payload']; } public function index(Request $request, SupplierEvaluationWorkflow $workflow): Response { $actor = $workflow->reader($request->user()); $filters = $request->validate(['supplier_id' => 'nullable|integer|min:1', 'status' => 'nullable|in:draft,review,returned,approved']); $ready = Schema::hasTable('supplier_evaluations'); $records = $ready ? SupplierEvaluation::query()->visibleTo($actor) ->when($filters['supplier_id'] ?? null, fn ($q, $id) => $q->where('supplier_id', $id)) ->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status)) ->latest('id')->paginate(20)->withQueryString()->through(fn (SupplierEvaluation $e) => [ ...$e->only(['id', 'status', 'revision', 'version', 'previous_id']), 'supplier' => $e->supplier_snapshot['name'], 'title' => $e->payload['title'], 'period_start' => $e->payload['period_start'], 'period_end' => $e->payload['period_end'], ]) : null; return $this->render($request, 'suppliers/evaluations', ['ready' => $ready, 'records' => $records, 'filters' => $filters, 'suppliers' => Supplier::query()->orderBy('name')->get(['id', 'name', 'nuit']), 'orders' => PurchaseOrder::query()->visibleTo($actor)->orderByDesc('id')->get(['id', 'number', 'supplier_id'])]); } public function show(Request $request, int $evaluation, SupplierEvaluationWorkflow $workflow): Response { return $this->view($request, $evaluation, $workflow, false); } public function report(Request $request, int $evaluation, SupplierEvaluationWorkflow $workflow): Response { return $this->view($request, $evaluation, $workflow, true); } private function view(Request $request, int $id, SupplierEvaluationWorkflow $workflow, bool $report): Response { $actor = $workflow->reader($request->user()); $this->ready(); $evaluation = SupplierEvaluation::query()->visibleTo($actor)->findOrFail($id); $improvementWorkflow = app(SupplierImprovementWorkflow::class); $measuresReady = Schema::hasTable('supplier_improvements'); $measures = $measuresReady ? SupplierImprovement::query()->where('supplier_evaluation_id', $id)->orderBy('due_on')->orderBy('id')->get() : collect(); $measureHistory = $measuresReady ? AuditLog::query()->where('action', 'like', 'supplier_improvement.%')->where('after->supplier_evaluation_id', $id)->latest('id')->paginate(10, ['id', 'action', 'user_id', 'after', 'created_at'], 'measures_history_page')->withQueryString() : null; $responsesReady = Schema::hasTable('supplier_responses'); $responses = $responsesReady ? SupplierResponse::query()->where('supplier_evaluation_id', $id)->orderBy('id')->get() : collect(); $responseHistory = $responsesReady ? AuditLog::query()->where('action', 'like', 'supplier_response.%')->where('after->supplier_evaluation_id', $id)->latest('id')->paginate(10, ['id', 'action', 'user_id', 'after', 'created_at'], 'responses_history_page')->withQueryString() : null; $history = AuditLog::query()->where('action', 'like', 'supplier_evaluation.%')->where('after->evaluation_id', $id)->latest('id')->paginate(10, ['id', 'action', 'user_id', 'after', 'created_at'], 'history_page')->withQueryString(); $peopleIds = collect($evaluation->contributors)->merge($history->pluck('user_id'))->merge($measures->flatMap(fn (SupplierImprovement $m) => $m->contributors))->merge($measures->pluck('reviewed_by'))->merge($measureHistory?->pluck('user_id') ?? []); $peopleIds = $peopleIds->merge($responses->flatMap(fn (SupplierResponse $r) => $r->contributors))->merge($responses->pluck('reviewed_by'))->merge($responseHistory?->pluck('user_id') ?? []); $people = User::query()->where('tenant_id', $actor->tenant_id)->whereIn('id', $peopleIds)->pluck('name', 'id'); $writable = $improvementWorkflow->canParticipate($actor, $evaluation); $next = SupplierEvaluation::query()->visibleTo($actor)->where('previous_id', $id)->value('id'); return $this->render($request, 'suppliers/evaluation', ['evaluation' => $workflow->snapshot($evaluation), 'reportOnly' => $report, 'canEdit' => ! $report && $writable && in_array($evaluation->status, ['draft', 'returned'], true), 'canReview' => ! $report && $writable && $evaluation->status === 'review' && ! in_array($actor->id, $evaluation->contributors, true), 'canRevise' => ! $report && $writable && $evaluation->status === 'approved' && $next === null, 'nextRevisionId' => $next, 'history' => $history, 'people' => $people, 'responses' => ['ready' => $responsesReady, 'canCreate' => ! $report && $responsesReady && $writable && $evaluation->status === 'approved', 'history' => $responseHistory, 'records' => $responses->map(fn (SupplierResponse $r) => [...app(SupplierResponseWorkflow::class)->snapshot($r), 'canEdit' => ! $report && $writable && in_array($r->status, ['draft', 'returned'], true), 'canReview' => ! $report && $writable && $r->status === 'review' && ! in_array($actor->id, $r->contributors, true)])], 'improvements' => ['ready' => $measuresReady, 'canCreate' => ! $report && $measuresReady && $writable && $evaluation->status === 'approved', 'today' => today()->toDateString(), 'records' => $measures->map(fn (SupplierImprovement $m) => [...$improvementWorkflow->snapshot($m), 'canPlan' => ! $report && $writable && in_array($m->status, ['open', 'returned'], true), 'canExecute' => ! $report && $writable && $actor->id === $m->responsible_id && in_array($m->status, ['open', 'returned'], true), 'canReview' => ! $report && $writable && $m->status === 'review' && ! in_array($actor->id, $m->contributors, true)]), 'history' => $measureHistory, 'assignees' => $report ? [] : User::query()->where('tenant_id', $actor->tenant_id)->orderBy('name')->get()->filter(fn (User $u) => $u->hasFunctions(SupplierEvaluationWorkflow::ROLES) && (! $u->departmentScoped() || ($u->department_id !== null && $u->department_id === $evaluation->department_id)))->map(fn (User $u) => $u->only(['id', 'name']))->values()], 'orders' => PurchaseOrder::query()->visibleTo($actor)->where('supplier_id', $evaluation->supplier_id)->orderByDesc('id')->get(['id', 'number', 'supplier_id']), 'generatedAt' => now()->toIso8601String()]); } /** @param array $props */ private function render(Request $request, string $component, array $props): Response { Inertia::encryptHistory(); $response = Inertia::render($component, [...$props, 'notice' => $request->session()->get('supplier_evaluation_notice')])->toResponse($request); $response->headers->set('Cache-Control', 'private, no-store, max-age=0'); return $response; } public function store(Request $request, SupplierEvaluationWorkflow $workflow): RedirectResponse { $this->ready(); $data = $request->validate(['supplier_id' => 'required|integer|min:1', 'operation_key' => 'required|uuid']); $evaluation = $workflow->create($request->user(), (int) $data['supplier_id'], $data['operation_key'], $this->payload($request)); return to_route('supplier-evaluations.show', $evaluation->id)->with('supplier_evaluation_notice', 'Rascunho guardado. Os dados podem ser recuperados nesta avaliação.'); } public function update(Request $request, int $evaluation, SupplierEvaluationWorkflow $workflow): RedirectResponse { $this->ready(); $data = $request->validate(['version' => 'required|integer|min:1']); $workflow->save($request->user(), $evaluation, (int) $data['version'], $this->payload($request)); return to_route('supplier-evaluations.show', $evaluation)->with('supplier_evaluation_notice', 'Alterações guardadas no histórico.'); } public function transition(Request $request, int $evaluation, string $action, SupplierEvaluationWorkflow $workflow): RedirectResponse { $this->ready(); $data = $request->validate(['version' => 'required|integer|min:1', 'reason' => $action === 'submit' ? 'nullable|string|max:4000' : 'required|string|min:10|max:4000', 'payload' => 'prohibited', 'supplier_id' => 'prohibited', 'tenant_id' => 'prohibited', 'reviewed_by' => 'prohibited']); $workflow->transition($request->user(), $evaluation, (int) $data['version'], $action, $data['reason'] ?? null); return to_route('supplier-evaluations.show', $evaluation)->with('supplier_evaluation_notice', 'Estado e decisão registados.'); } public function revise(Request $request, int $evaluation, SupplierEvaluationWorkflow $workflow): RedirectResponse { $this->ready(); $data = $request->validate(['version' => 'required|integer|min:1']); $revision = $workflow->revise($request->user(), $evaluation, (int) $data['version']); return to_route('supplier-evaluations.show', $revision->id)->with('supplier_evaluation_notice', 'Nova revisão preparada. A avaliação aprovada anterior mantém-se preservada.'); } }