This commit is contained in:
@@ -1,3 +1,17 @@
|
||||
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
|
||||
|
||||
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
|
||||
|
||||
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente, com rollback, cinco/seis resultados históricos e todos os anteriores preservados. Provas `outputs/irpc-cbf15-window-20261110/{fifth,expired}-verification.json` e relatórios respectivos. Modelo `IRPC-CBF15/2`, livro existente versão2 sem migration; snapshots aceites não são reescritos. Fonte já aceite APIEX2025 PDF71–72,CBF15(4/7), reutilizada sem nova validação de autenticidade. Provas anteriores reutilizadas; apenas fronteiras alteradas conferidas.
|
||||
|
||||
Saldo: transporte de créditos de investimento de anos anteriores, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários oficiais continuam por implementar nos respectivos recortes. A janela corrente não declara o transporte entregue. Resíduos L1–L7, restanteL8 e L9–L16 e saldo dos módulos não fiscais preservados. Conferência visual pendente no mesmo registo; portal não integral. Próximo trabalho executável: transporte documental CBF15 dentro da mesma janela, com consumo anterior reconciliado e sem duplicar o investimento corrente.
|
||||
|
||||
## 10/11/2026 — CBF15 instalado na homologação
|
||||
|
||||
**Revisão instalada: `89e6d72c1e86df8eb86e7be74583659975585036`**, promovida às13:27:59UTC e conferida às13:28:21UTC (15:28 locais), em https://teste-investidor.tempo.co.mz. Os17hashes do delta coincidem com Git; revisão/root/up/login HTTP200, configuração preservada, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos e conferidos;7c8d4726 preservada para reversão. Recibos `outputs/cbf15-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; pacote imutável `outputs/entrega-cbf15-20261110.zip`, SHA25613bdecef2697edcc821d6fa490586d0c2f4b171b4308c655aa60ba5632664c83.
|
||||
|
||||
O recorte CBF15 abaixo deixa de ser pendência de instalação: primeiro exercício2026, residente geral/MZN, activos e condições documentados, colecta integral do projecto ou quota residual documentada, cálculo/gravação/recuperação/relatórios com histórico. As notas QA anteriores ficam como histórico das provas aceites. Outros exercícios/transporte/caducidade, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários e restantes resíduos L1–L7/L8/L9–L16 permanecem no plano existente. Próximo trabalho: residual executável de CBF15/L8, preservando o recorte instalado e sem repetir as provas aceites. Conferência visual continua pendente no mesmo registo; portal não declarado integral.
|
||||
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## 10/11/2026 — acordo PT–MZ instalado; crédito CBF15 demonstrado em QA
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
|
||||
|
||||
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
|
||||
|
||||
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente, com rollback, cinco/seis resultados históricos e todos os anteriores preservados. Provas `outputs/irpc-cbf15-window-20261110/{fifth,expired}-verification.json` e relatórios respectivos. Modelo `IRPC-CBF15/2`, livro existente versão2 sem migration; snapshots aceites não são reescritos. Fonte já aceite APIEX2025 PDF71–72,CBF15(4/7), reutilizada sem nova validação de autenticidade. Provas anteriores reutilizadas; apenas fronteiras alteradas conferidas.
|
||||
|
||||
Saldo: transporte de créditos de investimento de anos anteriores, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários oficiais continuam por implementar nos respectivos recortes. A janela corrente não declara o transporte entregue. Resíduos L1–L7, restanteL8 e L9–L16 e saldo dos módulos não fiscais preservados. Conferência visual pendente no mesmo registo; portal não integral. Próximo trabalho executável: transporte documental CBF15 dentro da mesma janela, com consumo anterior reconciliado e sem duplicar o investimento corrente.
|
||||
|
||||
## 10/11/2026 — CBF15 instalado na homologação
|
||||
|
||||
**Revisão instalada: `89e6d72c1e86df8eb86e7be74583659975585036`**, promovida às13:27:59UTC e conferida às13:28:21UTC (15:28 locais), em https://teste-investidor.tempo.co.mz. Os17hashes do delta coincidem com Git; revisão/root/up/login HTTP200, configuração preservada, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos e conferidos;7c8d4726 preservada para reversão. Recibos `outputs/cbf15-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; pacote imutável `outputs/entrega-cbf15-20261110.zip`, SHA25613bdecef2697edcc821d6fa490586d0c2f4b171b4308c655aa60ba5632664c83.
|
||||
|
||||
O recorte CBF15 abaixo deixa de ser pendência de instalação: primeiro exercício2026, residente geral/MZN, activos e condições documentados, colecta integral do projecto ou quota residual documentada, cálculo/gravação/recuperação/relatórios com histórico. As notas QA anteriores ficam como histórico das provas aceites. Outros exercícios/transporte/caducidade, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários e restantes resíduos L1–L7/L8/L9–L16 permanecem no plano existente. Próximo trabalho: residual executável de CBF15/L8, preservando o recorte instalado e sem repetir as provas aceites. Conferência visual continua pendente no mesmo registo; portal não declarado integral.
|
||||
|
||||
## 10/11/2026 — acordo PT–MZ instalado; crédito CBF15 demonstrado em QA
|
||||
|
||||
**Homologação: `7c8d4726e9f93325480e2ee6e5e790e9adbcaf19`**, promovida às12:36:07UTC e conferida às12:37:11UTC (14:37 locais). Revisão HTTPS/10hashes iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos, b10b35f3 preservada. Recibos `outputs/cdt-agreement-delivery-20261110/`. Falha inicial de recepção Git delimitada ao directório objects/7c pertencente aroot: apenas proprietário/grupo ajustados a conta do serviço,modo0755 inalterado; pedido original validado recolocado sem alterar corpo/SHA ou controlos. Recibos git-permission-repair/request-retry; não foi falha do portal nem alteração de segurança do navegador. Sessão SSH preservada para continuação autenticada, sem credencial em disco.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,17 @@
|
||||
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
|
||||
|
||||
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
|
||||
|
||||
Cálculo → POST/PUT do mesmo rascunho privado → GET → recálculo → HTML/dossier demonstrados no QA55441 existente, com rollback, cinco/seis resultados históricos e todos os anteriores preservados. Provas `outputs/irpc-cbf15-window-20261110/{fifth,expired}-verification.json` e relatórios respectivos. Modelo `IRPC-CBF15/2`, livro existente versão2 sem migration; snapshots aceites não são reescritos. Fonte já aceite APIEX2025 PDF71–72,CBF15(4/7), reutilizada sem nova validação de autenticidade. Provas anteriores reutilizadas; apenas fronteiras alteradas conferidas.
|
||||
|
||||
Saldo: transporte de créditos de investimento de anos anteriores, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários oficiais continuam por implementar nos respectivos recortes. A janela corrente não declara o transporte entregue. Resíduos L1–L7, restanteL8 e L9–L16 e saldo dos módulos não fiscais preservados. Conferência visual pendente no mesmo registo; portal não integral. Próximo trabalho executável: transporte documental CBF15 dentro da mesma janela, com consumo anterior reconciliado e sem duplicar o investimento corrente.
|
||||
|
||||
## 10/11/2026 — CBF15 instalado na homologação
|
||||
|
||||
**Revisão instalada: `89e6d72c1e86df8eb86e7be74583659975585036`**, promovida às13:27:59UTC e conferida às13:28:21UTC (15:28 locais), em https://teste-investidor.tempo.co.mz. Os17hashes do delta coincidem com Git; revisão/root/up/login HTTP200, configuração preservada, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos e conferidos;7c8d4726 preservada para reversão. Recibos `outputs/cbf15-delivery-20261110/{backup,restore,files-restore,verification,delivery}.json`; pacote imutável `outputs/entrega-cbf15-20261110.zip`, SHA25613bdecef2697edcc821d6fa490586d0c2f4b171b4308c655aa60ba5632664c83.
|
||||
|
||||
O recorte CBF15 abaixo deixa de ser pendência de instalação: primeiro exercício2026, residente geral/MZN, activos e condições documentados, colecta integral do projecto ou quota residual documentada, cálculo/gravação/recuperação/relatórios com histórico. As notas QA anteriores ficam como histórico das provas aceites. Outros exercícios/transporte/caducidade, repartição automática/múltiplos projectos, EP/L7/cumulações/outros benefícios/formulários e restantes resíduos L1–L7/L8/L9–L16 permanecem no plano existente. Próximo trabalho: residual executável de CBF15/L8, preservando o recorte instalado e sem repetir as provas aceites. Conferência visual continua pendente no mesmo registo; portal não declarado integral.
|
||||
|
||||
## 10/11/2026 — acordo PT–MZ instalado; crédito CBF15 demonstrado em QA
|
||||
|
||||
**Homologação: `7c8d4726e9f93325480e2ee6e5e790e9adbcaf19`**, promovida às12:36:07UTC e conferida às12:37:11UTC (14:37 locais). Revisão HTTPS/10hashes iguais, quatro entradasHTTP200; configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos, b10b35f3 preservada. Recibos `outputs/cdt-agreement-delivery-20261110/`. Falha inicial de recepção Git delimitada ao directório objects/7c pertencente aroot: apenas proprietário/grupo ajustados a conta do serviço,modo0755 inalterado; pedido original validado recolocado sem alterar corpo/SHA ou controlos. Recibos git-permission-repair/request-retry; não foi falha do portal nem alteração de segurança do navegador. Sessão SSH preservada para continuação autenticada, sem credencial em disco.
|
||||
|
||||
@@ -27,10 +27,11 @@ export function SimulationIrpcInvestment({
|
||||
<p>
|
||||
Prepare o detalhe do investimento efectivo e da qualificação
|
||||
do projecto. Substitui neste cálculo o resumo anterior dos
|
||||
benefícios, que fica conservado. O primeiro recorte cobre o
|
||||
exercício inicial2026,com colecta integral ou quota residual
|
||||
documental do projecto, sem outros benefícios próprios ou
|
||||
transportes anteriores.
|
||||
benefícios, que fica conservado. O recorte cobre
|
||||
investimento efectivo de2026 dentro dos cinco exercícios do
|
||||
prazo,com colecta integral ou quota residual documental do
|
||||
projecto, sem outros benefícios próprios ou transportes
|
||||
anteriores.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
@@ -275,7 +276,7 @@ export function SimulationIrpcInvestment({
|
||||
{text('startDate', 'Data do início aplicável — AAAA-MM-DD')}
|
||||
{text(
|
||||
'startReference',
|
||||
'Suporte do início e primeiro exercício2026',
|
||||
'Suporte do início e contagem dos cinco exercícios',
|
||||
)}
|
||||
<p>
|
||||
Indique investimento efectivamente realizado,
|
||||
|
||||
@@ -2147,6 +2147,19 @@ export function annualReportRows(
|
||||
m.investmentCredit.version +
|
||||
'; ' +
|
||||
m.investmentCredit.source +
|
||||
(m.investmentCredit.window
|
||||
? '; JanelaCBF15/1: exercícios ' +
|
||||
m.investmentCredit.window.firstYear +
|
||||
'–' +
|
||||
m.investmentCredit.window.lastYear +
|
||||
'; exercício ' +
|
||||
m.investmentCredit.window.exercise +
|
||||
'/5; ' +
|
||||
(m.investmentCredit.window.status ===
|
||||
'expired'
|
||||
? 'prazo expirado'
|
||||
: 'dentro do prazo')
|
||||
: '') +
|
||||
'; projecto ' +
|
||||
m.investmentCredit.input.projectReference +
|
||||
'; ' +
|
||||
|
||||
@@ -21,9 +21,9 @@ import {
|
||||
fraction,
|
||||
type FiscalFraction,
|
||||
} from './fiscal-rational.ts';
|
||||
export const investmentCreditVersion = 'IRPC-CBF15/1';
|
||||
export const investmentCreditVersion = 'IRPC-CBF15/2';
|
||||
export const investmentCreditSource =
|
||||
'CBF,Lei4/2009,arts3/4/9/10/12/13/15; compilação oficial APIEX2025,PDF66–72,art15PDF71–72. Primeiro exercício2026,regime normal residente,ano civil/MZN,colecta inteiramente do projecto documentada. Activo corpóreo novo afecto à exploração nacional,sem exclusões15(6). Cidade de Maputo5%,restantes províncias10%. Menor entre crédito disponível e colecta remanescente depois dos créditos económicos/internacionais,antesPEC/RF/PPC (RIRPC21). Sem transporte/reembolso ou consumo de saldo oficial presumidos;outros exercícios,regimes e cumulações separados.';
|
||||
'CBF,Lei4/2009,arts3/4/9/10/12/13/15; compilação oficial APIEX2025,PDF66–72,art15PDF71–72. Liquidação2026,regime normal residente,ano civil/MZN,colecta integral ou quota residual documentada do projecto. Janela de cinco exercícios desde o início aplicável15(4/7),incluindo o primeiro; activos efectivos de2026,sem transporte de activos/créditos anteriores. Activo corpóreo novo afecto à exploração nacional,sem exclusões15(6). Cidade de Maputo5%,restantes províncias10%. Menor entre crédito disponível e colecta remanescente depois dos créditos económicos/internacionais,antesPEC/RF/PPC (RIRPC21). Sem transporte/reembolso ou consumo de saldo oficial presumidos;outros exercícios,regimes e cumulações separados.';
|
||||
type Choice = '' | 'yes' | 'no';
|
||||
export type InvestmentAsset = {
|
||||
id: string;
|
||||
@@ -197,7 +197,7 @@ const zero = () => fraction(0n);
|
||||
const minimum = (a: FiscalFraction, b: FiscalFraction) =>
|
||||
creditCompare(a, b) < 0n ? a : b;
|
||||
const iso = (s: string) =>
|
||||
/^2026-\d{2}-\d{2}$/.test(s) &&
|
||||
/^\d{4}-\d{2}-\d{2}$/.test(s) &&
|
||||
Number.isFinite(Date.parse(s)) &&
|
||||
new Date(s).toISOString().slice(0, 10) === s;
|
||||
export const investmentAssetIdentity = (r: InvestmentAsset) =>
|
||||
@@ -222,6 +222,15 @@ export function calculateInvestmentCredit(
|
||||
available: FiscalFraction | null = null,
|
||||
rate: number | null = null;
|
||||
let projectCeiling: FiscalFraction | undefined;
|
||||
let window:
|
||||
| {
|
||||
firstYear: number;
|
||||
lastYear: number;
|
||||
currentYear: number;
|
||||
exercise: number;
|
||||
status: 'active' | 'expired';
|
||||
}
|
||||
| undefined;
|
||||
const rows: {
|
||||
input: InvestmentAsset;
|
||||
value: FiscalFraction | null;
|
||||
@@ -284,8 +293,20 @@ export function calculateInvestmentCredit(
|
||||
!c.startReference.trim()
|
||||
)
|
||||
throw Error(
|
||||
'Local efectivo do investimento e primeiro exercício2026 por documentar: início de exploração para projecto novo,procedimentos após aprovação para projecto em funcionamento.',
|
||||
'Local efectivo do investimento e início aplicável por documentar: início de exploração para projecto novo,procedimentos após aprovação para projecto em funcionamento.',
|
||||
);
|
||||
const firstYear = Number(c.startDate.slice(0, 4));
|
||||
if (firstYear < 1 || firstYear > Number(b.year))
|
||||
throw Error(
|
||||
'Ano inicial inválido ou posterior à liquidação2026; não antecipar o crédito.',
|
||||
);
|
||||
window = {
|
||||
firstYear,
|
||||
lastYear: firstYear + 4,
|
||||
currentYear: Number(b.year),
|
||||
exercise: Number(b.year) - firstYear + 1,
|
||||
status: Number(b.year) > firstYear + 4 ? 'expired' : 'active',
|
||||
};
|
||||
if (!c.assets.length)
|
||||
throw Error(
|
||||
'Investimento conhecido exige activo/documento; zero não se presume.',
|
||||
@@ -321,6 +342,7 @@ export function calculateInvestmentCredit(
|
||||
!r.issuer.trim() ||
|
||||
!r.document.trim() ||
|
||||
!iso(r.date) ||
|
||||
!r.date.startsWith(b.year + '-') ||
|
||||
!r.reference.trim() ||
|
||||
r.newTangible !== 'yes' ||
|
||||
r.domesticUse !== 'yes' ||
|
||||
@@ -362,10 +384,12 @@ export function calculateInvestmentCredit(
|
||||
}
|
||||
rows.push({
|
||||
input: structuredClone(r),
|
||||
value,
|
||||
value: value && window?.status === 'expired' ? zero() : value,
|
||||
reason: issues.length
|
||||
? issues.join(' ')
|
||||
: 'Activo elegível documentado.',
|
||||
: window?.status === 'expired'
|
||||
? 'Prazo15(4) terminado no quinto exercício; investimento2026 conservado,sem crédito.'
|
||||
: 'Activo elegível documentado.',
|
||||
pending: issues,
|
||||
});
|
||||
pending.push(...issues);
|
||||
@@ -437,6 +461,7 @@ export function calculateInvestmentCredit(
|
||||
investment,
|
||||
...(projectCeiling === undefined ? {} : { projectCeiling }),
|
||||
rate,
|
||||
...(window ? { window } : {}),
|
||||
nominal,
|
||||
priorUse,
|
||||
available: pending.length ? null : available,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class IrpcInvestmentWindowDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_derived_national_fraction_round_trips_through_the_existing_private_draft_writer(): void
|
||||
{
|
||||
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('proactive_investment_20261027_test', $target->name);
|
||||
$this->assertSame(55441, (int) $target->port);
|
||||
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
|
||||
$this->actingAs(User::findOrFail(1021));
|
||||
$id = null;
|
||||
$etag = null;
|
||||
foreach (['fifth', 'expired'] as $name) {
|
||||
$snapshot = json_decode(file_get_contents(base_path('outputs/irpc-cbf15-window-20261110/'.$name.'.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$personal = ['person_type' => $snapshot['person_type']];
|
||||
unset($snapshot['person_type']);
|
||||
$payload = ['personal_snapshot' => $personal, 'economic_snapshot' => $snapshot];
|
||||
if ($id === null) {
|
||||
$id = $this->postJson('/api/v1/simulation-drafts', $payload, ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json('data.id');
|
||||
} else {
|
||||
$this->putJson('/api/v1/simulation-drafts/'.$id, $payload, ['If-Match' => $etag])->assertOk();
|
||||
}
|
||||
$response = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk();
|
||||
$etag = $response->headers->get('ETag');
|
||||
$back = $response->json('data.economic_snapshot');
|
||||
$this->assertSame($snapshot['modelo_a_snapshot'], $back['modelo_a_snapshot']);
|
||||
$this->assertSame($snapshot['business_plan_snapshot'], $back['business_plan_snapshot']);
|
||||
$book = json_decode($back['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR)['irpcAnnual'][$snapshot['context_opportunity']];
|
||||
$this->assertSame(2, $book['version']);
|
||||
$this->assertCount(array_search($name, ['fifth', 'expired'], true) + 5, $book['history']);
|
||||
$model = $book['result']['model'];
|
||||
$this->assertSame($name === 'fifth' ? 'active' : 'expired', $model['investmentCredit']['window']['status']);
|
||||
$this->assertSame('IRPC-CBF15/2', $model['investmentCredit']['version']);
|
||||
$this->assertSame(['numerator' => $name === 'fifth' ? '200' : '0', 'denominator' => '1'], $model['investmentCredit']['deducted']);
|
||||
$this->assertSame(['numerator' => $name === 'fifth' ? '500' : '700', 'denominator' => '1'], $model['arithmeticBalance']);
|
||||
file_put_contents(base_path('outputs/irpc-cbf15-window-20261110/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ void test('CBF15 requires actual project,source and first-exercise conditions wi
|
||||
{ soleProject: '' },
|
||||
{ noOtherBenefits: 'no' },
|
||||
{ assessmentKey: 'stale' },
|
||||
{ startDate: '2025-01-01' },
|
||||
{ startDate: '2027-01-01' },
|
||||
]) {
|
||||
const c = investmentCase();
|
||||
Object.assign(c.b.investmentCredit, patch);
|
||||
@@ -188,7 +188,7 @@ void test('CBF15 calculation-save-recovery-HTML and dossier reports retain sourc
|
||||
numerator: '200',
|
||||
denominator: '1',
|
||||
});
|
||||
assert.match(annualHtml(d), /IRPC-CBF15\/1/);
|
||||
assert.match(annualHtml(d), /IRPC-CBF15\/2/);
|
||||
assert.match(
|
||||
JSON.stringify(
|
||||
createProjectDossierReport(d, {
|
||||
@@ -196,7 +196,7 @@ void test('CBF15 calculation-save-recovery-HTML and dossier reports retain sourc
|
||||
generatedAt: '2026-11-10T19:00:00+02:00',
|
||||
}),
|
||||
),
|
||||
/IRPC-CBF15\/1/,
|
||||
/IRPC-CBF15\/2/,
|
||||
);
|
||||
const old = internationalCase();
|
||||
assert.equal(calculateAnnual(old.d, old.b).investmentCredit, undefined);
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { investmentCase } from './helpers/irpc-investment-case.mjs';
|
||||
import {
|
||||
calculateAnnual,
|
||||
recordAnnual,
|
||||
saveAnnualBook,
|
||||
annualBook,
|
||||
annualCurrent,
|
||||
annualHtml,
|
||||
} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
||||
void test('CBF15 includes first through fifth civil fiscal exercises, including a late-year start', () => {
|
||||
for (const [start, exercise] of [
|
||||
['2026-06-01', 1],
|
||||
['2025-12-31', 2],
|
||||
['2024-12-31', 3],
|
||||
['2023-12-31', 4],
|
||||
['2022-12-31', 5],
|
||||
]) {
|
||||
const c = investmentCase();
|
||||
c.b.investmentCredit.startDate = start;
|
||||
const m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.equal(m.investmentCredit.window.exercise, exercise);
|
||||
assert.equal(m.investmentCredit.window.status, 'active');
|
||||
assert.equal(
|
||||
m.investmentCredit.window.lastYear,
|
||||
Number(start.slice(0, 4)) + 4,
|
||||
);
|
||||
assert.deepEqual(m.investmentCredit.deducted, {
|
||||
numerator: '500',
|
||||
denominator: '1',
|
||||
});
|
||||
}
|
||||
});
|
||||
void test('CBF15 sixth exercise expires without erasing actual investment or creating a carry/refund', () => {
|
||||
const c = investmentCase();
|
||||
c.b.investmentCredit.startDate = '2021-12-31';
|
||||
const m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.equal(m.investmentCredit.window.exercise, 6);
|
||||
assert.equal(m.investmentCredit.window.status, 'expired');
|
||||
assert.deepEqual(m.investmentCredit.deducted, {
|
||||
numerator: '0',
|
||||
denominator: '1',
|
||||
});
|
||||
assert.deepEqual(m.arithmeticBalance, {
|
||||
numerator: '700',
|
||||
denominator: '1',
|
||||
});
|
||||
assert.equal(m.investmentCredit.rows[0].input.amount, '10000');
|
||||
assert.match(m.investmentCredit.rows[0].reason, /quinto/);
|
||||
assert.equal(m.investmentCredit.carryCreated, false);
|
||||
});
|
||||
void test('CBF15 does not admit future/invalid start dates or import older assets as current-year investments', () => {
|
||||
for (const start of ['2027-01-01', '2025-02-29', '0000-01-01', '']) {
|
||||
const c = investmentCase();
|
||||
c.b.investmentCredit.startDate = start;
|
||||
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
||||
}
|
||||
const c = investmentCase();
|
||||
c.b.investmentCredit.startDate = '2022-01-01';
|
||||
c.b.investmentCredit.assets[0].date = '2025-12-31';
|
||||
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
||||
});
|
||||
void test('CBF15 existing project uses procedural start; new project can invest before operations within the same window', () => {
|
||||
const c = investmentCase();
|
||||
c.b.investmentCredit.startKind = 'existing';
|
||||
c.b.investmentCredit.startDate = '2022-12-31';
|
||||
assert.deepEqual(calculateAnnual(c.d, c.b).pending, []);
|
||||
c.b.investmentCredit.startDate = '2026-06-01';
|
||||
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
||||
c.b.investmentCredit.startKind = 'new';
|
||||
assert.deepEqual(calculateAnnual(c.d, c.b).pending, []);
|
||||
});
|
||||
void test('CBF15 fifth-year then expiry recovers and reports window while preserving earlier result', () => {
|
||||
const c = investmentCase();
|
||||
c.b.investmentCredit.startDate = '2022-12-31';
|
||||
let b = recordAnnual(c.d, c.b, '2026-11-10T14:00:00Z');
|
||||
b.investmentCredit.startDate = '2021-12-31';
|
||||
b = recordAnnual(c.d, b, '2026-11-10T14:01:00Z');
|
||||
const d = { ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, b) },
|
||||
back = annualBook(d);
|
||||
assert.equal(annualCurrent(d, back), true);
|
||||
assert.equal(back.history.length, 1);
|
||||
assert.deepEqual(back.history[0].model.investmentCredit.deducted, {
|
||||
numerator: '500',
|
||||
denominator: '1',
|
||||
});
|
||||
assert.deepEqual(
|
||||
calculateAnnual(d, back).investmentCredit,
|
||||
back.result.model.investmentCredit,
|
||||
);
|
||||
assert.match(annualHtml(d), /JanelaCBF15\/1/);
|
||||
assert.match(annualHtml(d), /prazo expirado/);
|
||||
assert.match(
|
||||
JSON.stringify(
|
||||
createProjectDossierReport(d, {
|
||||
selectedSections: new Set(['finance']),
|
||||
generatedAt: '2026-11-10T14:01:00Z',
|
||||
}),
|
||||
),
|
||||
/JanelaCBF15/,
|
||||
);
|
||||
});
|
||||
@@ -18,7 +18,7 @@ $identity = $pdo->query('select current_database() as name,inet_server_port() as
|
||||
if ($identity['name'] !== 'proactive_investment_20261027_test' || (int) $identity['port'] !== 55441) {
|
||||
throw new RuntimeException('Isolated QA identity required.');
|
||||
}
|
||||
$case = ['quota' => 'IrpcEconomicQuotaDraftTest', 'inclusion' => 'IrpcEconomicInclusionDraftTest', 'liquidation' => 'IrpcEconomicLiquidationDraftTest', 'association' => 'IrpcEconomicAssociationDraftTest', 'special-inclusion' => 'IrpcEconomicSpecialInclusionDraftTest', 'pt-royalty' => 'IrpcPtMzCreditDraftTest', 'pt-capital' => 'IrpcPtMzCapitalDraftTest', 'pt-government' => 'IrpcPtMzGovernmentDraftTest', 'economic-fraction' => 'IrpcEconomicFractionDraftTest', 'pt-agreement' => 'IrpcPtMzAgreementDraftTest', 'investment' => 'IrpcInvestmentCreditDraftTest', 'investment-quota' => 'IrpcInvestmentQuotaDraftTest'][$argv[2] ?? ''] ?? throw new RuntimeException('Choose an explicit supported QA case.');
|
||||
$case = ['quota' => 'IrpcEconomicQuotaDraftTest', 'inclusion' => 'IrpcEconomicInclusionDraftTest', 'liquidation' => 'IrpcEconomicLiquidationDraftTest', 'association' => 'IrpcEconomicAssociationDraftTest', 'special-inclusion' => 'IrpcEconomicSpecialInclusionDraftTest', 'pt-royalty' => 'IrpcPtMzCreditDraftTest', 'pt-capital' => 'IrpcPtMzCapitalDraftTest', 'pt-government' => 'IrpcPtMzGovernmentDraftTest', 'economic-fraction' => 'IrpcEconomicFractionDraftTest', 'pt-agreement' => 'IrpcPtMzAgreementDraftTest', 'investment' => 'IrpcInvestmentCreditDraftTest', 'investment-quota' => 'IrpcInvestmentQuotaDraftTest', 'investment-window' => 'IrpcInvestmentWindowDraftTest'][$argv[2] ?? ''] ?? throw new RuntimeException('Choose an explicit supported QA case.');
|
||||
$argv = ['phpunit', '-c', 'phpunit.investmoz.xml', '--do-not-cache-result', 'tests/Feature/'.$case.'.php'];
|
||||
$argc = count($argv);
|
||||
$_SERVER['argv'] = $argv;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import fs from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { investmentCase } from '../tests/frontend/helpers/irpc-investment-case.mjs';
|
||||
import { recordAnnual,saveAnnualBook,annualBook,annualCurrent,calculateAnnual,annualHtml } from '../resources/js/lib/irpc-annual-settlement.ts';
|
||||
import { createProjectDossierReport } from '../resources/js/lib/project-dossier-report.ts';
|
||||
const out=new URL('../outputs/irpc-cbf15-window-20261110/',import.meta.url);fs.mkdirSync(out,{recursive:true});
|
||||
const original=JSON.parse(fs.readFileSync(new URL('../outputs/irpc-cbf15-20261110/recovered-partial.json',import.meta.url),'utf8'));let current=annualBook(original);
|
||||
for(const name of ['fifth','expired']){
|
||||
if(process.argv[2]==='recovered'){
|
||||
const d=JSON.parse(fs.readFileSync(new URL('recovered-'+name+'.json',out),'utf8')),b=annualBook(d),m=b.result.model;
|
||||
assert.equal(annualCurrent(d,b),true);assert.deepEqual(calculateAnnual(d,b).investmentCredit,m.investmentCredit);assert.equal(b.history.length,name==='fifth'?5:6);assert.equal(b.history[0].model.investmentCredit?.version??'legacy','legacy');
|
||||
const html=annualHtml(d),report=createProjectDossierReport(d,{selectedSections:new Set(['finance']),generatedAt:new Date().toISOString()});assert.match(html,/JanelaCBF15/);assert.match(JSON.stringify(report),/JanelaCBF15/);
|
||||
fs.writeFileSync(new URL(name+'-report.html',out),html);fs.writeFileSync(new URL(name+'-verification.json',out),JSON.stringify({synthetic:true,apiRecovered:true,currentRecalculation:true,html:true,dossierReport:true,history:b.history.length,window:m.investmentCredit.window,deducted:m.investmentCredit.deducted,balance:m.arithmeticBalance,activeChanged:false},null,2));
|
||||
}else{
|
||||
current=structuredClone(current);current.investmentCredit.startDate=name==='fifth'?'2022-12-31':'2021-12-31';current.investmentCredit.startReference='SYNTHETIC documented original start, civil exercises inclusive; not a new administrative decision';current=recordAnnual(original,current,new Date().toISOString());fs.writeFileSync(new URL(name+'.json',out),JSON.stringify({...original,person_type:investmentCase().d.person_type,modelo_a_snapshot:saveAnnualBook(original,current)}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user