Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95b33275aa |
@@ -9891,3 +9891,16 @@ Entrega expressamente autorizada e instalada pelo executor Git existente na main
|
||||
Backup/restauro fresco de193tabelas/413ficheiros e conferência pós-instalação de21tabelas protegidas sem diferenças. Mantidos instituições, dossiers, documentos, simulações, histórico, Educação, mapa, relatórios, pesquisa, suporte, permissões e12lotes/920456observações demográficas.23fontes conferidas por hash; revisão pública e HTTPS verificados. Executor/configuração inalterados, zero migrações/importações; readiness demográfica vinculada à revisão com original arquivado. CI obrigatório executado normalmente; provas anteriores reutilizadas sem nova ronda manual. Recibo: outputs/irpc-pec-last-amount-20261113/homologation-installed.json. Pacote agrupado v2 selado preservado; a versão anterior do ZIP conserva-se como histórico de normalização de finais de linha, sem alteração do produto.
|
||||
|
||||
Este lote deixa de ser pendência de instalação. L8 continua parcial para períodos/regimes especiais e casos fora do recorte; os resíduos L1–L7/restanteL8/L9–L16 e restantes módulos mantêm o saldo próprio. Próximo lote compatível: projecção conjunta PPC/PEC no ecrã e relatório, separada dos comprovativos pagos e do fecho anual.
|
||||
|
||||
|
||||
## 13/11/2026 — lote integrado de projecção mensal PPC/PEC, QA
|
||||
|
||||
Continuidade do saldo de integração e relatórios AUD-SIM-INT02/INT06, após f059c226 instalado. Antes deste delta, as estimativas PPC e a hipótese PEC eram apresentadas separadamente; faltava a vista mensal conjunta. O lote reúne a apresentação no ecrã L8, a mesma projecção no relatório do dossier e a separação entre estimativas e comprovativos documentados como pagos. Reutiliza os calculadores aceites, o livro, o rascunho, a recuperação e os exportadores existentes. Não acrescenta um motor fiscal, formulário de pagamento, API, tabela ou permissão.
|
||||
|
||||
A vista agrega cada componente uma única vez por mês, com fracções exactas. A opção PEC concentrada conserva o mês escolhido; a repartida conserva os três montantes explícitos. Uma componente desconhecida ou uma distribuição por reconciliar produz total por determinar e subtotais conhecidos identificados; não vira zero. Zero documentado é distinguido da falta de dados. São previsões PPC/PEC do recorte ordinário2026, não o saldo a executar ou todos os impostos do negócio. Valores pagos, deduções, saldo anual, fluxo financeiro e métricas do investimento não são acrescidos ou substituídos.
|
||||
|
||||
O relatório usa as opções guardadas na versão calculada, preservando as escolhas posteriores ainda por recalcular. Fontes e explicação aparecem no resumo da projecção; as linhas mensais conservam os montantes sem repetir extensamente as mesmas citações. A identificação de dossier/oportunidade e os exportadores existentes mantêm-se. Legados sem estas preparações não recebem uma projecção presumida.
|
||||
|
||||
QA:42testes frontend afectados, incluindo sete novos de agregação exacta, sobreposição, ausência, distribuição decimal, zero, recuperação, relatório e apresentação. Os sete testes do delta foram reconferidos após o ajuste de apresentação; o ensaio da vista L8 confirma os grupos separados, além da tabela. Prova nativa de POST/PUT/GET/recuperação em quatro cenários com42asserções e rollback na base isolada investmoz_demography_test:55441. Cenários:112000MZN totais com64000emMaio, distribuição por seis meses, PEC por completar sem total presumido, e zero explícito sem saída. Saldo anual sintético138MZN e histórico anterior preservados; relatório financeiro do mesmo dossier conferido. Tipos, lint focado sem reformatação dos ficheiros legados, Pint/sintaxe e build QA aprovados. Edge:componente real compilado e relatório, estados completo/parcial/zero e apresentação final sem explicações repetidas. Prévia sintética sem API/BD; provas aceites dos restantes percursos reutilizadas. Provas:outputs/irpc-cashflow-group-20261113/.
|
||||
|
||||
Estado:implementado e verificado em QA, ainda sem instalação. Homologação mantém f059c226. Pacote único compatível; instalação depende de autorização concreta, com backup/restauro fresco e reversão de release preservando snapshots e edições posteriores. Este lote encerra a integração visual e documental destas previsões mensais; não encerra L8 nem o portal. Períodos/regimes especiais, anualização PPC28(3), outros casos documentais e resíduos L1–L7/restanteL8/L9–L16 e módulos conservam o saldo próprio. Execução de pagamentos não é requisito do portal.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -8,6 +8,8 @@ import { SimulationIrpcInternational } from './simulation-irpc-international';
|
||||
import { SimulationIrpcPec } from './simulation-irpc-pec';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
advanceCashflowAmount,
|
||||
annualAdvanceCashflow,
|
||||
annualBook,
|
||||
newAnnualBook,
|
||||
saveAnnualBook,
|
||||
@@ -341,10 +343,14 @@ export function SimulationIrpcAnnual({ draft, onChange }: Props) {
|
||||
})}
|
||||
</fieldset>
|
||||
<fieldset className="space-y-2 rounded border p-3">
|
||||
<legend>PPC efectivamente pagos</legend>
|
||||
<legend>Estimativas PPC e PEC para a viabilidade</legend>
|
||||
<p>Simulação fiscal para avaliar a viabilidade do negócio e as necessidades de tesouraria. Os valores e meses previstos são estimativas; o portal não executa pagamentos nem submete declarações fiscais.</p>
|
||||
<SimulationIrpcPecObligation book={b} onChange={pecObligation=>patch({pecObligation})}/>
|
||||
<SimulationIrpcPpcObligation book={b} onChange={ppcObligation=>patch({ppcObligation})}/>
|
||||
<SimulationIrpcAdvanceCashflow book={b}/>
|
||||
</fieldset>
|
||||
<fieldset className="space-y-2 rounded border p-3">
|
||||
<legend>PPC documentados como pagos</legend>
|
||||
{group('ppc', 'PPC efectuados')}
|
||||
<p>
|
||||
Regista comprovativos de pagamentos por conta; não
|
||||
@@ -613,3 +619,23 @@ export function SimulationIrpcAnnual({ draft, onChange }: Props) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function SimulationIrpcAdvanceCashflow({book}: {book: AnnualBook}) {
|
||||
if (!book.ppcObligation && !book.pecObligation) return null;
|
||||
const m = annualAdvanceCashflow(book);
|
||||
const show = advanceCashflowAmount;
|
||||
return <section className="space-y-3 rounded border p-3" aria-label="Projecção conjunta PPC e PEC">
|
||||
<h5 className="font-semibold">Saídas previstas PPC/PEC por mês</h5>
|
||||
<p>Reúne as estimativas PPC e a hipótese de tesouraria PEC. Os comprovativos pagos e o fecho anual permanecem separados; esta vista não acrescenta outra saída ao fluxo financeiro do negócio.</p>
|
||||
<p role="status">{m.total ? 'Total da projecção: '+show(m.total) : 'Projecção parcial — total por determinar.'}</p>
|
||||
{!!m.pending.length && <ul className="list-disc pl-5">{m.pending.map(p=><li key={p}>{p}</li>)}</ul>}
|
||||
{!!m.rows.length && <div className="overflow-x-auto"><table className="w-full text-left text-sm">
|
||||
<caption className="sr-only">Projecção mensal das estimativas PPC e PEC</caption>
|
||||
<thead><tr><th className="p-2">Mês</th><th className="p-2">PPC</th><th className="p-2">PEC</th><th className="p-2">{m.total ? 'Total previsto' : 'Subtotal conhecido'}</th></tr></thead>
|
||||
<tbody>{m.rows.map(r=><tr key={r.month} className="border-t"><th scope="row" className="p-2">{r.month}</th><td className="p-2">{m.ppcKnown ? show(r.ppc) : 'Por determinar'}</td><td className="p-2">{m.pecKnown ? show(r.pec) : 'Por determinar'}</td><td className="p-2">{show(r.knownSubtotal)}</td></tr>)}</tbody>
|
||||
</table></div>}
|
||||
{!m.rows.length && m.total && <p>Sem saída PPC/PEC estimada neste recorte.</p>}
|
||||
<p>Meses e montantes dependem das fontes e opções preparadas acima. A opção PEC concentrada conserva o mês escolhido; esta apresentação não certifica o prazo legal.</p>
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { calculatePecPlan } from './irpc-pec-plan.ts';
|
||||
import {pecCorrectionVersion} from './irpc-pec-correction.ts';
|
||||
import {pecPlanVersion} from './irpc-pec-plan.ts';
|
||||
import {validatePecObligation,pecObligationReport,pecObligationVersion,pecPpcExcessVersion,calculatePecObligation,type PecObligation} from './irpc-pec-obligation.ts';
|
||||
import { validatePpcObligation, ppcObligationReport, ppcObligationVersion, type PpcObligation } from './irpc-ppc-obligation.ts';
|
||||
import { calculatePpcObligation, validatePpcObligation, ppcObligationReport, ppcObligationVersion, type PpcObligation } from './irpc-ppc-obligation.ts';
|
||||
import {
|
||||
newInvestmentCredit,
|
||||
validateInvestmentCredit,
|
||||
@@ -1727,6 +1728,7 @@ export function annualReportRows(
|
||||
return [
|
||||
...(m.input.pecObligation ? pecObligationReport(m.input.pecObligation,m.subjectNuit,m.year,m.input.assessment) : []),
|
||||
...(m.input.ppcObligation ? ppcObligationReport(m.input.ppcObligation,m.subjectNuit,m.year,m.input.assessment) : []),
|
||||
...annualAdvanceCashflowReport(m.input),
|
||||
[
|
||||
'IRPC — L8',
|
||||
'Fecho anual — âmbito e origem',
|
||||
@@ -2408,3 +2410,64 @@ export function annualHtml(d: Record<string, unknown>) {
|
||||
'</html>'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/** Derived cash view only. Reuses the existing estimates; never changes the annual ledger. */
|
||||
export function annualAdvanceCashflow(b: Pick<AnnualBook, 'subjectNuit' | 'year' | 'assessment' | 'ppcObligation' | 'pecObligation'>) {
|
||||
const pending: string[] = [];
|
||||
const sources: string[] = [];
|
||||
const amounts: Array<{month: string; kind: 'ppc' | 'pec'; amount: FiscalFraction}> = [];
|
||||
let ppcKnown = false, pecKnown = false;
|
||||
if (!b.ppcObligation) pending.push('Prepare a estimativa PPC para completar a projecção conjunta.');
|
||||
else {
|
||||
const p = calculatePpcObligation(b.ppcObligation, b.subjectNuit, b.year, b.assessment);
|
||||
sources.push(b.ppcObligation.assessmentReference, b.ppcObligation.withholdingReference, p.source);
|
||||
pending.push(...p.pending);
|
||||
ppcKnown = p.pending.length === 0 && p.total !== null;
|
||||
if (ppcKnown) amounts.push(...p.schedule.map(r => ({...r, kind: 'ppc' as const})));
|
||||
}
|
||||
if (!b.pecObligation) pending.push('Prepare a estimativa PEC para completar a projecção conjunta.');
|
||||
else {
|
||||
const p = calculatePecObligation(b.pecObligation, b.subjectNuit, b.year, b.assessment);
|
||||
sources.push(b.pecObligation.turnoverReference, b.pecObligation.ppcReference, p.source);
|
||||
pending.push(...p.pending);
|
||||
if (!p.pending.length && p.total) {
|
||||
if (BigInt(p.total.numerator) === 0n) pecKnown = true;
|
||||
else if (!b.pecObligation.plan) pending.push('Distribua a hipótese de tesouraria PEC para completar os meses da projecção conjunta.');
|
||||
else {
|
||||
const allocation = calculatePecPlan(b.pecObligation.plan, p.total, p.notApplicable, b.year);
|
||||
sources.push(b.pecObligation.plan.reference, allocation.source);
|
||||
pending.push(...allocation.pending);
|
||||
pecKnown = allocation.allocationReconciled;
|
||||
if (pecKnown) amounts.push(...allocation.rows.map(r => ({...r, kind: 'pec' as const})));
|
||||
}
|
||||
}
|
||||
}
|
||||
const grouped = new Map<string, {month: string; ppc: FiscalFraction; pec: FiscalFraction}>();
|
||||
for (const row of amounts) {
|
||||
if (BigInt(row.amount.numerator) === 0n) continue;
|
||||
const current = grouped.get(row.month) ?? {month: row.month, ppc: fraction(0n), pec: fraction(0n)};
|
||||
current[row.kind] = addFractions([current[row.kind], row.amount]);
|
||||
grouped.set(row.month, current);
|
||||
}
|
||||
const rows = [...grouped.values()].sort((a,b) => a.month.localeCompare(b.month)).map(r => ({...r, knownSubtotal: addFractions([r.ppc,r.pec])}));
|
||||
const knownSubtotal = addFractions(rows.map(r => r.knownSubtotal));
|
||||
return {version:'IRPC-advance-cashflow/1', ppcKnown, pecKnown, pending:[...new Set(pending)], rows,
|
||||
total: ppcKnown && pecKnown ? knownSubtotal : null, knownSubtotal, sources:[...new Set(sources.filter(s => s.trim()))]};
|
||||
}
|
||||
|
||||
export function advanceCashflowAmount(value: FiscalFraction): string {
|
||||
return fractionPresentation(value).decimal+' MZN';
|
||||
}
|
||||
|
||||
export function annualAdvanceCashflowReport(b: Pick<AnnualBook, 'subjectNuit' | 'year' | 'assessment' | 'ppcObligation' | 'pecObligation'>): Array<[string,string,string,string]> {
|
||||
if (!b.ppcObligation && !b.pecObligation) return [];
|
||||
const m = annualAdvanceCashflow(b);
|
||||
const show = advanceCashflowAmount;
|
||||
const source = m.sources.join('; ');
|
||||
return [
|
||||
['IRPC — L8', 'Projecção mensal conjunta PPC/PEC', (m.total ? 'Total da projecção: '+show(m.total)+'.' : 'Projecção parcial; total por determinar. '+m.pending.join(' '))+' Estimativas de tesouraria; comprovativos pagos, deduções e fecho anual separados. Esta vista não acrescenta outra saída ao fluxo financeiro.', source],
|
||||
...m.rows.map(r => ['IRPC — L8', 'PPC/PEC — '+r.month,
|
||||
'PPC: '+(m.ppcKnown ? show(r.ppc) : 'por determinar')+'; PEC: '+(m.pecKnown ? show(r.pec) : 'por determinar')+'; '+(m.total ? 'total previsto: ' : 'subtotal conhecido: ')+show(r.knownSubtotal)+'.', ''] as [string,string,string,string]),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 IrpcAdvanceCashflowDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_pec_obligation_and_calendar_survive_native_draft_without_new_paid_deductions(): void
|
||||
{
|
||||
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('investmoz_demography_test', $target->name);
|
||||
$this->assertSame(55441, (int) $target->port);
|
||||
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
|
||||
$actor = User::factory()->create();
|
||||
$this->actingAs($actor);
|
||||
$id = null;
|
||||
$etag = null;
|
||||
foreach (['overlap' => 138, 'distributed' => 138, 'partial' => 138, 'zero' => 138] as $name => $balance) {
|
||||
$snapshot = json_decode(file_get_contents(base_path('outputs/irpc-cashflow-group-20261113/'.$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']);
|
||||
$book = json_decode($back['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR)['irpcAnnual'][$snapshot['context_opportunity']];
|
||||
$this->assertCount(1, $book['history']);
|
||||
$this->assertSame(['numerator' => '138', 'denominator' => '1'], $book['history'][0]['model']['arithmeticBalance']);
|
||||
$this->assertSame($balance === null ? null : ['numerator' => (string) $balance, 'denominator' => '1'], $book['result']['model']['arithmeticBalance']);
|
||||
$this->assertSame($book['pecObligation'], $book['result']['model']['input']['pecObligation']);
|
||||
$this->assertSame($book['ppcObligation'], $book['result']['model']['input']['ppcObligation']);
|
||||
$this->assertSame('2026', $book['ppcObligation']['year']);
|
||||
$this->assertSame('SYNTHETIC effective activity'.($name === 'start' ? '2026' : '2020'), $book['pecObligation']['activityReference']);
|
||||
file_put_contents(base_path('outputs/irpc-cashflow-group-20261113/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import {createRequire} from 'node:module';
|
||||
import {compileFunction} from 'node:vm';
|
||||
import ts from 'typescript';
|
||||
import React from 'react';
|
||||
import {renderToStaticMarkup} from 'react-dom/server';
|
||||
import * as annual from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||
import {combinedCase} from './helpers/irpc-investment-combination-case.mjs';
|
||||
import {plan as ppc} from './helpers/irpc-ppc-obligation-case.mjs';
|
||||
import {plan as pec} from './helpers/irpc-pec-obligation-case.mjs';
|
||||
import {newPecPlan} from '../../resources/js/lib/irpc-pec-plan.ts';
|
||||
const require=createRequire(import.meta.url),module={exports:{}};
|
||||
const source=fs.readFileSync(new URL('../../resources/js/components/simulation-irpc-annual.tsx',import.meta.url),'utf8');
|
||||
const code=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,jsx:ts.JsxEmit.ReactJSX}}).outputText;
|
||||
compileFunction(code,['require','module','exports'])(name=>name==='@/lib/irpc-annual-settlement'?annual:name.startsWith('./')?new Proxy({},{get:()=>()=>null}):require(name),module,module.exports);
|
||||
const Summary=module.exports.SimulationIrpcAdvanceCashflow;
|
||||
const book=()=>({...combinedCase().b,ppcObligation:ppc(),pecObligation:{...pec(),plan:{...newPecPlan(),mode:'single',singleMonth:'2026-05',reference:'SYNTHETIC cash choice'}}});
|
||||
const render=b=>renderToStaticMarkup(React.createElement(Summary,{book:b}));
|
||||
test('joint view labels monthly contributions and distinguishes a partial subtotal from a complete forecast',()=>{
|
||||
const b=book(),html=render(b);assert.match(html,/Saídas previstas PPC\/PEC por mês/);assert.match(html,/112000\.00/);assert.match(html,/64000\.00/);assert.match(html,/não acrescenta outra saída/);assert.match(html,/prazo legal/);
|
||||
delete b.pecObligation;const partial=render(b);assert.match(partial,/Projecção parcial/);assert.match(partial,/Subtotal conhecido/);assert.match(partial,/Por determinar/);assert.ok(!partial.includes('Total da projecção:'));
|
||||
});
|
||||
test('explicit zero has a complete zero forecast and legacy absence does not create a forecast',()=>{
|
||||
const b=book();b.ppcObligation.previousTax='0';b.ppcObligation.previousWithholdings='0';b.pecObligation.previousPpcPaid='60000';const html=render(b);assert.match(html,/Total da projecção: 0\.00 MZN/);assert.match(html,/Sem saída PPC\/PEC estimada/);
|
||||
delete b.ppcObligation;delete b.pecObligation;assert.equal(render(b),'');const {d}=combinedCase(),prepared=book();d.modelo_a_snapshot=annual.saveAnnualBook(d,annual.recordAnnual(d,prepared,'2026-11-13T21:45:00Z'));
|
||||
const full=renderToStaticMarkup(React.createElement(module.exports.SimulationIrpcAnnual,{draft:d,onChange:()=>{}}));
|
||||
const estimation=full.indexOf('<legend>Estimativas PPC e PEC para a viabilidade</legend>'),paid=full.indexOf('<legend>PPC documentados como pagos</legend>');
|
||||
assert.ok(estimation>=0 && paid>estimation);const between=full.slice(estimation,paid);assert.match(between,/Saídas previstas PPC\/PEC por mês/);assert.match(between,/<\/fieldset>/);
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {plan as ppcPlan} from './helpers/irpc-ppc-obligation-case.mjs';
|
||||
import {plan as pecPlan} from './helpers/irpc-pec-obligation-case.mjs';
|
||||
import {newPecPlan} from '../../resources/js/lib/irpc-pec-plan.ts';
|
||||
import {combinedCase} from './helpers/irpc-investment-combination-case.mjs';
|
||||
import {annualAdvanceCashflow,annualAdvanceCashflowReport,annualHtml,recordAnnual,saveAnnualBook,annualBook,annualCurrent} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||
const f=n=>({numerator:String(n),denominator:'1'});
|
||||
const book=()=>({...combinedCase().b,ppcObligation:ppcPlan(),pecObligation:{...pecPlan(),plan:{...newPecPlan(),mode:'single',singleMonth:'2026-05',reference:'SYNTHETIC cash hypothesis'}}});
|
||||
test('joint cash projection groups the same month once using existing exact PPC and PEC estimates',()=>{
|
||||
const b=book(),before=JSON.stringify(b),m=annualAdvanceCashflow(b);
|
||||
assert.equal(m.pending.length,0);assert.deepEqual(m.total,f(112000));assert.equal(m.rows.length,3);assert.deepEqual(m.rows[0],{month:'2026-05',ppc:f(24000),pec:f(40000),knownSubtotal:f(64000)});
|
||||
assert.equal(JSON.stringify(b),before);
|
||||
const report=annualAdvanceCashflowReport(b);assert.ok(report[0][3].includes('SYNTHETIC'));assert.ok(report.slice(1).every(r=>r[3]===''));
|
||||
});
|
||||
test('missing or unreconciled component stays unknown; known rows never become a complete total',()=>{
|
||||
const b=book();delete b.pecObligation;let m=annualAdvanceCashflow(b);assert.equal(m.total,null);assert.equal(m.pecKnown,false);assert.deepEqual(m.knownSubtotal,f(72000));
|
||||
const c=book();c.pecObligation.plan={...c.pecObligation.plan,mode:'three',june:'1',august:'1',october:'1'};m=annualAdvanceCashflow(c);assert.equal(m.total,null);assert.equal(m.pecKnown,false);assert.ok(m.pending.length);assert.equal(m.rows.length,3);
|
||||
delete c.ppcObligation;m=annualAdvanceCashflow(c);assert.equal(m.total,null);assert.equal(m.rows.length,0);
|
||||
});
|
||||
test('confirmed zero PEC and zero PPC differ from unavailable estimates and preserve deliberate dates',()=>{
|
||||
const b=book();b.pecObligation.previousPpcPaid='60000';let m=annualAdvanceCashflow(b);assert.equal(m.pecKnown,true);assert.deepEqual(m.total,f(72000));
|
||||
b.ppcObligation.previousTax='0';b.ppcObligation.previousWithholdings='0';m=annualAdvanceCashflow(b);assert.deepEqual(m.total,f(0));assert.equal(m.ppcKnown,true);assert.equal(m.rows.length,0);
|
||||
const c=book();c.pecObligation.plan.singleMonth='2026-04';m=annualAdvanceCashflow(c);assert.equal(m.rows[0].month,'2026-04');assert.deepEqual(m.rows[0].pec,f(40000));
|
||||
});
|
||||
test('monthly decimal allocation retains source precision and sums only the accepted cash hypotheses',()=>{
|
||||
const b=book();b.pecObligation.plan={...newPecPlan(),mode:'three',june:'10000.01',august:'14999.99',october:'15000',reference:'SYNTHETIC three explicit amounts'};
|
||||
const m=annualAdvanceCashflow(b);assert.deepEqual(m.total,f(112000));assert.equal(m.rows.length,6);assert.deepEqual(m.rows.find(r=>r.month==='2026-06').pec,{numerator:'1000001',denominator:'100'});
|
||||
});
|
||||
test('recovery and frozen report use the same cash projection without adding to the annual balance or rewriting history',()=>{
|
||||
const {d}=combinedCase(),b=book();const prior=recordAnnual(d,b,'2026-11-13T21:30:00Z');d.modelo_a_snapshot=saveAnnualBook(d,prior);const recovered=annualBook(d);
|
||||
assert.equal(annualCurrent(d,recovered),true);assert.deepEqual(recovered.result.model.arithmeticBalance,f(138));assert.match(annualHtml(d),/Projecção mensal conjunta PPC\/PEC/);assert.match(annualHtml(d),/64000\.00/);
|
||||
const changed={...recovered,pecObligation:{...recovered.pecObligation,plan:{...recovered.pecObligation.plan,singleMonth:'2026-06'}}};assert.equal(annualCurrent(d,changed),false);
|
||||
assert.deepEqual(annualAdvanceCashflow(changed).total,f(112000));assert.match(annualHtml(d),/PPC\/PEC — 2026-05/);assert.equal(annualAdvanceCashflowReport({...b,ppcObligation:undefined,pecObligation:undefined}).length,0);
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import fs from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import {combinedCase} from '../tests/frontend/helpers/irpc-investment-combination-case.mjs';
|
||||
import {plan as ppc} from '../tests/frontend/helpers/irpc-ppc-obligation-case.mjs';
|
||||
import {plan as pec} from '../tests/frontend/helpers/irpc-pec-obligation-case.mjs';
|
||||
import {newPecPlan} from '../resources/js/lib/irpc-pec-plan.ts';
|
||||
import {annualAdvanceCashflow,annualBook,annualCurrent,annualHtml,recordAnnual,saveAnnualBook} from '../resources/js/lib/irpc-annual-settlement.ts';
|
||||
import {createProjectDossierReport} from '../resources/js/lib/project-dossier-report.ts';
|
||||
const out=new URL('../outputs/irpc-cashflow-group-20261113/',import.meta.url);fs.mkdirSync(out,{recursive:true});
|
||||
for(const name of ['overlap','distributed','partial','zero']){
|
||||
if(process.argv[2]==='generate'){
|
||||
const {d,b}=combinedCase();const prior=recordAnnual(d,b,'2026-11-13T21:40:00Z');
|
||||
prior.ppcObligation={...ppc(),subjectNuit:b.subjectNuit};
|
||||
prior.pecObligation={...pec(),subjectNuit:b.subjectNuit,plan:{...newPecPlan(),mode:'single',singleMonth:'2026-05',reference:'SYNTHETIC cash hypothesis'}};
|
||||
if(name==='distributed')prior.pecObligation.plan={...newPecPlan(),mode:'three',june:'10000.01',august:'14999.99',october:'15000',reference:'SYNTHETIC explicit allocation'};
|
||||
if(name==='partial')delete prior.pecObligation.plan;
|
||||
if(name==='zero'){prior.ppcObligation.previousTax='0';prior.ppcObligation.previousWithholdings='0';prior.pecObligation.previousPpcPaid='60000';}
|
||||
d.modelo_a_snapshot=saveAnnualBook(d,recordAnnual(d,prior,'2026-11-13T21:41:00Z'));
|
||||
fs.writeFileSync(new URL(name+'.json',out),JSON.stringify(d));
|
||||
}else{
|
||||
const d=JSON.parse(fs.readFileSync(new URL('recovered-'+name+'.json',out),'utf8')),b=annualBook(d),before=JSON.stringify(b),m=annualAdvanceCashflow(b);
|
||||
assert.equal(annualCurrent(d,b),true);assert.equal(b.history.length,1);assert.deepEqual(b.result.model.arithmeticBalance,{numerator:'138',denominator:'1'});assert.equal(JSON.stringify(b),before);
|
||||
assert.deepEqual(m.total,name==='partial'?null:{numerator:name==='zero'?'0':'112000',denominator:'1'});
|
||||
if(name==='overlap')assert.deepEqual(m.rows[0].knownSubtotal,{numerator:'64000',denominator:'1'});
|
||||
if(name==='partial')assert.equal(m.pecKnown,false);
|
||||
const html=annualHtml(d),report=createProjectDossierReport(d,{selectedSections:new Set(['finance']),generatedAt:'2026-11-13T21:42:00Z'});
|
||||
assert.match(html,/Projecção mensal conjunta PPC\/PEC/);assert.match(JSON.stringify(report),/Projecção mensal conjunta PPC\/PEC/);
|
||||
fs.writeFileSync(new URL(name+'-report.html',out),html);fs.writeFileSync(new URL(name+'-verification.json',out),JSON.stringify({synthetic:true,nativeApiRecovery:true,annualCurrent:true,historyPreserved:true,annualBalanceUnchanged:true,report:true,model:m},null,2));
|
||||
}
|
||||
}
|
||||
console.log(process.argv[2]==='generate'?'Generated four synthetic cash projection cases':'Verified four recovered cash projections and dossier reports');
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
if (($argv[1] ?? '') !== 'test' || basename($root) !== 'portal-investidor') {
|
||||
throw new RuntimeException('Explicit isolated QA test operation required');
|
||||
}
|
||||
foreach (['APP_ENV' => 'testing', 'APP_KEY' => 'base64:'.base64_encode(str_repeat('Q', 32)),
|
||||
'APP_CONFIG_CACHE' => $root.'/outputs/irpc-cashflow-group-20261113/no-config.php', 'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '55441',
|
||||
'DB_DATABASE' => 'investmoz_demography_test', 'DB_USERNAME' => 'demography_qa', 'DB_PASSWORD' => '', 'DB_URL' => '', 'DATABASE_URL' => '',
|
||||
'CACHE_STORE' => 'array', 'SESSION_DRIVER' => 'array', 'QUEUE_CONNECTION' => 'sync', 'MAIL_MAILER' => 'array', 'SUITECRM_ENABLED' => 'false'] as $key => $value) {
|
||||
putenv($key.'='.$value);
|
||||
$_ENV[$key] = $_SERVER[$key] = $value;
|
||||
}
|
||||
$pdo = new PDO('pgsql:host=127.0.0.1;port=55441;dbname=investmoz_demography_test', 'demography_qa', '');
|
||||
$identity = $pdo->query('select current_database(),inet_server_port()')->fetch(PDO::FETCH_NUM);
|
||||
if ($identity !== ['investmoz_demography_test', 55441] && $identity !== ['investmoz_demography_test', '55441']) {
|
||||
throw new RuntimeException('Isolated QA identity required');
|
||||
}
|
||||
$argv = ['phpunit', '-c', 'phpunit.investmoz.xml', '--do-not-cache-result', 'tests/Feature/IrpcAdvanceCashflowDraftTest.php'];
|
||||
$_SERVER['argv'] = $argv;
|
||||
$_SERVER['argc'] = count($argv);
|
||||
require $root.'/vendor/autoload.php';
|
||||
require $root.'/vendor/phpunit/phpunit/phpunit';
|
||||
Reference in New Issue
Block a user