Entrega dashboard e correccoes transversais preservando Ofertas instalado
tests / ci (push) Waiting to run

This commit is contained in:
2026-11-15 18:56:39 +02:00
parent 1349789226
commit fb076c174f
117 changed files with 10380 additions and 1905 deletions
+42 -11
View File
@@ -68,7 +68,17 @@ final class FundingProgrammes
/** @param array<string,mixed> $filters */
public function catalogueQuery(array $filters): Builder
{
Validator::make($filters, ['q' => 'nullable|string|max:150', 'state' => 'nullable|in:open,closed,upcoming,unknown', 'certainty' => 'nullable|in:include_unknown,documented',
'sector' => ['nullable', 'integer', Rule::in(Sector::choices()->whereNotNull('published_at')->pluck('id')->all())], 'need' => ['nullable', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))], 'include_historical' => 'nullable|boolean'])->validate();
$filters = app(InvestmentGeography::class)->interestSelection($filters);
$query = $this->publicQuery();
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])->orWhereRaw("(v.payload->'aliases')::text ilike ?", [$term])->orWhereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'locations','[]'::jsonb)) l WHERE l->>'label' ilike ? OR l->>'address' ilike ? OR l->>'location_text' ilike ?)", [$term, $term, $term]));
}
if (! empty($filters['kind'])) {
$query->whereRaw("v.payload->>'kind' = ?", [$filters['kind']]);
}
app(FundingInstitutionDetails::class)->applyFilters($query, $filters);
$includeUnknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
if (! empty($filters['sector'])) {
@@ -94,22 +104,43 @@ final class FundingProgrammes
});
}
if (! empty($filters['state'])) {
$today = today()->toDateString();
$state = $filters['state'];
if ($state === 'unknown') {
$query->whereRaw("(jsonb_array_length(v.payload->'windows') = 0 OR EXISTS (SELECT 1 FROM jsonb_array_elements(v.payload->'windows') w WHERE w->>'mode' = 'unknown'))");
} elseif ($state === 'closed') {
$query->whereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(v.payload->'windows') w WHERE w->>'mode' = 'closed' OR (w->>'mode' = 'dated' AND w->>'end_on' < ?))", [$today]);
} elseif ($state === 'upcoming') {
$query->whereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(v.payload->'windows') w WHERE w->>'mode' = 'dated' AND w->>'start_on' > ?)", [$today]);
} else {
$query->whereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(v.payload->'windows') w WHERE w->>'mode' IN ('dated','rolling') AND COALESCE(NULLIF(w->>'start_on',''),?) <= ? AND COALESCE(NULLIF(w->>'end_on',''),?) >= ?)", [$today, $today, $today, $today]);
}
[$windowSql, $windowBindings] = $this->windowFilter($filters);
$query->whereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(v.payload->'windows','[]'::jsonb)) w WHERE ".$windowSql.')', $windowBindings);
}
return $query;
}
/** Window dates and geographical scope concern the same documented window.
* @param array<string,mixed> $filters
* @return array{literal-string,list<mixed>}
*/
private function windowFilter(array $filters): array
{
$today = today()->toDateString();
$state = $filters['state'];
$bindings = [];
if ($state === 'unknown') $sql = "w->>'mode' = 'unknown'";
elseif ($state === 'closed') { $sql = "(w->>'mode' = 'closed' OR (w->>'mode' = 'dated' AND w->>'end_on' < ?))"; $bindings = [$today]; }
elseif ($state === 'upcoming') { $sql = "w->>'mode' = 'dated' AND w->>'start_on' > ?"; $bindings = [$today]; }
else { $sql = "w->>'mode' IN ('dated','rolling') AND COALESCE(NULLIF(w->>'start_on',''),?) <= ? AND COALESCE(NULLIF(w->>'end_on',''),?) >= ?"; $bindings = [$today, $today, $today, $today]; }
if (! empty($filters['territory']) && ($filters['location_mode'] ?? '') !== 'physical') {
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
$window = ["EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(w->'territory_paths','[]'::jsonb)) t WHERE t @> ?::jsonb)"];
$scopeBindings = [json_encode([['id' => $filters['territory']]], JSON_THROW_ON_ERROR)];
foreach ($path as $id) { $window[] = "w->'territory_ids' @> ?::jsonb"; $scopeBindings[] = json_encode([$id], JSON_THROW_ON_ERROR); }
$parent = ["v.payload->>'coverage'='national'", "v.payload->'territory_ancestor_ids' @> ?::jsonb"];
$scopeBindings[] = json_encode([$filters['territory']], JSON_THROW_ON_ERROR);
foreach ($path as $id) { $parent[] = "v.payload->'territory_ids' @> ?::jsonb"; $scopeBindings[] = json_encode([$id], JSON_THROW_ON_ERROR); }
if (($filters['certainty'] ?? 'include_unknown') === 'include_unknown') $parent[] = "v.payload->>'coverage'='unknown'";
$scope = "(w->>'coverage'='national' OR (w->>'coverage'='territorial' AND (".implode(' OR ', $window).")) OR (w->>'coverage'='programme' AND (".implode(' OR ', $parent).')))';
if (($filters['certainty'] ?? 'include_unknown') === 'include_unknown') $scope = '('.$scope." OR w->>'coverage'='unknown')";
$sql = '('.$sql.') AND '.$scope;
$bindings = [...$bindings, ...$scopeBindings];
}
return [$sql, $bindings];
}
/** @param array<string,mixed> $record
* @param array<string,mixed> $filters
* @return array<string,string> */