Files
Investidor-V2/app/Console/Commands/ImportIndustrialParkCandidates.php
T
nala e65cb26901
tests / ci (push) Canceled after 0s
varias alteracoes
2026-10-06 14:50:12 +02:00

110 lines
6.1 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Services\InvestmentZones;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use RuntimeException;
class ImportIndustrialParkCandidates extends Command
{
protected $signature = 'investment-zones:import-industrial-candidates {--apply : Create missing private drafts} {--actor= : Existing eligible manager user ID required with --apply}';
protected $description = 'Reconcile and optionally create source-backed industrial-park/ZFI drafts without publishing';
public function handle(): int
{
$document = json_decode(file_get_contents(database_path('data/industrial-park-candidate-drafts.json')), true, flags: JSON_THROW_ON_ERROR);
if (($document['source_document_attached'] ?? null) !== true
|| ($document['source_document_reconciled'] ?? null) !== true) {
$this->error('The source document must be attached and reconciled before importing drafts. No records written.');
return self::FAILURE;
}
$rows = [];
foreach ($document['records'] as $record) {
$existing = DB::table('investment_zones')->where('code', $record['code'])->first(['id', 'name', 'publication_status']);
$territories = [];
foreach ($record['territories'] as $territory) {
$expected = $this->normalize($territory['name']);
$matches = DB::table('geo_locations')->whereNull('deleted_at')->where('location_type', $territory['type'])
->get(['id', 'name'])->filter(fn ($node) => $this->normalize($node->name) === $expected);
if ($matches->count() !== 1) {
throw new RuntimeException("Territory {$territory['name']} ({$territory['type']}) matched {$matches->count()} rows; approximate matching is disabled.");
}
$territories[] = [...$territory, 'geo_location_id' => $matches->first()->id, 'resolved_name' => $matches->first()->name];
}
$rows[] = ['record' => $record, 'existing' => $existing, 'territories' => $territories];
}
$this->table(['Code', 'Action', 'Territory IDs resolved', 'Areas'], array_map(fn ($row) => [
$row['record']['code'], $row['existing'] ? 'reconcile/skip-existing' : ($this->option('apply') ? 'create-private-draft' : 'would-create-private-draft'),
implode('; ', array_map(fn ($t) => $t['resolved_name'], $row['territories'])), count($row['record']['areas']),
], $rows));
if (! $this->option('apply')) {
$this->info('Dry run only. No writes made; every created record remains a private draft, with legal regime/availability not implied by the source.');
return self::SUCCESS;
}
if (! Schema::hasColumn('investment_zones', 'source_evidence')) {
$this->error('The additive industrial-park schema is not migrated in this database. No records written.');
return self::FAILURE;
}
$actor = filter_var($this->option('actor'), FILTER_VALIDATE_INT);
$user = $actor ? DB::table('users')->where('id', $actor)->first(['id', 'email', 'email_verified_at', 'two_factor_confirmed_at']) : null;
if (! $user || ! $user->email_verified_at || ! $user->two_factor_confirmed_at
|| ! in_array(mb_strtolower($user->email), config('portal.managers', []), true)) {
$this->error('An existing verified, MFA-enabled configured manager is required. No records written.');
return self::FAILURE;
}
$created = DB::transaction(function () use ($rows, $actor, $document): array {
$created = [];
foreach ($rows as $row) {
if ($row['existing']) {
continue;
}
$record = $row['record'];
$source = $record['source'];
$sourceReference = $source['url'].' — '.$source['title'];
$locations = array_map(fn ($location) => ['geo_location_id' => $location['geo_location_id'], 'scope_type' => $location['scope_type'],
'source_reference' => $sourceReference, 'legal_source_id' => null], $row['territories']);
$first = $row['territories'][0]['geo_location_id'] ?? null;
$zone = app(InvestmentZones::class)->save(null, [
'code' => $record['code'], 'name' => $record['name'], 'category' => $record['category'],
'physical_category' => $record['physical_category'], 'legal_regime' => $record['legal_regime'],
'development_status' => $record['development_status'], 'availability_status' => $record['availability_status'],
'operational_status' => 'unknown', 'description' => $record['source']['supports'].(! empty($record['territory_note']) ? ' '.$record['territory_note'] : ''),
'source_evidence' => [[...$source, 'source_date' => null]],
'area_components' => array_map(fn ($area) => [...$area, 'source_reference' => $sourceReference.' — '.$area['source_reference'], 'source_date' => null], $record['areas']),
'area_value' => null, 'area_unit' => null, 'activities' => [], 'resources' => [], 'aliases' => [],
'sources_consulted_at' => $document['consulted_at'],
'legal_source_id' => null, 'parent_zone_id' => null, 'primary_territory_id' => $first,
'territory_version' => $first ? 1 : 0, 'locations' => $locations,
], (int) $actor);
$created[] = [$zone->code, $zone->id];
}
return $created;
});
$this->info('Private drafts created: '.count($created).'. Existing codes skipped. No legal sources were approved and no records were published.');
foreach ($created as [$code, $id]) {
$this->line($code.' '.$id);
}
return self::SUCCESS;
}
private function normalize(string $value): string
{
return Str::upper(Str::ascii(trim($value)));
}
}