*/ private function filters(Request $request): array { $actor = app(SupplierEvaluationWorkflow::class)->reader($request->user()); $request->setUserResolver(fn () => $actor); abort_unless(Schema::hasTable('supplier_improvements'), 503, 'A instalação das medidas de melhoria ainda não está concluída.'); $filters = $request->validate(['supplier_id' => 'nullable|integer|min:1', 'responsible_id' => 'nullable|integer|min:1', 'status' => 'nullable|in:open,review,returned,closed', 'from' => 'nullable|date_format:Y-m-d', 'to' => 'nullable|date_format:Y-m-d', 'overdue' => 'nullable|in:1', 'search' => 'nullable|string|max:200']); if (! empty($filters['from']) && ! empty($filters['to']) && $filters['to'] < $filters['from']) { throw ValidationException::withMessages(['to' => 'O fim do intervalo deve ser igual ou posterior ao início.']); } return $filters; } /** @param array $filters * @return Builder */ private function query(Request $request, array $filters, string $today): Builder { $query = SupplierImprovement::query()->visibleTo($request->user()); if (! empty($filters['supplier_id'])) { $query->whereHas('evaluation', fn ($q) => $q->where('supplier_id', $filters['supplier_id'])); } foreach (['responsible_id', 'status'] as $field) { if (! empty($filters[$field])) { $query->where($field, $filters[$field]); } } foreach (['from' => '>=', 'to' => '<='] as $field => $operator) { if (! empty($filters[$field])) { $query->where('due_on', $operator, $filters[$field]); } } if (! empty($filters['overdue'])) { $query->where('status', '!=', 'closed')->where('due_on', '<', $today); } if (isset($filters['search']) && $filters['search'] !== '') { $query->whereRaw('LOWER(title) LIKE ?', ['%'.mb_strtolower($filters['search']).'%']); } return $query; } /** @param Builder $query * @return Builder */ private function records(Builder $query, Request $request): Builder { return $query->with(['evaluation:id,supplier_id,supplier_snapshot,revision', 'responsible' => fn ($q) => $q->where('tenant_id', $request->user()->tenant_id)->select(['id', 'name'])]); } /** @return array */ private function row(SupplierImprovement $m, string $today): array { return ['id' => $m->id, 'evaluation_id' => $m->supplier_evaluation_id, 'revision' => $m->evaluation?->revision, 'supplier' => $m->evaluation?->supplier_snapshot['name'] ?? 'Fornecedor não disponível', 'responsible' => $m->responsible->name ?? '#'.$m->responsible_id, ...$m->only(['title', 'description', 'due_on', 'status', 'version', 'result', 'evidence', 'review_reason', 'reviewed_at']), 'overdue' => $m->status !== 'closed' && $m->due_on < $today]; } public function index(Request $request): Response { $props = $this->snapshot(function () use ($request): array { $filters = $this->filters($request); $today = today()->toDateString(); $all = SupplierImprovement::query()->visibleTo($request->user()); $query = $this->query($request, $filters, $today); $counts = (clone $query)->selectRaw('status, COUNT(*) AS count')->groupBy('status')->pluck('count', 'status')->map(fn ($n) => (int) $n); $overdue = (clone $query)->where('status', '!=', 'closed')->where('due_on', '<', $today)->count(); // Choices derive only from visible measures, including prior revisions and former assignees. $evaluations = SupplierEvaluation::query()->visibleTo($request->user())->whereIn('id', (clone $all)->select('supplier_evaluation_id'))->orderByDesc('id')->get(['supplier_id', 'supplier_snapshot']); return ['filters' => $filters, 'today' => $today, 'generatedAt' => now()->toIso8601String(), 'counts' => $counts, 'overdue' => $overdue, 'records' => $this->records($query, $request)->orderBy('due_on')->orderBy('id')->paginate(25)->withQueryString()->through(fn (SupplierImprovement $m) => $this->row($m, $today)), 'suppliers' => $evaluations->map(fn (SupplierEvaluation $e) => ['id' => $e->supplier_id, 'name' => $e->supplier_snapshot['name']])->unique('id')->values(), 'responsibles' => User::query()->where('tenant_id', $request->user()->tenant_id)->whereIn('id', (clone $all)->select('responsible_id'))->orderBy('name')->get(['id', 'name'])]; }); Inertia::encryptHistory(); $response = Inertia::render('reports/supplier-improvements', $props)->toResponse($request); $response->headers->set('Cache-Control', 'private, no-store, max-age=0'); return $response; } public function csv(Request $request): Response { $filters = $this->filters($request); $text = fn (?string $value) => preg_match('/^[\s]*[=+\-@]|^[\t\r\n]/u', $value ?? '') ? "'".$value : ($value ?? ''); return response()->streamDownload(fn () => $this->snapshot(function () use ($request, $filters, $text) { // Refresh the profile inside the export snapshot as well. $this->filters($request); $today = today()->toDateString(); $out = fopen('php://output', 'w'); if ($out === false) { throw new \RuntimeException('Não foi possível gerar o ficheiro.'); } fwrite($out, "\xEF\xBB\xBF"); fputcsv($out, ['Medidas de melhoria de fornecedores', 'Consulta', now()->toIso8601String(), 'Data de referência', $today], ';', '"', ''); fputcsv($out, ['Filtros', $text(json_encode($filters, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR))], ';', '"', ''); fputcsv($out, ['Medida', 'Fornecedor preservado', 'Avaliação', 'Revisão', 'Título', 'Objectivo', 'Responsável', 'Prazo', 'Estado', 'Prazo ultrapassado', 'Versão', 'Resultado', 'Evidência', 'Última conferência', 'Conferida em', 'Avaliação e acompanhamento', 'Relatório individual'], ';', '"', ''); foreach ($this->records($this->query($request, $filters, $today), $request)->lazyById(200) as $m) { $row = $this->row($m, $today); $url = url('/fornecedores/avaliacoes/'.$m->supplier_evaluation_id); fputcsv($out, ['MM-'.$m->id, $text($row['supplier']), 'AVF-'.$m->supplier_evaluation_id, $row['revision'], $text($m->title), $text($m->description), $text($row['responsible']), $m->due_on, $m->status, $row['overdue'] ? 'Sim' : 'Não', $m->version, $text($m->result), $text($m->evidence), $text($m->review_reason), $m->reviewed_at?->toIso8601String(), $url.'#medida-'.$m->id, $url.'/relatorio#medida-'.$m->id], ';', '"', ''); } fclose($out); }), 'medidas-fornecedores-'.now()->format('Ymd-His').'.csv', ['Content-Type' => 'text/csv; charset=UTF-8', 'Cache-Control' => 'private, no-store, max-age=0']); } }