['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.'; } } } }