publicQuery(); } public function index(Request $request, Procurement $service): Response { $filters = $request->validate(['q' => 'nullable|string|max:150', 'entity' => 'nullable|uuid', 'sector' => 'nullable|integer', 'territory' => 'nullable|uuid', 'modality' => 'nullable|string|max:200', 'financier' => 'nullable|string|max:200', 'state' => 'nullable|in:open,extended,deadline_ended,cancelled,results', 'deadline_before' => 'nullable|date_format:Y-m-d', 'regime' => 'nullable|in:state,external,private,other']); $filters = array_filter($filters, fn ($value) => $value !== null && $value !== ''); $query = $service->publicQuery($filters); $rows = $query->select('t.id', 'v.id as vid')->orderByDesc('v.published_at')->paginate(20)->withQueryString(); $rows->through(fn ($r) => ['id' => $r->id, ...$this->summary($service, $r->vid)]); $facets = $this->query($service)->get(['v.payload'])->map(fn ($r) => json_decode($r->payload, true)); $options = []; foreach (['entity' => ['organization_id', 'organization_name'], 'sector' => ['sector_id', 'sector_name'], 'territory' => ['territory_id', 'territory_name'], 'modality' => ['modality', 'modality'], 'financier' => ['financier', 'financier']] as $name => [$value,$label]) { $options[$name] = $facets->filter(fn ($p) => ! empty($p[$value]))->map(fn ($p) => ['id' => (string) $p[$value], 'name' => $p[$label]])->unique('id')->values(); } return tap(Inertia::render('procurement/index', ['items' => $rows, 'filters' => $filters, 'options' => $options, 'regimes' => config('procurement.regimes'), 'management' => $service->access($request->user())])->toResponse($request), static fn (Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } /** * @return array */ private function summary(Procurement $service, string $vid): array { $v = $service->publishedVersion($vid); return ['publication' => $v, 'state' => $service->state($v)]; } public function show(Request $request, string $id, Procurement $service): Response { return $this->detail($request, $id, $service, false); } public function print(Request $request, string $id, Procurement $service): Response { return $this->detail($request, $id, $service, true); } private function detail(Request $request, string $id, Procurement $service, bool $print): Response { $row = $this->query($service)->where('t.id', $id)->firstOrFail(['t.id', 'v.id as vid']); $filters = $request->only(['q', 'entity', 'sector', 'territory', 'modality', 'financier', 'state', 'deadline_before', 'regime', 'page']); return tap(Inertia::render('procurement/show', ['id' => $id, ...$this->summary($service, $row->vid), 'printMode' => $print, 'backUrl' => '/concursos'.($filters ? '?'.http_build_query($filters) : ''), 'history' => DB::table('portal_tender_versions')->where('tender_id', $id)->whereNotNull('published_at')->orderBy('version')->pluck('id')->map(fn ($vid) => $service->publishedVersion($vid)), 'watching' => $request->user() && DB::table('portal_tender_watches')->where('tender_id', $id)->where('user_id', $request->user()->id)->exists()])->toResponse($request), static fn (Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function manage(Request $request, Procurement $service): Response { $service->guard($request->user()); return tap(Inertia::render('procurement/manage', ['items' => DB::table('portal_tenders')->orderByDesc('updated_at')->get(['id', 'draft', 'version', 'published_version_id'])->map(fn ($r) => ['id' => $r->id, 'title' => json_decode($r->draft, true)['title'] ?? 'Rascunho sem título', 'version' => $r->version, 'published' => (bool) $r->published_version_id])])->toResponse($request), static fn (Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function edit(Request $request, Procurement $service, ?string $id = null): Response { $service->guard($request->user()); $r = $id ? $service->record($id) : null; return tap(Inertia::render('procurement/edit', ['record' => $r ? ['id' => $id, 'version' => $r->version, 'draft' => json_decode($r->draft, true), 'published' => (bool) $r->published_version_id] : null, 'references' => $service->references($request->user()), 'history' => $id ? DB::table('portal_tender_versions')->where('tender_id', $id)->orderByDesc('version')->get(['version', 'act', 'reason', 'actor_id', 'published_at', 'created_at']) : []])->toResponse($request), static fn (Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function save(Request $request, Procurement $service, ?string $id = null): RedirectResponse { $id = $service->save($request->user(), $id, $request->all()); return redirect('/management/concursos/'.$id)->with('success', 'Rascunho guardado. A publicação anterior permanece intacta.'); } public function preview(Request $request, string $id, Procurement $service): JsonResponse { return response()->json($service->preview($request->user(), $id, (string) $request->input('act')))->header('Cache-Control', 'private, no-store'); } public function publish(Request $request, string $id, Procurement $service): RedirectResponse { $service->publish($request->user(), $id, $request->all()); return redirect('/management/concursos/'.$id)->with('success', 'Acto publicado, com versão e auditoria. Nenhuma proposta recebida pelo portal.'); } public function watch(Request $request, string $id, Procurement $service): RedirectResponse { $row = $this->query($service)->where('t.id', $id)->firstOrFail(['v.id']); DB::table('portal_tender_watches')->updateOrInsert(['tender_id' => $id, 'user_id' => $request->user()->id], ['seen_version_id' => $row->id, 'created_at' => now(), 'updated_at' => now()]); return back()->with('success', 'Concurso guardado para acompanhamento. Isto não apresenta uma proposta.'); } public function unwatch(Request $request, string $id, Procurement $service): RedirectResponse { abort_unless($service->available(), 404); DB::table('portal_tender_watches')->where('tender_id', $id)->where('user_id', $request->user()->id)->delete(); return back(); } public function watched(Request $request, Procurement $service): Response { $rows = $this->query($service)->join('portal_tender_watches as w', 'w.tender_id', '=', 't.id')->where('w.user_id', $request->user()->id)->get(['t.id', 'v.id as vid', 'w.seen_version_id'])->map(fn ($r) => ['id' => $r->id, 'changed' => $r->seen_version_id !== $r->vid, ...$this->summary($service, $r->vid)]); return tap(Inertia::render('procurement/manage', ['watched' => true, 'items' => $rows])->toResponse($request), static fn (Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } }