commit16
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\Opportunity;
|
||||
use App\Services\InvestmentCatalogue;
|
||||
use App\Services\OpportunityMaturity;
|
||||
use App\Services\OpportunityTextSearch;
|
||||
use App\Services\SectorOrganization;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -41,16 +42,7 @@ class OpportunityController extends Controller
|
||||
}
|
||||
|
||||
if (! empty($validated['q'])) {
|
||||
$term = $validated['q'];
|
||||
$query->where(fn ($q) => $q
|
||||
->whereLike('title', "%{$term}%")
|
||||
->orWhereLike('description', "%{$term}%")
|
||||
->orWhereLike('summary', "%{$term}%")
|
||||
->orWhereLike('reference', "%{$term}%")
|
||||
->orWhereLike('province', "%{$term}%")
|
||||
->orWhereHas('sector', fn ($sectorQuery) => $sectorQuery
|
||||
->whereLike('name', "%{$term}%")
|
||||
->orWhereLike('slug', "%{$term}%")));
|
||||
app(OpportunityTextSearch::class)->constrain($query, $validated['q']);
|
||||
}
|
||||
|
||||
if (! empty($validated['sector'])) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\DeskServiceEditor;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
final class DeskServiceEditorController extends Controller
|
||||
{
|
||||
public function index(Request $request, DeskServiceEditor $editor): Response|JsonResponse
|
||||
{
|
||||
$data = ['services' => $editor->listing()];
|
||||
|
||||
return $request->expectsJson() ? response()->json($data) : Inertia::render('desk/service-editor', $data);
|
||||
}
|
||||
|
||||
public function publish(Request $request, DeskServiceEditor $editor, string $code): JsonResponse|RedirectResponse
|
||||
{
|
||||
$row = $editor->publish($request->user(), $code, $request->all());
|
||||
|
||||
return $request->expectsJson() ? response()->json(['id' => $row->id, 'version' => $row->version], 201)
|
||||
: back()->with('success', 'Nova versão publicada para pedidos futuros. Os pedidos existentes conservam a sua versão.');
|
||||
}
|
||||
|
||||
public function history(Request $request, DeskServiceEditor $editor, string $code): JsonResponse
|
||||
{
|
||||
$data = $request->validate(['page' => ['nullable', 'integer', 'min:1']]);
|
||||
|
||||
return response()->json($editor->history($code, (int) ($data['page'] ?? 1)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Sector;
|
||||
use App\Services\DocumentManagementAccess;
|
||||
use App\Services\FundingProgrammeReferences;
|
||||
use App\Services\FundingProgrammes;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\Procurement;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
final class FundingProgrammeController extends Controller
|
||||
{
|
||||
public function reference(Request $request, string $code, FundingProgrammeReferences $references): \Symfony\Component\HttpFoundation\Response
|
||||
{
|
||||
return tap(Inertia::render('funding/reference', ['reference' => $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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\InvestmentOpportunityAlerts;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
final class InvestmentAlertPreferencesController extends Controller
|
||||
{
|
||||
public function save(Request $request, InvestmentOpportunityAlerts $alerts): JsonResponse|RedirectResponse
|
||||
{
|
||||
$preferences = $alerts->save($request->user(), $request->all());
|
||||
|
||||
return $request->expectsJson() ? response()->json($preferences) : back()->with('success', $preferences['enabled']
|
||||
? 'Preferências guardadas. O acompanhamento considera novas disponibilizações nos catálogos escolhidos.'
|
||||
: 'Alertas dos catálogos cancelados. As notificações anteriores foram conservadas.');
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Services\InvestmentComplements;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\InvestmentZones;
|
||||
use App\Services\OpportunityCorridorExplorer;
|
||||
use App\Services\OpportunityTextSearch;
|
||||
use App\Services\SectorInvestment;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -29,12 +30,20 @@ class InvestmentDiscoveryController extends Controller
|
||||
if ($request->routeIs('investment.education')) {
|
||||
$filters['education'] = true;
|
||||
}
|
||||
$results = $catalogue->query($filters)->paginate(12)->withQueryString()->through(fn ($record) => $catalogue->presentLocation($record));
|
||||
$relatedQuery = $results->total() === 0 ? app(OpportunityTextSearch::class)->relatedQuery($filters['q'] ?? '') : null;
|
||||
$relatedFilters = [...Arr::except($filters, ['page', 'q']), 'q' => $relatedQuery];
|
||||
$related = $relatedQuery === null ? null : [
|
||||
'query' => $filters['q'], 'subject' => $relatedQuery,
|
||||
'href' => ($request->routeIs('investment.education') ? '/educacao' : '/oportunidades').'?'.http_build_query($relatedFilters),
|
||||
'items' => $catalogue->query($relatedFilters)->limit(6)->get()->map(fn ($record) => $catalogue->presentLocation($record)),
|
||||
];
|
||||
|
||||
return Inertia::render('investment/catalogue', ['filters' => $filters, 'catalogueUrl' => $request->routeIs('investment.education') ? '/educacao' : '/oportunidades', 'references' => $catalogue->references(false, $filters),
|
||||
'corridorGroups' => ($filters['view'] ?? '') === 'corridors' ? app(OpportunityCorridorExplorer::class)->groups($filters) : [],
|
||||
'territorySelection' => app(InvestmentGeography::class)->selection($filters),
|
||||
'guides' => app(SectorInvestment::class)->guides(Sector::where('slug', $filters['sector'] ?? '')->where('is_active', true)->whereNotNull('published_at')->first()),
|
||||
'results' => $catalogue->query($filters)->paginate(12)->withQueryString()->through(fn ($record) => $catalogue->presentLocation($record)),
|
||||
'results' => $results, 'relatedSearch' => $related,
|
||||
'favourites' => $request->user() ? DB::table('opportunity_favourites')->where('user_id', $request->user()->id)->pluck('opportunity_id') : [],
|
||||
'territorial' => $this->territorial($filters)]);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,17 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InvestmentSubmission;
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\Sector;
|
||||
use App\Models\User;
|
||||
use App\Services\CountryCatalogue;
|
||||
use App\Services\FundingProgrammes;
|
||||
use App\Services\InvestmentGeography;
|
||||
use App\Services\InvestmentOpportunityAlerts;
|
||||
use App\Services\InvestmentWorkspace;
|
||||
use App\Services\OpportunityManagement;
|
||||
use App\Services\OrganizationCountry;
|
||||
use App\Services\Procurement;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -119,14 +126,38 @@ class InvestmentWorkspaceController extends Controller
|
||||
public function notifications(Request $request): Response|JsonResponse
|
||||
{
|
||||
$query = DB::table('investment_notifications')->where('user_id', $request->user()->id);
|
||||
if (! $request->user()->can('manage-portal')) {
|
||||
$alerts = app(InvestmentOpportunityAlerts::class);
|
||||
$available = $alerts->installed();
|
||||
if ($available) {
|
||||
$query->where(function ($sources) use ($request, $alerts) {
|
||||
$sources->whereIn('opportunity_id', Opportunity::published()->select('id'))
|
||||
->orWhere(function ($legacy) use ($request) {
|
||||
$legacy->whereNotNull('submission_id');
|
||||
if (! $request->user()->can('manage-portal')) {
|
||||
$legacy->whereIn('submission_id', InvestmentSubmission::where('user_id', $request->user()->id)->select('id'));
|
||||
}
|
||||
});
|
||||
if ($alerts->tendersAvailable()) {
|
||||
$sources->orWhereIn('tender_version_id', app(Procurement::class)->readableVersions()->select('id'));
|
||||
}
|
||||
if ($alerts->fundingAvailable()) {
|
||||
$sources->orWhereIn('funding_version_id', app(FundingProgrammes::class)->readableVersions()->select('id'));
|
||||
}
|
||||
});
|
||||
} elseif (! $request->user()->can('manage-portal')) {
|
||||
$query->whereIn('submission_id', InvestmentSubmission::where('user_id', $request->user()->id)->select('id'));
|
||||
}
|
||||
$data = ['unread' => (clone $query)->whereNull('read_at')->count(), 'notifications' => $query->orderByDesc('created_at')->paginate(20)->through(function ($row) {
|
||||
$row->payload = json_decode(Crypt::decryptString($row->payload), true, 512, JSON_THROW_ON_ERROR);
|
||||
$data = ['alertPreferences' => $available ? $alerts->preferences($request->user()->id) : null,
|
||||
'alertOptions' => $available ? ['frequencies' => InvestmentOpportunityAlerts::FREQUENCIES,
|
||||
'sectors' => Sector::choices()->whereNotNull('published_at')->orderBy('name')->get(['slug', 'name']),
|
||||
'provinces' => app(InvestmentGeography::class)->publicReferences()->where('location_type', 'province')->orderBy('name')->get(['id', 'name']),
|
||||
'categories' => OpportunityManagement::CATEGORIES, 'tendersAvailable' => $alerts->tendersAvailable(), 'fundingAvailable' => $alerts->fundingAvailable(),
|
||||
'tenderRegimes' => config('procurement.regimes'), 'tenderActs' => config('procurement.acts')] : null,
|
||||
'unread' => (clone $query)->whereNull('read_at')->count(), 'notifications' => $query->orderByDesc('created_at')->orderByDesc('id')->paginate(20)->through(function ($row) {
|
||||
$row->payload = json_decode(Crypt::decryptString($row->payload), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
return $row;
|
||||
})];
|
||||
return $row;
|
||||
})];
|
||||
|
||||
return $request->expectsJson() ? response()->json($data) : Inertia::render('investment/notifications', $data);
|
||||
}
|
||||
@@ -135,7 +166,17 @@ class InvestmentWorkspaceController extends Controller
|
||||
{
|
||||
$row = DB::table('investment_notifications')->where('id', $id)->where('user_id', $request->user()->id)->first();
|
||||
abort_unless($row !== null, 404);
|
||||
$this->submission($request, $row->submission_id);
|
||||
if ($row->opportunity_id ?? null) {
|
||||
abort_unless(Opportunity::published()->whereKey($row->opportunity_id)->exists(), 404);
|
||||
} elseif ($row->tender_version_id ?? null) {
|
||||
abort_unless(app(InvestmentOpportunityAlerts::class)->tendersAvailable()
|
||||
&& app(Procurement::class)->readableVersions()->where('id', $row->tender_version_id)->exists(), 404);
|
||||
} elseif ($row->funding_version_id ?? null) {
|
||||
abort_unless(app(InvestmentOpportunityAlerts::class)->fundingAvailable()
|
||||
&& app(FundingProgrammes::class)->readableVersions()->where('id', $row->funding_version_id)->exists(), 404);
|
||||
} else {
|
||||
$this->submission($request, $row->submission_id);
|
||||
}
|
||||
DB::table('investment_notifications')->where('id', $id)->whereNull('read_at')->update(['read_at' => now()]);
|
||||
|
||||
return $request->expectsJson() ? response()->json(['read' => true]) : back();
|
||||
|
||||
@@ -13,37 +13,16 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ProcurementController extends Controller
|
||||
{
|
||||
private const STATE_SQL = "CASE WHEN v.payload->>'procedural_state' IN ('cancelled','results') THEN v.payload->>'procedural_state' WHEN ((v.payload->>'deadline')::timestamp AT TIME ZONE (v.payload->>'timezone'))<=CURRENT_TIMESTAMP THEN 'deadline_ended' WHEN v.payload->>'extended'='true' THEN 'extended' ELSE 'open' END";
|
||||
|
||||
private function query(Procurement $service): Builder
|
||||
{
|
||||
abort_unless($service->available(), 404);
|
||||
|
||||
return DB::table('portal_tenders as t')->join('portal_tender_versions as v', fn ($q) => $q->on('v.id', '=', 't.published_version_id')->on('v.tender_id', '=', 't.id'))->whereNotNull('v.published_at');
|
||||
return $service->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 = $this->query($service);
|
||||
if (! empty($filters['q'])) {
|
||||
$query->where(fn ($q) => $q->whereRaw("v.payload->>'title' ILIKE ?", ['%'.$filters['q'].'%'])->orWhereRaw("v.payload->>'object' ILIKE ?", ['%'.$filters['q'].'%'])->orWhereRaw("v.payload->>'reference' ILIKE ?", ['%'.$filters['q'].'%']));
|
||||
}
|
||||
foreach (['entity' => 'organization_id', 'sector' => 'sector_id', 'modality' => 'modality', 'financier' => 'financier', 'regime' => 'regime'] as $filter => $field) {
|
||||
if (! empty($filters[$filter])) {
|
||||
$query->whereRaw('v.payload->>? = ?', [$field, (string) $filters[$filter]]);
|
||||
}
|
||||
}
|
||||
if (! empty($filters['territory'])) {
|
||||
$query->whereRaw("v.payload->>'territory_id' IN (WITH RECURSIVE scope(id,depth) AS (SELECT id,0 FROM geo_locations WHERE id=? AND deleted_at IS NULL UNION ALL SELECT c.id,s.depth+1 FROM geo_locations c JOIN scope s ON c.parent_id=s.id WHERE c.deleted_at IS NULL AND s.depth<4) SELECT id::text FROM scope)", [$filters['territory']]);
|
||||
}
|
||||
if (! empty($filters['state'])) {
|
||||
$query->whereRaw(self::STATE_SQL.' = ?', [$filters['state']]);
|
||||
}
|
||||
if (! empty($filters['deadline_before'])) {
|
||||
$query->whereRaw("left(v.payload->>'deadline',10)<=?", [$filters['deadline_before']]);
|
||||
}
|
||||
$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));
|
||||
|
||||
@@ -87,8 +87,17 @@ class PublicBusinessOpportunityController extends Controller
|
||||
}
|
||||
|
||||
$returnFilters = $this->filters($request);
|
||||
$similarProvince = $record->getAttribute('similar_province_id');
|
||||
if ($similarProvince !== null && ! app(InvestmentGeography::class)->publicReferences()->where('geo_locations.id', $similarProvince)->exists()) {
|
||||
$similarProvince = null;
|
||||
}
|
||||
|
||||
return Inertia::render('investment/business-opportunity', ['record' => $resource->resolve($request),
|
||||
'similarCatalogueUrl' => '/negocios?'.http_build_query(array_filter([
|
||||
'modality' => $record->modality,
|
||||
'sector' => $record->sector_id,
|
||||
'province_id' => $similarProvince,
|
||||
], fn ($value) => $value !== null && $value !== '')),
|
||||
'returnUrl' => $returnFilters === [] ? '/negocios' : '/negocios?'.http_build_query($returnFilters),
|
||||
'interestUrl' => '/negocios/'.rawurlencode($slug).'/manifestar-interesse'.($returnFilters === [] ? '' : '?'.http_build_query($returnFilters))]);
|
||||
}
|
||||
@@ -116,7 +125,7 @@ class PublicBusinessOpportunityController extends Controller
|
||||
->leftJoin('geo_locations as locality', 'locality.id', '=', 'locations.locality_id')
|
||||
->where('locations.is_public', true)->where('locations.is_primary', true)
|
||||
->whereNull('province.deleted_at')->whereNull('district.deleted_at')->whereNull('locality.deleted_at')
|
||||
->select('locations.existing_business_id', 'province.name as province')
|
||||
->select('locations.existing_business_id', 'province.name as province', 'province.id as similar_province_id')
|
||||
->selectRaw("CASE WHEN locations.precision_level IN ('district','admin_post','locality','exact') THEN district.name ELSE NULL END AS district")
|
||||
->selectRaw('NULL::text AS locality');
|
||||
|
||||
@@ -127,6 +136,6 @@ class PublicBusinessOpportunityController extends Controller
|
||||
'existing_businesses.currency', 'existing_businesses.amount_min', 'existing_businesses.amount_max',
|
||||
'existing_businesses.available_percentage', 'existing_businesses.published_at', 'existing_businesses.sector_id',
|
||||
'existing_businesses.owner_user_id', 'existing_businesses.organization_id', 'existing_businesses.version_no', 'existing_businesses.expires_at', 'existing_businesses.status', 'existing_businesses.modality',
|
||||
'public_location.province', 'public_location.district', 'public_location.locality']);
|
||||
'public_location.province', 'public_location.district', 'public_location.locality', 'public_location.similar_province_id']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ class VirtualDeskController extends Controller
|
||||
'serviceSource' => json_decode(DB::table('desk_service_versions')->where('id', $row->service_version_id)->value('source'), true),
|
||||
'owner' => $desk->owns($request->user(), $case), 'manager' => app(ScopedPortalAdministration::class)->deskDecision($request->user(), $row), 'analyst' => (int) $row->analyst_id === $request->user()->id,
|
||||
'fieldsEditable' => in_array($row->status, ['draft', 'returned'], true), 'states' => config('virtual-desk.request_states'), 'transitions' => config('virtual-desk.transitions.'.$row->status, []),
|
||||
'findings' => $desk->findings($payload), 'ai' => config('virtual-desk.ai'), 'documents' => $this->documents($case->id, $request->user())->filter(fn ($d) => ! $d->request_id || $d->request_id === $id)->values(),
|
||||
'findings' => $desk->findings($payload, $definition), 'ai' => config('virtual-desk.ai'), 'documents' => $this->documents($case->id, $request->user())->filter(fn ($d) => ! $d->request_id || $d->request_id === $id)->values(),
|
||||
'messages' => DB::table('desk_messages as m')->join('users as u', 'u.id', '=', 'm.actor_id')->where('request_id', $id)->orderBy('m.created_at')->get(['m.id', 'm.actor_id', 'm.body', 'm.created_at', 'u.name'])->map(fn ($m) => [...(array) $m, 'body' => Crypt::decryptString($m->body),
|
||||
'kind' => $messageKinds[$m->id] ?? 'historical_unclassified']),
|
||||
'events' => $this->events($case->id, $desk, $request->user())->filter(fn ($e) => $e['request_id'] === $id)->values(),
|
||||
@@ -245,7 +245,10 @@ class VirtualDeskController extends Controller
|
||||
$this->auditRead($request, 'desk_requests', $id, 'receipt_downloaded');
|
||||
|
||||
if ($format === 'html') {
|
||||
return response()->view('desk.receipt', ['record' => $row, 'snapshot' => $snapshot, 'definition' => $desk->definition($row)], 200, ['Cache-Control' => 'private, no-store', 'X-Content-Type-Options' => 'nosniff']);
|
||||
$definition = $desk->definition($row);
|
||||
|
||||
return response()->view('desk.receipt', ['record' => $row, 'snapshot' => $snapshot, 'definition' => $definition,
|
||||
'applicableFields' => $desk->applicableFields($snapshot['payload'] ?? [], $definition)], 200, ['Cache-Control' => 'private, no-store', 'X-Content-Type-Options' => 'nosniff']);
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($row, $snapshot) {
|
||||
|
||||
Reference in New Issue
Block a user