.
This commit is contained in:
@@ -18,7 +18,7 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
/** Informational funding catalogue, separate from applications, credit decisions and fiscal calculations. */
|
||||
final class FundingProgrammes
|
||||
{
|
||||
public const KINDS = ['fund' => 'Fundo', 'programme' => 'Programa de apoio', 'incentive' => 'Incentivo'];
|
||||
public const KINDS = ['institution' => 'Instituição financeira', 'fund' => 'Fundo', 'programme' => 'Programa de apoio', 'incentive' => 'Incentivo'];
|
||||
|
||||
public const WINDOWS = ['dated' => 'Datas documentadas', 'rolling' => 'Recepção contínua documentada', 'unknown' => 'Janela por confirmar', 'closed' => 'Encerrada segundo a fonte'];
|
||||
|
||||
@@ -38,6 +38,19 @@ final class FundingProgrammes
|
||||
abort_unless($this->access($actor), 403);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $reference */
|
||||
public function matchesReference(User $actor, string $id, array $reference): bool
|
||||
{
|
||||
$this->guard($actor);
|
||||
$row = DB::table('funding_programmes')->where('id', $id)->firstOrFail();
|
||||
$expected = $this->normalize($actor, $reference, false);
|
||||
$actual = json_decode($row->draft, true, 512, JSON_THROW_ON_ERROR);
|
||||
abort_unless(is_array($actual), 503);
|
||||
|
||||
return json_encode(Arr::sortRecursive($actual), JSON_THROW_ON_ERROR)
|
||||
=== json_encode(Arr::sortRecursive($expected), JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
public function publicQuery(): Builder
|
||||
{
|
||||
abort_unless($this->installed(), 404);
|
||||
@@ -56,6 +69,7 @@ final class FundingProgrammes
|
||||
public function catalogueQuery(array $filters): Builder
|
||||
{
|
||||
$query = $this->publicQuery();
|
||||
app(FundingInstitutionDetails::class)->applyFilters($query, $filters);
|
||||
$includeUnknown = ($filters['certainty'] ?? 'include_unknown') === 'include_unknown';
|
||||
if (! empty($filters['sector'])) {
|
||||
$query->where(function (Builder $scope) use ($filters, $includeUnknown): void {
|
||||
@@ -66,10 +80,10 @@ final class FundingProgrammes
|
||||
}
|
||||
});
|
||||
}
|
||||
if (! empty($filters['territory'])) {
|
||||
if (! empty($filters['territory']) && ($filters['location_mode'] ?? '') !== 'physical') {
|
||||
$path = array_column(app(InvestmentGeography::class)->path($filters['territory']), 'id');
|
||||
$query->where(function (Builder $scope) use ($filters, $path, $includeUnknown): void {
|
||||
$scope->whereRaw("v.payload->>'coverage' = 'national'")
|
||||
$scope->whereRaw("v.payload->>'kind' = 'institution'")->orWhereRaw("v.payload->>'coverage' = 'national'")
|
||||
->orWhereJsonContains('v.payload->territory_ancestor_ids', $filters['territory']);
|
||||
foreach ($path as $id) {
|
||||
$scope->orWhereJsonContains('v.payload->territory_ids', $id);
|
||||
@@ -102,6 +116,15 @@ final class FundingProgrammes
|
||||
public function filterExplanation(array $record, array $filters): array
|
||||
{
|
||||
$payload = $record['payload'];
|
||||
if ($payload['kind'] === 'institution') {
|
||||
$products = array_values(array_filter($payload['products'] ?? [], fn ($p) => app(FundingInstitutionDetails::class)->match($p, $filters)));
|
||||
|
||||
if (($filters['location_mode'] ?? '') === 'physical') {
|
||||
return ['institution' => 'Pesquisa por presença física documentada, não por cobertura de financiamento. Confira a precisão do local e as indicações por confirmar na ficha.'];
|
||||
}
|
||||
|
||||
return ['institution' => $products === [] ? 'Instituição identificada; produtos, cobertura e disponibilidade por confirmar. Não é uma correspondência confirmada.' : 'Consulte os produtos correspondentes e as condições por confirmar. Sede e balcões não determinam a cobertura do financiamento.'];
|
||||
}
|
||||
$result = [];
|
||||
if (! empty($filters['sector'])) {
|
||||
$result['sector'] = $payload['sectors_scope'] === 'unknown'
|
||||
@@ -200,6 +223,13 @@ final class FundingProgrammes
|
||||
if (! $row && DB::table('funding_programmes')->where('code', $data['code'])->exists()) {
|
||||
throw ValidationException::withMessages(['code' => 'Este identificador já pertence a uma ficha. Abra-a para actualizar ou acrescentar uma janela.']);
|
||||
}
|
||||
if ($draft['kind'] === 'institution') {
|
||||
DB::select('select pg_advisory_xact_lock(261102, hashtext(?))', ['institution:'.$draft['institution_key']]);
|
||||
$duplicate = DB::table('funding_programmes')->whereRaw("draft->>'kind' = 'institution'")->whereRaw("draft->>'institution_key' = ?", [$draft['institution_key']])->when($id, fn ($q) => $q->where('id', '<>', $id))->exists();
|
||||
if ($duplicate) {
|
||||
throw ValidationException::withMessages(['institution_key' => 'A instituição já tem ficha. Reutilize a identidade existente; não substitua as suas edições.']);
|
||||
}
|
||||
}
|
||||
$id ??= (string) Str::uuid();
|
||||
DB::table('funding_programmes')->updateOrInsert(['id' => $id], ['code' => $data['code'], 'version' => (int) $data['version'] + 1,
|
||||
'draft' => json_encode($draft, JSON_THROW_ON_ERROR), 'created_by' => $row->created_by ?? $actor->id,
|
||||
@@ -218,13 +248,14 @@ final class FundingProgrammes
|
||||
$rules = [
|
||||
'title' => "$required|string|max:250", 'kind' => ['required', Rule::in(array_keys(self::KINDS))], 'summary' => "$required|string|max:4000",
|
||||
'manager_name' => 'nullable|string|max:250', 'manager_organization_id' => ['nullable', 'uuid', Rule::exists('organizations', 'id')->whereNull('deleted_at')],
|
||||
'source_url' => "$required|url:http,https|max:2000", 'source_reference' => "$required|string|max:1500", 'source_checked_on' => "$required|date_format:Y-m-d|before_or_equal:today",
|
||||
'source_url' => 'nullable|url:http,https|max:2000', 'source_document_name' => 'nullable|string|max:250', 'source_document_sha256' => 'nullable|string|regex:/^[a-f0-9]{64}$/D', 'source_reference' => "$required|string|max:1500", 'source_checked_on' => "$required|date_format:Y-m-d|before_or_equal:today",
|
||||
'source_published_on' => 'nullable|date_format:Y-m-d', 'source_status' => 'required|in:identified,unknown,example',
|
||||
'coverage' => 'required|in:national,territorial,unknown', 'coverage_text' => 'nullable|string|max:2000',
|
||||
'territory_ids' => 'array|max:30', 'territory_ids.*' => 'required|uuid|distinct',
|
||||
'sectors_scope' => 'required|in:all,selected,unknown', 'sectors_text' => 'nullable|string|max:2000', 'sector_ids' => 'array|max:40',
|
||||
'sector_ids.*' => ['required', 'integer', 'distinct', Rule::in(Sector::choices()->whereNotNull('published_at')->pluck('id')->all())],
|
||||
'resource_ids' => 'array|max:30', 'resource_ids.*' => 'required|integer|distinct|exists:portal_resources,id',
|
||||
'funding_needs' => 'array|max:6', 'funding_needs.*' => ['required', 'distinct', Rule::in(array_keys(FundingInstitutionDetails::NEEDS))],
|
||||
'criteria' => 'array|max:30', 'windows' => 'array|max:20',
|
||||
'windows.*' => 'required|array:code,title,mode,start_on,end_on,coverage,coverage_text,territory_ids,source_url,source_reference,checked_on,application_url,criteria',
|
||||
'windows.*.code' => 'required|string|regex:/^[a-z0-9][a-z0-9-]{0,79}$/D|distinct', 'windows.*.title' => "$required|string|max:250",
|
||||
@@ -244,6 +275,12 @@ final class FundingProgrammes
|
||||
$rules[$prefix.'.*.explanation'] = "$required|string|max:2000";
|
||||
}
|
||||
$data = Validator::make($input, $rules)->validate();
|
||||
if ($publishing && empty($data['source_url'])) {
|
||||
Validator::make($data, ['source_document_name' => 'required|string', 'source_document_sha256' => 'required|string|size:64'])->validate();
|
||||
}
|
||||
if ($data['kind'] === 'institution') {
|
||||
$data = [...$data, ...app(FundingInstitutionDetails::class)->normalize($input, $publishing)];
|
||||
}
|
||||
$data = [...$data, 'territory_ids' => $data['territory_ids'] ?? [], 'sector_ids' => $data['sector_ids'] ?? [], 'resource_ids' => $data['resource_ids'] ?? [],
|
||||
'criteria' => $data['criteria'] ?? [], 'windows' => $data['windows'] ?? []];
|
||||
if ($data['coverage'] !== 'territorial' && $data['territory_ids'] !== []) {
|
||||
|
||||
Reference in New Issue
Block a user