Files
Investidor-V2/app/Services/OpportunityInitiativeApplicability.php
T

95 lines
4.8 KiB
PHP

<?php
namespace App\Services;
/** Tempo decision: programme/portfolio applicability; persisted in existing canonical JSON. */
class OpportunityInitiativeApplicability
{
public const GROUPS = [
'physical_site' => ['location_mode', 'licenses', 'environmental_social'],
'individual_business' => ['promoter_id', 'promoter_type', 'opportunity_type', 'investor_profiles', 'products', 'assets', 'inputs', 'markets', 'competitive_advantage', 'employment', 'impact_indicators', 'enterprise_size'],
'unit_investment' => ['total_investment', 'currency', 'investment_breakdown', 'secured_financing', 'funding_sought', 'financial_indicators'],
];
public function enabled(array $p): bool
{
return ($p['opportunity_category'] ?? null) === 'investment_project'
&& in_array($p['initiative']['kind'] ?? null, ['program', 'portfolio'], true);
}
public function applicable(array $p, string $group): bool
{
return ! $this->enabled($p) || ($p['initiative']['applicability'][$group] ?? 'missing') !== 'not_applicable';
}
public function schema(array $schema, array $payload): array
{
$base = &$schema['$defs']['opportunityCreate'];
$text = ['type' => ['string', 'null'], 'maxLength' => 4000];
$base['properties']['initiative'] = ['type' => 'object', 'additionalProperties' => false,
'required' => ['kind', 'responsible_name', 'coverage_statement', 'applicability'],
'properties' => [
'kind' => ['enum' => ['program', 'portfolio']],
'responsible_name' => ['type' => 'string', 'minLength' => 1, 'maxLength' => 500],
'coverage_statement' => ['type' => 'string', 'minLength' => 1, 'maxLength' => 4000],
'aggregate_budget_statement' => $text,
'applicability' => ['type' => 'object', 'additionalProperties' => false,
'required' => array_keys(self::GROUPS),
'properties' => array_fill_keys(array_keys(self::GROUPS), ['enum' => ['applicable', 'not_applicable', 'missing']])],
]];
if (! $this->enabled($payload)) {
// A profile cannot relax requirements of another category.
if (isset($payload['initiative'])) {
$base['allOf'][] = ['properties' => ['opportunity_category' => ['const' => 'investment_project']]];
}
return $schema;
}
$ignored = [];
foreach (self::GROUPS as $group => $fields) {
if (! $this->applicable($payload, $group)) {
$ignored = [...$ignored, ...$fields];
}
}
// Program responsibility is distinct from component promoter identity, in all cases.
$ignored = array_unique([...$ignored, 'promoter_id', 'promoter_type']);
$base['required'] = array_values(array_diff($base['required'], $ignored));
$base['required'][] = 'initiative';
foreach ($ignored as $field) {
$base['properties'][$field] = ['description' => 'Not applicable; retained without alteration']; // Retain old bytes; do not validate an inapplicable placeholder.
}
foreach ($base['allOf'] as &$condition) {
if (isset($condition['then']['required'])) {
$condition['then']['required'] = array_values(array_diff($condition['then']['required'], $ignored));
}
if (isset($condition['then']['properties'])) {
$condition['then']['properties'] = array_diff_key($condition['then']['properties'], array_flip($ignored));
}
// Location counts apply to implantation, not to the coverage resources.
if (! $this->applicable($payload, 'physical_site') && isset($condition['if']['properties']['location_mode'])) {
$condition = ['description' => 'Coverage is distinct from physical implantation'];
}
}
unset($condition);
if (! $this->applicable($payload, 'physical_site')) {
unset($base['properties']['locations']['minItems']);
}
return $schema;
}
public function validate(array $p, array &$errors): void
{
if (! $this->enabled($p)) {
return;
}
if (! in_array($p['maturity_stage'] ?? null, array_keys(OpportunityMaturity::STAGES), true)) {
$errors['canonical.maturity_stage'] = 'Indique a etapa conhecida do programa/carteira; a classificação não confirma preparação ou viabilidade.';
}
foreach (self::GROUPS as $group => $fields) {
if (($p['initiative']['applicability'][$group] ?? 'missing') === 'missing') {
$errors['canonical.initiative.applicability.'.$group] = 'Informação em falta: indique se este grupo é aplicável ou não aplicável à ficha; não invente dados das componentes.';
}
}
}
}