getDriverName() === 'pgsql' && DB::transactionLevel() !== 0) { throw new \LogicException('O relatório exige uma transacção própria de leitura.'); } return DB::transaction(function () use ($read) { if (DB::connection()->getDriverName() === 'pgsql') { DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY'); } return $read(); }); } /** @return array */ private function filters(Request $request): array { $actor = User::query()->findOrFail($request->user()->id); abort_unless($actor->tenant_id === $request->user()->tenant_id && $actor->hasFunctions([PurchasingRole::Admin, PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]), 403); $request->setUserResolver(fn () => $actor); $filters = $request->validate([ 'search' => 'nullable|string|max:100', 'status' => 'nullable|in:draft,pending,approved,rejected,cancelled', 'from' => 'nullable|date_format:Y-m-d', 'to' => 'nullable|date_format:Y-m-d', 'department_id' => 'nullable|integer|min:1', ]); if (! empty($filters['from']) && ! empty($filters['to']) && $filters['from'] > $filters['to']) { throw ValidationException::withMessages(['to' => 'A data final deve ser igual ou posterior à data inicial.']); } return $filters; } /** @param array $filters * @return Builder */ private function query(User $actor, array $filters): Builder { $query = Requisition::query()->where('tenant_id', $actor->tenant_id)->visibleTo($actor); if (isset($filters['search']) && $filters['search'] !== '') { $query->where('number', 'like', '%'.$filters['search'].'%'); } foreach (['status', 'department_id'] as $field) { if (! empty($filters[$field])) { $query->where($field, $filters[$field]); } } if (! empty($filters['from'])) { $query->where('created_at', '>=', $filters['from'].' 00:00:00'); } if (! empty($filters['to'])) { $query->where('created_at', '<', (new \DateTimeImmutable($filters['to']))->modify('+1 day')->format('Y-m-d').' 00:00:00'); } return $query; } /** @param Builder $query * @return Builder */ private function records(Builder $query): Builder { return $query->select('requisitions.*')->with(['department:id,name', 'requester:id,name'])->addSelect([ 'quotations_count' => Quotation::query()->selectRaw('COUNT(*)')->whereColumn('requisition_id', 'requisitions.id'), 'order_number' => PurchaseOrder::query()->visibleTo(request()->user())->select('number')->whereColumn('requisition_id', 'requisitions.id')->orderBy('id')->limit(1), ]); } /** @return array */ private function row(Requisition $record): array { return [ 'id' => $record->id, 'number' => $record->number, 'status' => $record->status, 'created_at' => $record->created_at?->toIso8601String(), 'department' => $record->department?->name, 'requester' => $record->requester?->name, 'total_minor' => (string) $record->total_minor, 'quotations_count' => (int) $record->getAttribute('quotations_count'), 'order_number' => $record->getAttribute('order_number'), ]; } public function index(Request $request): Response { $props = $this->snapshot(function () use ($request): array { $filters = $this->filters($request); $query = $this->query($request->user(), $filters); $counts = (clone $query)->selectRaw('status, COUNT(*) AS count')->groupBy('status')->pluck('count', 'status')->map(fn ($value) => (int) $value); $records = $this->records(clone $query)->orderByDesc('id')->paginate(25)->withQueryString()->through(fn (Requisition $record) => $this->row($record)); $departments = Department::query()->when($request->user()->departmentScoped(), fn ($q) => $q->whereKey($request->user()->department_id))->orderBy('name')->get(['id', 'name']); return ['records' => $records, 'counts' => $counts, 'filters' => $filters, 'departments' => $departments, 'generatedAt' => now()->toIso8601String()]; }); Inertia::encryptHistory(); $response = Inertia::render('reports/procurement', $props)->toResponse($request); $response->headers->set('Cache-Control', 'private, no-store, max-age=0'); return $response; } public function export(Request $request): Response { $filters = $this->filters($request); $query = $this->records($this->query($request->user(), $filters)); $text = fn (?string $value) => preg_match('/^[\s]*[=+\-@]|^[\t\r\n]/u', $value ?? '') ? "'".$value : ($value ?? ''); return response()->streamDownload(fn () => $this->snapshot(function () use ($query, $text, $filters) { $output = fopen('php://output', 'w'); if ($output === false) { throw new \RuntimeException('Não foi possível gerar o ficheiro.'); } fwrite($output, "\xEF\xBB\xBF"); fputcsv($output, ['Relatório de compras', 'Criado em', now()->toIso8601String()], ';', '"', ''); fputcsv($output, ['Filtros', $text(json_encode($filters, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR))], ';', '"', ''); fputcsv($output, ['Requisição', 'Criada em', 'Departamento', 'Solicitante', 'Estado da requisição', 'Estimativa total (centavos MZN)', 'Propostas registadas', 'Ordem de compra', 'Processo', 'Relatório de conferência'], ';', '"', ''); foreach ($query->lazyById(200) as $record) { $row = $this->row($record); $base = url('/requisicoes/'.$record->id); fputcsv($output, [$text($record->number), $row['created_at'], $text($row['department']), $text($row['requester']), $record->status, $row['total_minor'], $row['quotations_count'], $text($row['order_number']), $base.'/processo', $base.'/relatorio'], ';', '"', ''); } fclose($output); }), 'relatorio-compras-'.now()->format('Ymd-His').'.csv', ['Content-Type' => 'text/csv; charset=UTF-8', 'Cache-Control' => 'private, no-store, max-age=0']); } }