*/ public function preview(User $actor, string $code): array { app(FundingProgrammes::class)->guard($actor); $bytes = CheckedFile::read(database_path('data/funding-references.json')); $package = json_decode($bytes, true, 512, JSON_THROW_ON_ERROR); abort_unless($package['format'] === 'investmoz-funding-references-v1', 503); $reference = $package['references'][$code] ?? null; abort_unless(is_array($reference), 404); $unresolved = []; $resolve = function (string $name) use (&$unresolved): ?string { $matches = app(InvestmentGeography::class)->publicReferences()->where('location_type', 'province')->get(['id', 'name'])->filter(fn ($row) => Str::lower(Str::ascii($row->name)) === Str::lower(Str::ascii($name))); if ($matches->count() !== 1) { $unresolved[] = $name; return null; } return $matches->first()->id; }; if (! empty($reference['territory_names'])) { $ids = array_values(array_filter(array_map($resolve, $reference['territory_names']))); if (count($ids) === count($reference['territory_names'])) { $reference['draft']['territory_ids'] = $ids; } else { $reference['draft']['coverage'] = 'unknown'; $reference['draft']['territory_ids'] = []; $reference['draft']['coverage_text'] .= ' Correspondência ao cadastro territorial por confirmar.'; } } foreach ($reference['location_territory_names'] ?? [] as $index => $name) { $reference['draft']['locations'][$index]['territory_id'] = $resolve($name); } $plan = ['unresolved_territories' => $unresolved, 'code' => $code, 'edition' => $reference['edition'], 'draft' => $reference['draft'], 'provenance' => $reference['provenance'], 'package_sha256' => hash('sha256', $bytes)]; $plan['hash'] = hash_hmac('sha256', json_encode(Arr::sortRecursive($plan), JSON_THROW_ON_ERROR), config('app.key')); return $plan; } /** @return list */ public function choices(User $actor): array { app(FundingProgrammes::class)->guard($actor); $package = json_decode(CheckedFile::read(database_path('data/funding-references.json')), true, 512, JSON_THROW_ON_ERROR); $choices = []; foreach ($package['references'] as $code => $reference) { $choices[] = ['code' => $code, 'title' => $reference['draft']['title']]; } return $choices; } /** @return array{id:string,created:bool} */ public function prepare(User $actor, string $code, string $hash): array { $plan = $this->preview($actor, $code); abort_unless(hash_equals($plan['hash'], $hash), 409, 'As referências mudaram. Recupere a prévia antes de preparar a ficha.'); return DB::transaction(function () use ($actor, $code, $plan): array { DB::select('select pg_advisory_xact_lock(261102, hashtext(?))', [$code]); $existing = DB::table('funding_programmes')->where('code', $code)->lockForUpdate()->first(); if ($existing) { return ['id' => $existing->id, 'created' => false]; } $id = app(FundingProgrammes::class)->save($actor, null, ['version' => 0, 'code' => $code, 'reason' => 'Preparação de referência documental '.$plan['edition'].'; rascunho sem publicação automática.', 'draft' => $plan['draft']]); DB::table('audit_events')->insert(['source_system' => 'funding-catalogue', 'entity_type' => 'funding_programmes', 'entity_id' => $id, 'actor_user_id' => $actor->id, 'action' => 'reference_prepared', 'data_classification' => 'restricted', 'after_data' => json_encode(['edition' => $plan['edition'], 'package_sha256' => $plan['package_sha256'], 'provenance' => $plan['provenance'], 'effect' => 'private_draft_only'], JSON_THROW_ON_ERROR)]); return ['id' => $id, 'created' => true]; }); } }