Files
Investidor-V2/app/Services/OpportunityMaturity.php

93 lines
4.2 KiB
PHP

<?php
namespace App\Services;
use App\Models\Opportunity;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Validation\ValidationException;
/** Catalogue versions are not interchangeable: historical positions keep their meaning. */
final class OpportunityMaturity
{
public const VERSION = 'spec-v2';
public const LEGACY = 'legacy-v1';
public const STAGES = [
'idea' => 'Ideia',
'concept' => 'Conceito',
'prefeasibility' => 'Pré-viabilidade',
'feasibility' => 'Viabilidade',
'structured' => 'Estruturado',
'investment_ready' => 'Pronto para investimento',
'implementation' => 'Implementação',
'operational' => 'Operacional',
];
public const LEGACY_LABELS = [1 => 'Ideia identificada', 'Pré-estudo disponível', 'Estudo de viabilidade em curso', 'Projecto estruturado', 'Pronto para investimento', 'Em negociação ou implementação'];
/** Public selection is explicit; historical numbers never stand for current stages.
* @return array<array-key, mixed>
*/
public static function searchOptions(): array
{
$options = [];
foreach (self::STAGES as $code => $label) {
$options[] = ['value' => 'stage:'.$code, 'label' => $label, 'group' => 'Etapas de preparação'];
}
foreach (self::LEGACY_LABELS as $level => $label) {
$options[] = ['value' => 'historical:'.$level, 'label' => $label.' — registo histórico', 'group' => 'Classificações históricas'];
}
return $options;
}
/**
* @param Builder<Opportunity> $query
*/
public static function constrainSearch(Builder $query, string $selection): void
{
[$kind, $value] = explode(':', $selection, 2);
if ($kind === 'stage') {
$query->where('workflow_context->publication->maturity->catalogue_version', self::VERSION)
->where('workflow_context->publication->maturity->stage', $value);
} else {
$query->where(fn ($q) => $q->where('workflow_context->publication->maturity->catalogue_version', self::LEGACY)
->orWhere(fn ($old) => $old->whereNull('workflow_context->publication->maturity->catalogue_version')->whereNull('workflow_context->publication->maturity->stage')))
->where('maturity_level', (int) $value);
}
}
/** @param array<string,mixed> $data
* @return array<string,mixed> */
public function normalize(array $data): array
{
if (! array_key_exists('maturity_level', $data) && ! array_key_exists('maturity_stage', $data)) {
if (isset($data['maturity_catalogue_version'])) {
throw ValidationException::withMessages(['maturity_catalogue_version' => 'Seleccione também a etapa; mudar só a versão não converte o valor histórico.']);
}
unset($data['maturity_catalogue_version']);
return $data;
}
$level = isset($data['maturity_level']) ? (int) $data['maturity_level'] : null;
$version = $data['maturity_catalogue_version'] ?? ((isset($data['maturity_stage']) || ($level ?? 0) > 6) ? self::VERSION : self::LEGACY);
if ($version === self::LEGACY) {
if (($level ?? 0) > 6 || isset($data['maturity_stage'])) {
throw ValidationException::withMessages(['maturity_level' => 'O catálogo histórico tem seis níveis; seleccione explicitamente o catálogo de oito etapas.']);
}
return [...$data, 'maturity_catalogue_version' => self::LEGACY, 'maturity_stage' => null, 'maturity_label' => self::LEGACY_LABELS[$level ?? 0] ?? null];
}
$codes = array_keys(self::STAGES);
$stage = $data['maturity_stage'] ?? ($level !== null ? $codes[$level - 1] : null);
if ($stage !== null && $level !== null && $codes[$level - 1] !== $stage) {
throw ValidationException::withMessages(['maturity_stage' => 'O código da etapa e a posição no catálogo não correspondem.']);
}
return [...$data, 'maturity_catalogue_version' => self::VERSION, 'maturity_stage' => $stage,
'maturity_level' => $stage !== null ? array_search($stage, $codes, true) + 1 : null,
'maturity_label' => $stage !== null ? self::STAGES[$stage] : null];
}
}