95 lines
4.8 KiB
PHP
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.';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|