220 lines
18 KiB
PHP
220 lines
18 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use Brick\Math\BigDecimal;
|
||
|
||
/** Adapter of the dossier's declared context to preparation of a private-investment request. No tax or entitlement decision. */
|
||
final class PrivateInvestmentPreparation
|
||
{
|
||
public const VERSION = 'private-investment-preparation-2026-v1';
|
||
|
||
public const SOURCE = 'https://pt.apiex.gov.mz/wp-content/uploads/2025/02/LEGISLACAO-DE-INVESTIMENTOS-EM-MOCAMBIQUE.pdf';
|
||
|
||
public const HASH = 'ba1e9eaabfec5d0c4cb2d9f2444a6860f7a90f222034764197829be402d408ed';
|
||
|
||
public const FIELDS = ['investment_form', 'investment_preparation', 'investment_activity_scope', 'implementation_mode', 'capital_origin', 'site_tenure', 'project_concession', 'significant_implications', 'land_extension', 'forest_concession', 'forest_concession_hectares', 'total_investment', 'area_required', 'area_unit'];
|
||
|
||
public const DOCUMENTS = ['identity', 'registration', 'name_reservation', 'site_plan', 'lease', 'duat', 'representation_license', 'feasibility', 'financial_capacity', 'foreign_capital'];
|
||
|
||
public function available(): bool
|
||
{
|
||
return (bool) config('investor-path.private_preparation', false);
|
||
}
|
||
|
||
/**
|
||
* @return array<array-key, mixed>
|
||
*/
|
||
public function questions(): array
|
||
{
|
||
$tri = ['unknown' => 'Por determinar', 'yes' => 'Sim', 'no' => 'Não'];
|
||
|
||
return [
|
||
['key' => 'investment_preparation', 'label' => 'Pretende preparar o pedido de garantias/incentivos ao investimento privado?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 2'],
|
||
['key' => 'investment_activity_scope', 'label' => 'Natureza efectiva da actividade do projecto', 'options' => ['unknown' => 'Por determinar', 'general' => 'Outra actividade económica', 'extractive' => 'Prospecção, pesquisa ou produção extractiva', 'processing' => 'Processamento industrial mineiro/petrolífero', 'public_nonprofit' => 'Investimento público ou exclusivamente social/sem fins lucrativos'], 'source' => 'Lei 8/2023, artigos 2 e 22'],
|
||
['key' => 'investment_form', 'label' => 'Tipo deste investimento', 'options' => ['unknown' => 'Por determinar', 'direct' => 'Directo', 'indirect' => 'Indirecto'], 'source' => 'Lei 8/2023, artigo 14(b); Regulamento, artigo 13', 'help' => 'O limiar monetário do artigo 13 aplica-se ao investimento directo. A forma indirecta não é convertida silenciosamente em directa.'],
|
||
['key' => 'implementation_mode', 'label' => 'Modalidade da empresa implementadora', 'options' => ['unknown' => 'Por determinar', 'domestic_entity' => 'Empresa constituída/em constituição em Moçambique', 'foreign_representation' => 'Representação comercial estrangeira'], 'source' => 'Regulamento 8/2024, artigo 9(2)'],
|
||
['key' => 'capital_origin', 'label' => 'Origem dos capitais/recursos deste investimento', 'options' => ['unknown' => 'Por determinar', 'national' => 'Nacionais', 'external' => 'Provenientes do exterior', 'mixed' => 'Mistos'], 'source' => 'Lei 8/2023, glossário; Regulamento, artigos 4–6'],
|
||
['key' => 'site_tenure', 'label' => 'Fundamento declarado da utilização das instalações', 'options' => ['unknown' => 'Por determinar', 'lease' => 'Arrendamento', 'duat' => 'DUAT'], 'source' => 'Regulamento, artigos 9(1)(d) e 14(1)(d)'],
|
||
['key' => 'project_concession', 'label' => 'O empreendimento é PPP ou concessão empresarial?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(ii)'],
|
||
['key' => 'significant_implications', 'label' => 'Existem previsíveis implicações económicas, ambientais, de segurança ou saúde pública que exijam autorização?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(i)', 'help' => 'Declare o enquadramento conhecido; o nome do sector não responde a esta pergunta. Sem fundamento suficiente, mantenha por determinar.'],
|
||
['key' => 'land_extension', 'label' => 'O projecto requer extensão de terra?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(iii)'],
|
||
['key' => 'forest_concession', 'label' => 'O projecto requer concessão florestal?', 'options' => $tri, 'source' => 'Lei 8/2023, artigo 22(1)(b)(iv)'],
|
||
['key' => 'forest_concession_hectares', 'label' => 'Área da concessão florestal (hectares)', 'type' => 'number', 'when' => ['field' => 'forest_concession', 'equals' => 'yes'], 'source' => 'Lei 8/2023, artigo 22(1)(b)(iv)'],
|
||
['key' => 'total_investment', 'label' => 'Valor total do investimento na moeda do projecto', 'type' => 'number', 'source' => 'Regulamento, artigo 13(2)'],
|
||
['key' => 'area_required', 'label' => 'Área de terra requerida', 'type' => 'number', 'when' => ['field' => 'land_extension', 'equals' => 'yes'], 'source' => 'Lei 8/2023, artigo 22(1)(b)(iii)'],
|
||
['key' => 'area_unit', 'label' => 'Unidade da área requerida', 'options' => ['ha' => 'Hectares', 'm2' => 'Metros quadrados'], 'when' => ['field' => 'land_extension', 'equals' => 'yes'], 'source' => 'Conversão de unidade exacta; não é benefício ou condição adicional'],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param array<array-key, mixed> $f
|
||
*/
|
||
private function number(array $f, string $key): ?BigDecimal
|
||
{
|
||
$value = $f[$key] ?? '';
|
||
|
||
return is_scalar($value) && preg_match('/^\d+(?:\.\d+)?$/D', (string) $value) ? BigDecimal::of((string) $value) : null;
|
||
}
|
||
|
||
/** @param array<array-key, mixed> $fields
|
||
* @param list<string> $missing
|
||
*/
|
||
private function answer(array $fields, string $key, array &$missing): ?bool
|
||
{
|
||
$value = $fields[$key] ?? null;
|
||
if ($value === 'yes') {
|
||
return true;
|
||
}
|
||
if ($value === 'no') {
|
||
return false;
|
||
}
|
||
$missing[] = $key;
|
||
|
||
return null;
|
||
}
|
||
|
||
/** @param array<string, bool|null> $triggers
|
||
* @return list<string>
|
||
*/
|
||
private function triggerReasons(array $triggers): array
|
||
{
|
||
$reasons = [];
|
||
foreach (['amount' => 'Valor superior a 32 mil milhões MZN — R13(2), R21(2)(a)', 'project_concession' => 'PPP/concessão empresarial — Lei 8/2023, art. 22(1)(b)(ii), R21(2)(b)', 'significant_implications' => 'Implicações significativas declaradas — Lei 8/2023, art. 22(1)(b)(i), R21(1)', 'land_extension' => 'Extensão de terra igual/superior a 10 mil hectares — Lei 8/2023, art. 22(1)(b)(iii), R21(2)(c)', 'forest_concession' => 'Concessão florestal superior a 100 mil hectares — Lei 8/2023, art. 22(1)(b)(iv), R21(2)(d)'] as $condition => $description) {
|
||
if (($triggers[$condition] ?? null) === true) {
|
||
$reasons[] = $description.'.';
|
||
}
|
||
}
|
||
|
||
return $reasons;
|
||
}
|
||
|
||
/**
|
||
* @param array<array-key, mixed> $context
|
||
* @param array<array-key, mixed> $documents
|
||
* @return array<array-key, mixed>
|
||
*/
|
||
public function assess(array $context, array $documents = []): array
|
||
{
|
||
$f = $context['fields'] ?? [];
|
||
$missing = [];
|
||
$enabled = ($f['investment_preparation'] ?? '') === 'yes';
|
||
$date = $f['expected_start_date'] ?? '';
|
||
$period = preg_match('/^2026-\d{2}-\d{2}$/D', $date) === 1;
|
||
$scope = $f['investment_activity_scope'] ?? 'unknown';
|
||
$covered = in_array($scope, ['general', 'processing'], true);
|
||
$mode = 'indeterminate';
|
||
$reasons = [];
|
||
if ($enabled) {
|
||
if (! $date) {
|
||
$missing[] = 'expected_start_date';
|
||
}
|
||
if ($scope === 'unknown' || $scope === '') {
|
||
$missing[] = 'investment_activity_scope';
|
||
}
|
||
if (empty($f['currency'])) {
|
||
$missing[] = 'currency';
|
||
}
|
||
if ($covered && $period) {
|
||
$triggers = [];
|
||
if ($scope === 'processing') {
|
||
$triggers['processing'] = true;
|
||
$reasons[] = 'Processamento industrial mineiro/petrolífero: Lei 8/2023, artigo 22(1)(b)(v).';
|
||
}
|
||
$amount = $this->number($f, 'total_investment');
|
||
$triggers['amount'] = $amount && ($f['currency'] ?? '') === 'MZN' && ($f['investment_form'] ?? '') === 'direct' ? $amount->isGreaterThan('32000000000') : null;
|
||
if (! in_array($f['investment_form'] ?? '', ['direct', 'indirect'], true)) {
|
||
$missing[] = 'investment_form';
|
||
}
|
||
if (($f['investment_form'] ?? '') === 'indirect') {
|
||
$reasons[] = 'Investimento indirecto: a classificação pela dimensão e o encaminhamento monetário não são determinados por este lote. As restantes condições documentadas continuam avaliadas.';
|
||
}
|
||
if (! $amount) {
|
||
$missing[] = 'total_investment';
|
||
}
|
||
if (($f['currency'] ?? '') !== 'MZN') {
|
||
$reasons[] = 'A condição monetária não é determinada noutra moeda: não se fez conversão.';
|
||
}
|
||
foreach (['project_concession', 'significant_implications'] as $key) {
|
||
$triggers[$key] = $this->answer($f, $key, $missing);
|
||
}
|
||
$land = $this->answer($f, 'land_extension', $missing);
|
||
if ($land === true) {
|
||
$area = $this->number($f, 'area_required');
|
||
$unit = $f['area_unit'] ?? '';
|
||
if (! $area) {
|
||
$missing[] = 'area_required';
|
||
}if (! in_array($unit, ['ha', 'm2'], true)) {
|
||
$missing[] = 'area_unit';
|
||
}
|
||
$triggers['land_extension'] = $area && in_array($unit, ['ha', 'm2'], true) ? $area->isGreaterThanOrEqualTo($unit === 'ha' ? '10000' : '100000000') : null;
|
||
} else {
|
||
$triggers['land_extension'] = $land;
|
||
}
|
||
$forest = $this->answer($f, 'forest_concession', $missing);
|
||
if ($forest === true) {
|
||
$area = $this->number($f, 'forest_concession_hectares');
|
||
if (! $area) {
|
||
$missing[] = 'forest_concession_hectares';
|
||
}$triggers['forest_concession'] = $area ? $area->isGreaterThan('100000') : null;
|
||
} else {
|
||
$triggers['forest_concession'] = $forest;
|
||
}
|
||
$reasons = [...$reasons, ...$this->triggerReasons($triggers)];
|
||
$mode = in_array(true, $triggers, true) ? 'authorization' : (in_array(null, $triggers, true) ? 'indeterminate' : 'registration');
|
||
$reasons[] = $mode === 'authorization' ? 'Foi identificada uma condição de autorização; as restantes condições desconhecidas não foram presumidas negativas.' : ($mode === 'registration' ? 'As condições de autorização deste lote foram declaradas negativas e o montante não ultrapassa o limite.' : 'Falta resolver pelo menos uma condição de autorização.');
|
||
}
|
||
foreach (['implementation_mode', 'capital_origin', 'site_tenure', 'entity_stage'] as $key) {
|
||
if (empty($f[$key]) || $f[$key] === 'unknown') {
|
||
$missing[] = $key;
|
||
}
|
||
}
|
||
}
|
||
if ($mode === 'authorization' && in_array(true, array_intersect_key($triggers, array_flip(['amount', 'project_concession', 'land_extension', 'forest_concession'])), true)) {
|
||
$missing = array_values(array_diff($missing, ['investment_form', 'project_concession', 'significant_implications', 'land_extension', 'forest_concession', 'forest_concession_hectares', 'area_required', 'area_unit', 'total_investment']));
|
||
}
|
||
$base = $enabled && $covered && $period ? true : null;
|
||
if (! $enabled || in_array($scope, ['extractive', 'public_nonprofit'], true) || ($date && ! $period)) {
|
||
$base = false;
|
||
}
|
||
$rules = [
|
||
['identity', 'Identificação do investidor', $base, 'S06', 'R9(1)(a) / R14(1)(a)', 41, 'Identificação do proponente do pedido.'],
|
||
['registration', 'Certidão de registo comercial', $base === true ? (($f['entity_stage'] ?? '') === 'existing' ? true : (($f['entity_stage'] ?? '') === 'being_formed' ? false : null)) : $base, 'S05', 'R9(1)(b) / R14(1)(b)', 41, 'Alternativa para empresa já registada.'],
|
||
['name_reservation', 'Reserva da denominação social', $base === true ? (($f['entity_stage'] ?? '') === 'being_formed' ? true : (($f['entity_stage'] ?? '') === 'existing' ? false : null)) : $base, 'S05', 'R9(1)(b) / R14(1)(b)', 41, 'Alternativa para empresa em constituição.'],
|
||
['site_plan', 'Planta ou esboço do local', $base, 'S06', 'R9(1)(c) / R14(1)(c)', 42, 'Local de implantação identificado; não certifica disponibilidade.'],
|
||
];
|
||
foreach (['lease' => 'Contrato de arrendamento', 'duat' => 'Título DUAT'] as $key => $label) {
|
||
$rules[] = [$key, $label, $base === true ? (($f['site_tenure'] ?? 'unknown') === 'unknown' ? null : ($f['site_tenure'] ?? '') === $key) : $base, 'S06', 'R9(1)(d) / R14(1)(d)', 42, 'Alternativa correspondente à utilização declarada das instalações.'];
|
||
}
|
||
$rules[] = ['representation_license', 'Licença de representação comercial', $base === true ? (($f['implementation_mode'] ?? 'unknown') === 'unknown' ? null : ($f['implementation_mode'] ?? '') === 'foreign_representation') : $base, 'S06', 'R9(2)', 42, 'Só para a modalidade representação comercial estrangeira; não decorre da nacionalidade.'];
|
||
foreach (['feasibility' => 'Estudo de viabilidade técnica e económico-financeira', 'financial_capacity' => 'Comprovação da capacidade financeira'] as $key => $label) {
|
||
$rules[] = [$key, $label, $base === true ? ($mode === 'indeterminate' ? null : $mode === 'authorization') : $base, 'S02', 'R14(1)–(2)', 45, 'Integra a preparação do pedido de autorização.'];
|
||
}
|
||
$rules[] = ['foreign_capital', 'Prova de realização/registo do aporte externo', $base === true ? (($f['capital_origin'] ?? 'unknown') === 'unknown' ? null : in_array($f['capital_origin'] ?? '', ['external', 'mixed'], true)) : $base, 'S06', 'R5(1)–(5)', 39, 'Preparação específica do aporte externo; formalidades cambiais concretas não calculadas.'];
|
||
$links = $context['preparation_documents'] ?? [];
|
||
$requirements = [];
|
||
foreach ($rules as [$key,$label,$applies,$stage,$article,$page,$reason]) {
|
||
$doc = isset($links[$key]) ? collect($documents)->firstWhere('document_id', $links[$key]) : null;
|
||
$requirements[] = ['key' => $key, 'label' => $label, 'state' => $applies === false ? 'not_applicable' : ($applies === null ? 'indeterminate' : ($doc ? 'associated' : 'missing')), 'required' => $applies === true, 'document_id' => $links[$key] ?? null, 'document' => $doc, 'stage' => $stage, 'reason' => $reason, 'source' => ['reference' => $article, 'url' => self::SOURCE.'#page='.$page, 'sha256' => self::HASH, 'period' => '2026; Regulamento desde 07/03/2024'], 'legal_verification' => 'not_inferred'];
|
||
}
|
||
$authoritySource = ['reference' => 'R13(1)', 'url' => self::SOURCE.'#page=45'];
|
||
$zone = $context['selection']['park'] ?? null;
|
||
$amount = $this->number($f, 'total_investment');
|
||
$authority = 'Por determinar neste lote';
|
||
if ($base === true && $mode === 'registration' && $amount && ($f['currency'] ?? '') === 'MZN') {
|
||
$special = in_array($zone['fields']['investment_regime'] ?? '', ['zee', 'zfi'], true);
|
||
$authority = $special ? ($amount->isLessThanOrEqualTo('6500000000') ? 'APIEX — condicionado ao enquadramento ZEE/ZFI efectivamente reconhecido' : 'Ministro que superintende Finanças') : ($amount->isLessThanOrEqualTo('3500000000') ? 'Governador da Província' : ($amount->isLessThanOrEqualTo('6500000000') ? 'APIEX' : 'Ministro que superintende Finanças'));
|
||
}
|
||
if ($base === true && $mode === 'authorization') {
|
||
$council = array_intersect_key($triggers, array_flip(['amount', 'project_concession', 'land_extension', 'forest_concession']));
|
||
$authority = in_array(true, $council, true) ? 'Conselho de Ministros' : (in_array(null, $council, true) ? 'Autorização identificada; completar as condições em falta para distinguir a entidade decisória' : 'Ministro que superintende Finanças; eventual submissão ao Conselho de Ministros nos termos do R21(3)');
|
||
$authoritySource = ['reference' => 'R21(1)–(3)', 'url' => self::SOURCE.'#page=50'];
|
||
}
|
||
|
||
$labels = array_column($this->questions(), 'label', 'key') + ['currency' => 'Moeda do projecto', 'expected_start_date' => 'Data prevista de início', 'entity_stage' => 'Situação da entidade'];
|
||
$missingLabels = array_map(fn ($key) => $labels[$key] ?? $key, array_values(array_unique($missing)));
|
||
|
||
return ['version' => self::VERSION, 'enabled' => $enabled, 'period_covered' => $period, 'scope_covered' => $covered, 'mode' => $mode, 'reason' => $reasons, 'missing_fields' => array_values(array_unique($missing)), 'missing_labels' => $missingLabels, 'questions' => $this->questions(), 'requirements' => $requirements, 'authority' => $authority, 'authority_source' => $authoritySource, 'next' => $base === false ? 'Rever o âmbito/período; este lote não cobre o pedido' : ($mode === 'indeterminate' ? 'Completar condições do enquadramento' : ($mode === 'authorization' ? 'Preparar estudo e documentos do pedido de autorização' : 'Preparar documentos do pedido de mero registo')), 'calculation_notice' => 'O regime procedimental não altera a taxa de IVA. Reutilize a simulação geral apenas no seu âmbito e período; benefícios, apuramento final e conversão cambial não são presumidos.', 'source' => ['url' => self::SOURCE, 'sha256' => self::HASH], 'notice' => 'Preparação informativa baseada em declarações; documentos associados não constituem validação jurídica, certificado ou submissão à Administração.'];
|
||
}
|
||
}
|