$references->preview($request->user(), $code)])->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function prepareReference(Request $request, string $code, FundingProgrammeReferences $references): RedirectResponse { $data = $request->validate(['hash' => 'required|string|size:64']); $result = $references->prepare($request->user(), $code, $data['hash']); return redirect('/management/fundos/'.$result['id'])->with('success', $result['created'] ? 'Rascunho preparado com as fontes indicadas. Ainda não está publicado.' : 'Esta referência já tem ficha. Aberta a versão existente, sem substituir alterações ou acrescentar chamadas.'); } public function manage(Request $request, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response { $service->guard($request->user()); $items = DB::table('funding_programmes')->orderByDesc('updated_at')->paginate(20); $items->through(fn ($row) => ['id' => $row->id, 'title' => json_decode($row->draft, true)['title'] ?? 'Rascunho sem título', 'version' => (int) $row->version, 'published' => $row->published_version_id !== null]); return tap(Inertia::render('funding/manage', ['items' => $items])->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function edit(Request $request, FundingProgrammes $service, ?string $id = null): \Symfony\Component\HttpFoundation\Response { $service->guard($request->user()); $row = $id ? DB::table('funding_programmes')->where('id', $id)->firstOrFail() : null; $editable = app(DocumentManagementAccess::class)->documents($request->user())->select('id'); $references = ['organizations' => DB::table('organizations')->whereNull('deleted_at')->orderBy('name')->get(['id', 'name']), 'sectors' => Sector::choices()->whereNotNull('published_at')->orderBy('name')->get(['id', 'name']), 'territories' => app(InvestmentGeography::class)->publicReferences()->orderBy('name')->get(['id', 'name']), 'documents' => DB::table('portal_resources as r')->join('documents as d', 'd.id', '=', 'r.document_id') ->whereNull('d.deleted_at')->where(fn ($q) => $q->whereIn('d.id', $editable) ->orWhereIn('r.id', app(Procurement::class)->publicDocuments()->select('r.id'))) ->orderBy('r.title')->get(['r.id', 'r.title'])]; return tap(Inertia::render('funding/edit', ['record' => $row ? ['id' => $row->id, 'code' => $row->code, 'version' => (int) $row->version, 'draft' => json_decode($row->draft, true), 'published' => $row->published_version_id !== null] : null, 'references' => Arr::only($references, ['organizations', 'sectors', 'territories', 'documents']), 'kinds' => FundingProgrammes::KINDS, 'windowModes' => FundingProgrammes::WINDOWS, 'history' => $id ? DB::table('funding_programme_versions')->where('programme_id', $id)->orderByDesc('version')->get(['version', 'reason', 'published_at']) : []])->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function save(Request $request, FundingProgrammes $service, ?string $id = null): RedirectResponse { $id = $service->save($request->user(), $id, $request->all()); return redirect('/management/fundos/'.$id)->with('success', 'Rascunho guardado. Confira a prévia antes de publicar.'); } public function preview(Request $request, string $id, FundingProgrammes $service): JsonResponse { return response()->json($service->preview($request->user(), $id))->header('Cache-Control', 'private, no-store'); } public function publish(Request $request, string $id, FundingProgrammes $service): RedirectResponse { $service->publish($request->user(), $id, $request->all()); return back()->with('success', 'Nova versão publicada. Os interesses anteriores conservam a versão guardada.'); } public function withdraw(Request $request, string $id, FundingProgrammes $service): RedirectResponse { $data = $request->validate(['version' => 'required|integer|min:1', 'reason' => 'required|string|min:20|max:2000']); $service->withdraw($request->user(), $id, $data['version'], $data['reason']); return back()->with('success', 'Publicação retirada. Versões e interesses preservados.'); } public function saved(Request $request, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response { abort_unless($service->installed(), 404); $items = DB::table('funding_programme_interests as i') ->join('funding_programme_versions as v', fn ($join) => $join->on('v.id', '=', 'i.version_id')->on('v.programme_id', '=', 'i.programme_id')) ->join('funding_programmes as p', 'p.id', '=', 'i.programme_id') ->where('i.user_id', $request->user()->id)->whereNull('i.removed_at') ->orderByDesc('i.saved_at')->orderBy('i.programme_id') ->paginate(20, ['i.programme_id', 'i.saved_at', 'v.version', 'v.payload', 'p.published_version_id'])->withQueryString(); $items->through(function ($row) use ($service) { $current = $service->publicQuery()->where('p.id', $row->programme_id)->first(['v.version']); $snapshot = json_decode($row->payload, true, 512, JSON_THROW_ON_ERROR); return ['id' => $row->programme_id, 'title' => $snapshot['title'], 'saved_version' => (int) $row->version, 'saved_at' => $row->saved_at, 'available' => $current !== null, 'current_version' => $current ? (int) $current->version : null]; }); return tap(Inertia::render('funding/saved', ['items' => $items])->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function index(Request $request, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response { $filters = $request->validate(['q' => 'nullable|string|max:150', 'kind' => 'nullable|in:fund,programme,incentive', 'territory' => ['nullable', 'uuid', Rule::in(app(InvestmentGeography::class)->publicReferences()->pluck('id')->all())], 'sector' => ['nullable', 'integer', Rule::in(Sector::choices()->whereNotNull('published_at')->pluck('id')->all())], 'certainty' => 'nullable|in:include_unknown,documented', 'state' => 'nullable|in:open,closed,upcoming,unknown']); $filters = array_filter($filters, fn ($value) => $value !== null && $value !== ''); $query = $service->catalogueQuery($filters); if (! empty($filters['q'])) { $term = '%'.str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $filters['q']).'%'; $query->where(fn ($q) => $q->whereRaw("v.payload->>'title' ilike ?", [$term])->orWhereRaw("v.payload->>'summary' ilike ?", [$term])); } if (! empty($filters['kind'])) { $query->whereRaw("v.payload->>'kind' = ?", [$filters['kind']]); } $items = $query->orderByDesc('v.published_at')->orderBy('p.id')->paginate(20, ['p.id', 'p.code', 'v.id as version_id', 'v.version', 'v.payload', 'v.published_at'])->withQueryString(); $items->through(function ($row) use ($service, $filters) { $record = $service->present($row); return [...$record, 'filter_explanation' => $service->filterExplanation($record, $filters)]; }); return tap(Inertia::render('funding/index', ['items' => $items, 'filters' => $filters, 'kinds' => FundingProgrammes::KINDS, 'options' => ['territories' => app(InvestmentGeography::class)->publicReferences()->orderBy('name')->get(['id', 'name']), 'sectors' => Sector::choices()->whereNotNull('published_at')->orderBy('name')->get(['id', 'name'])], 'management' => $service->access($request->user())]) ->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function show(Request $request, string $id, FundingProgrammes $service): \Symfony\Component\HttpFoundation\Response { $record = $service->published($id); $saved = $request->user() ? DB::table('funding_programme_interests as i')->join('funding_programme_versions as v', 'v.id', '=', 'i.version_id') ->where('i.programme_id', $id)->where('i.user_id', $request->user()->id)->whereNull('i.removed_at')->first(['v.version', 'i.saved_at']) : null; return tap(Inertia::render('funding/show', ['record' => $record, 'saved' => $saved, 'authenticated' => $request->user() !== null]) ->toResponse($request), static fn (\Symfony\Component\HttpFoundation\Response $response) => $response->headers->set('Cache-Control', 'private, no-store')); } public function assess(Request $request, string $id, FundingProgrammes $service): JsonResponse { return response()->json($service->assess($id, $request->all()))->header('Cache-Control', 'private, no-store'); } public function saveInterest(Request $request, string $id, FundingProgrammes $service): RedirectResponse { $data = $request->validate(['version_id' => 'required|uuid', 'consent' => 'required|accepted']); $service->saveInterest($request->user(), $id, $data['version_id'], true); return back()->with('success', 'Programa guardado na sua área privada. Consulte a versão indicada na ficha.'); } public function removeInterest(Request $request, string $id, FundingProgrammes $service): RedirectResponse { abort_unless($service->installed(), 404); $service->removeInterest($request->user(), $id); return back()->with('success', 'Interesse removido da sua lista.'); } public function download(string $id, string $version, int $resource, FundingProgrammes $service): Response|BinaryFileResponse { return $service->download($id, $version, $resource); } }