125 lines
7.4 KiB
PHP
125 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Http\Controllers\InvestmentPortfolioController;
|
|
use App\Models\Opportunity;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/** Multiple identified references in the existing encrypted investment context. */
|
|
final class InvestmentOpportunityChoices
|
|
{
|
|
/**
|
|
* @param array<array-key, mixed> $ids
|
|
* @return array<array-key, mixed>
|
|
*/
|
|
public function portfolioOrigins(array $ids, int $opportunity): array
|
|
{
|
|
$origins = [];
|
|
foreach (array_unique($ids) as $id) {
|
|
$record = InvestmentPortfolioController::visible()->addSelect('version_no')->whereKey($id)->firstOrFail();
|
|
$kind = null;
|
|
if (in_array($opportunity, app(CorridorAgribusinessOpportunities::class)->portfolioOpportunityIds($record), true)) {
|
|
$kind = 'corridor';
|
|
}
|
|
foreach (['projects' => 'project_id', 'clusters' => 'cluster_id', 'corridors' => 'corridor_id'] as $relation => $foreign) {
|
|
if ($record->$foreign && DB::table('opportunity_'.$relation)->where('opportunity_id', $opportunity)->where($foreign, $record->$foreign)->exists()) {
|
|
$kind = rtrim($relation, 's');
|
|
}
|
|
}
|
|
abort_unless($kind !== null, 422, 'A associação escolhida não pertence a esta oportunidade.');
|
|
$origins[] = ['id' => $record->id, 'title' => $record->title, 'kind' => $kind, 'href' => '/carteira/'.$record->id,
|
|
'version' => $record->version_no, 'sha256' => hash('sha256', json_encode([$record->title, $record->source, $record->content], JSON_THROW_ON_ERROR))];
|
|
}
|
|
|
|
return $origins;
|
|
}
|
|
|
|
/** Add explicitly consulted associations without resetting saved geography or deliberate answers.
|
|
* @param array<array-key, mixed> $entries
|
|
* @param array<array-key, mixed> $saved
|
|
* @return array<array-key, mixed>
|
|
*/
|
|
public function mergeEntries(array $saved, array $entries): array
|
|
{
|
|
foreach ($entries as $entry) {
|
|
$index = array_search($entry['opportunity']['key'], array_column(array_column($saved, 'opportunity'), 'key'), true);
|
|
if ($index === false) {
|
|
$saved[] = $entry;
|
|
|
|
continue;
|
|
}
|
|
$origins = collect([...($saved[$index]['portfolio_origins'] ?? []), ...($entry['portfolio_origins'] ?? [])])->unique('id')->values()->all();
|
|
if ($origins) {
|
|
$saved[$index]['portfolio_origins'] = $origins;
|
|
}
|
|
}
|
|
|
|
return $saved;
|
|
}
|
|
|
|
/**
|
|
* @param array<array-key, mixed> $choices
|
|
* @param array<array-key, mixed> $previous
|
|
* @return array<array-key, mixed>
|
|
*/
|
|
public function resolve(array $choices, array $previous = [], bool $pending = false): array
|
|
{
|
|
$result = [];
|
|
foreach ($choices as $choice) {
|
|
$ref = app(InvestorContext::class)->reference($choice['source']);
|
|
abort_unless($ref['kind'] === 'opportunity', 422, 'Seleccione uma ficha de oportunidade.');
|
|
$record = Opportunity::whereKey($ref['id'])->firstOrFail();
|
|
$old = collect($previous)->first(fn ($row) => $row['opportunity']['key'] === $ref['key']);
|
|
$origins = $this->portfolioOrigins($choice['portfolio_origins'] ?? array_column($old['portfolio_origins'] ?? [], 'id'), (int) $record->id);
|
|
$corridors = app(InvestmentCorridorContext::class);
|
|
$corridor = $choice['corridor_key'] ?? $old['corridor_key'] ?? $corridors->key($record);
|
|
$provinces = $corridor ? $corridors->provinces($corridor) : [];
|
|
$documented = $record->primary_territory_id;
|
|
$territory = array_key_exists('territory', $choice) ? $choice['territory'] : ($documented ? 'territory:'.$documented : null);
|
|
if (! $territory && count($provinces) === 1) {
|
|
$territory = 'territory:'.$provinces[0]->id;
|
|
}
|
|
if ($corridor && $territory) {
|
|
abort_unless($corridors->compatible($corridor, substr($territory, 10)), 422, 'A localização não pertence ao corredor escolhido.');
|
|
}
|
|
$previousTerritory = $old['territory']['key'] ?? null;
|
|
$newAlternative = $documented && $territory !== 'territory:'.$documented
|
|
&& (! $old || ($old['documented_territory_id'] ?? null) !== $documented || $territory !== $previousTerritory);
|
|
$corridorChanged = $old && ($old['corridor_key'] ?? $corridors->key($record)) !== $corridor;
|
|
$incompatibleOld = $corridorChanged && $previousTerritory && ! $corridors->compatible($corridor, substr($previousTerritory, 10));
|
|
if (! $pending && ($newAlternative || ($previousTerritory && $territory !== $previousTerritory && ! $incompatibleOld))) {
|
|
abort_unless(! empty($choice['confirm_location_change']) && mb_strlen(trim($choice['location_reason'] ?? '')) >= 20,
|
|
422, 'Uma localização documentada ou já confirmada só muda por escolha explícita, com confirmação e motivo.');
|
|
}
|
|
$location = $territory ? app(InvestorContext::class)->reference($territory) : null;
|
|
abort_unless($location === null || $location['kind'] === 'territory', 422);
|
|
$parkKey = array_key_exists('park', $choice) ? $choice['park'] : ($old['park']['key'] ?? null);
|
|
$park = $parkKey ? app(InvestorContext::class)->reference($parkKey) : null;
|
|
if ($park) {
|
|
$eligible = $corridor ? array_column($corridors->parks($corridor, $location['id'] ?? null), 'id') : [];
|
|
if (! in_array($park['id'], $eligible, true)) {
|
|
abort_if(! empty($choice['park']), 422, 'A zona ou parque não é compatível com a localização desta oportunidade.');
|
|
$park = null;
|
|
}
|
|
abort_unless(! $park || $park['kind'] === 'park', 422);
|
|
}
|
|
$result[] = ['opportunity' => $ref, 'territory' => $location, ...($origins ? ['portfolio_origins' => $origins] : []),
|
|
'park' => $park,
|
|
'corridor_key' => $corridor, 'corridor_name' => $corridor ? config('investment_corridors.'.$corridor.'.name') : null,
|
|
'allowed_provinces' => array_map(fn ($p) => (array) $p, $provinces),
|
|
'territory_path' => $location ? array_map(fn ($node) => Arr::only((array) $node, ['id', 'name', 'parent_id', 'location_type']), app(InvestmentGeography::class)->path($location['id'], true)) : [],
|
|
'activities' => $choice['activities'] ?? ($old['activities'] ?? []),
|
|
'documented_territory_id' => $documented, 'location_reason' => $choice['location_reason'] ?? '',
|
|
'location_state' => $location ? ($documented && $territory === 'territory:'.$documented ? 'documented' : 'investor_selected') : 'undetermined',
|
|
'location_changed_explicitly' => $old && $territory === $previousTerritory ? (bool) ($old['location_changed_explicitly'] ?? false) : (bool) ($choice['confirm_location_change'] ?? false),
|
|
'coverage' => data_get($record->workflow_context, 'corridor_agribusiness.corridor'),
|
|
'source' => data_get($record->workflow_context, 'corridor_agribusiness.source'),
|
|
'source_sha256' => data_get($record->workflow_context, 'corridor_agribusiness.source_sha256')];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|