Clarify viability simulation purpose and preserve PEC cash-flow hypotheses
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
## Simulação de viabilidade — clarificação expressa do proprietário,11/11/2026
|
||||
|
||||
O InvesteMoz simula impostos para avaliar a viabilidade do negócio; não actua como entidade que executa pagamentos. Apresentar PPC/PEC e distribuição por meses como estimativas fiscais e projecções de tesouraria,com fontes e pressupostos. Montantes já pagos,quando fornecidos,são dados documentais para o cálculo. Não confundir uma hipótese de caixa com pagamento efectuado,declaração submetida ou cumprimento fiscal certificado. Preservar os cálculos,históricos e dados aceites; não criar serviços de cobrança/execução de pagamentos como requisito de conclusão deste simulador.
|
||||
|
||||
# Instruções permanentes do projeto InvesteMoz
|
||||
|
||||
## Encerramento e reabertura do dossier — decisão do proprietário, 03/11/2026
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 11/11/2026 — finalidade do simulador e projecção de tesouraria PEC
|
||||
|
||||
**Clarificação expressa do proprietário:** o portal simula impostos para avaliar a viabilidade do negócio; não faz pagamentos. A apresentação anterior de «obrigação» e «plano de pagamento» criava uma expectativa errada de execução. Ecrãs PPC/PEC passam a **Simular PPC/PEC**,estimativa fiscal e projecção de tesouraria. Fontes legais e dados documentais já pagos continuam a informar o cálculo; não são operações executadas pelo portal. A execução de pagamentos não é pendência deste simulador.
|
||||
|
||||
**Implementado em QA:** hipótese PEC distribuída por Junho/Agosto/Outubro com montantes explícitos cuja soma corresponde exactamente ao total,ou hipótese concentrada num mês escolhido. Não inventa igualdade/arredondamento ou validação de prazo para a opção única. Valores desconhecidos,divergência da soma,total alterado e ausência de estimativa positiva invalidam a projecção dependente,sem bloquear o cálculo fiscal. Trocar hipótese conserva edições;resumo com Alterar. Rascunho privado,recuperação,histórico e relatório do mesmo dossier reutilizados. As estimativas não alimentam os comprovativos pagos nem alteram deduções anuais.
|
||||
|
||||
Cinco verificações novas de cálculo/interface e35afectadas aprovadas;percurso sintéticoAPI/recuperação/relatório em quatro cenários(distribuído,concentrado,divergente,total alterado),com saldo anual138MZN conservado. Provas `outputs/irpc-pec-plan-20261111/*-verification.json`. Sem migração,base,papel ou permissão nova. Tipos/lint/compilação aprovados;uma prova nativa/38asserções com rollback. Pacote Git e reversão conferidos no fecho. Isto não é conferência visual.
|
||||
|
||||
**Estado:** QA,ainda sem instalação;homologação permanece `bcbbe51c` e270instituições publicadas. Restam instalação/visual e recortes fiscais de base29(5),rectificação29(3),PPC excedente,períodos/regimes e outras regras de simulação;prazo legal da hipótese única não é certificado. L1–L7/restanteL8/L9–L16 e demais módulos permanecem no saldo,sem transformar o portal em sistema de execução fiscal. Próximo trabalho:primeiro residual de simulação executável com fontes aceites.
|
||||
|
||||
## 11/11/2026 — total e calendário PEC instalados embcbbe51c
|
||||
|
||||
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:28:18Africa/Johannesburg(08:28:18UTC): `bcbbe51c2fc279d5b2d85412f7bcb6ca2423a9b6`. Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PEC**,com total ordinário2026 baseado no volume documental2025,limites antes dos PPC pagos anteriores,mesesJunho/Agosto/Outubro e dispensa no ano de início efectivo. Guardar/recuperar pelo rascunho privado e apresentar no relatório do mesmo dossier. Não cria pagamento,dedução anual ou reembolso.
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 11/11/2026 — finalidade do simulador e projecção de tesouraria PEC
|
||||
|
||||
**Clarificação expressa do proprietário:** o portal simula impostos para avaliar a viabilidade do negócio; não faz pagamentos. A apresentação anterior de «obrigação» e «plano de pagamento» criava uma expectativa errada de execução. Ecrãs PPC/PEC passam a **Simular PPC/PEC**,estimativa fiscal e projecção de tesouraria. Fontes legais e dados documentais já pagos continuam a informar o cálculo; não são operações executadas pelo portal. A execução de pagamentos não é pendência deste simulador.
|
||||
|
||||
**Implementado em QA:** hipótese PEC distribuída por Junho/Agosto/Outubro com montantes explícitos cuja soma corresponde exactamente ao total,ou hipótese concentrada num mês escolhido. Não inventa igualdade/arredondamento ou validação de prazo para a opção única. Valores desconhecidos,divergência da soma,total alterado e ausência de estimativa positiva invalidam a projecção dependente,sem bloquear o cálculo fiscal. Trocar hipótese conserva edições;resumo com Alterar. Rascunho privado,recuperação,histórico e relatório do mesmo dossier reutilizados. As estimativas não alimentam os comprovativos pagos nem alteram deduções anuais.
|
||||
|
||||
Cinco verificações novas de cálculo/interface e35afectadas aprovadas;percurso sintéticoAPI/recuperação/relatório em quatro cenários(distribuído,concentrado,divergente,total alterado),com saldo anual138MZN conservado. Provas `outputs/irpc-pec-plan-20261111/*-verification.json`. Sem migração,base,papel ou permissão nova. Tipos/lint/compilação aprovados;uma prova nativa/38asserções com rollback. Pacote Git e reversão conferidos no fecho. Isto não é conferência visual.
|
||||
|
||||
**Estado:** QA,ainda sem instalação;homologação permanece `bcbbe51c` e270instituições publicadas. Restam instalação/visual e recortes fiscais de base29(5),rectificação29(3),PPC excedente,períodos/regimes e outras regras de simulação;prazo legal da hipótese única não é certificado. L1–L7/restanteL8/L9–L16 e demais módulos permanecem no saldo,sem transformar o portal em sistema de execução fiscal. Próximo trabalho:primeiro residual de simulação executável com fontes aceites.
|
||||
|
||||
## 11/11/2026 — total e calendário PEC instalados embcbbe51c
|
||||
|
||||
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:28:18Africa/Johannesburg(08:28:18UTC): `bcbbe51c2fc279d5b2d85412f7bcb6ca2423a9b6`. Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PEC**,com total ordinário2026 baseado no volume documental2025,limites antes dos PPC pagos anteriores,mesesJunho/Agosto/Outubro e dispensa no ano de início efectivo. Guardar/recuperar pelo rascunho privado e apresentar no relatório do mesmo dossier. Não cria pagamento,dedução anual ou reembolso.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,13 @@
|
||||
## 11/11/2026 — finalidade do simulador e projecção de tesouraria PEC
|
||||
|
||||
**Clarificação expressa do proprietário:** o portal simula impostos para avaliar a viabilidade do negócio; não faz pagamentos. A apresentação anterior de «obrigação» e «plano de pagamento» criava uma expectativa errada de execução. Ecrãs PPC/PEC passam a **Simular PPC/PEC**,estimativa fiscal e projecção de tesouraria. Fontes legais e dados documentais já pagos continuam a informar o cálculo; não são operações executadas pelo portal. A execução de pagamentos não é pendência deste simulador.
|
||||
|
||||
**Implementado em QA:** hipótese PEC distribuída por Junho/Agosto/Outubro com montantes explícitos cuja soma corresponde exactamente ao total,ou hipótese concentrada num mês escolhido. Não inventa igualdade/arredondamento ou validação de prazo para a opção única. Valores desconhecidos,divergência da soma,total alterado e ausência de estimativa positiva invalidam a projecção dependente,sem bloquear o cálculo fiscal. Trocar hipótese conserva edições;resumo com Alterar. Rascunho privado,recuperação,histórico e relatório do mesmo dossier reutilizados. As estimativas não alimentam os comprovativos pagos nem alteram deduções anuais.
|
||||
|
||||
Cinco verificações novas de cálculo/interface e35afectadas aprovadas;percurso sintéticoAPI/recuperação/relatório em quatro cenários(distribuído,concentrado,divergente,total alterado),com saldo anual138MZN conservado. Provas `outputs/irpc-pec-plan-20261111/*-verification.json`. Sem migração,base,papel ou permissão nova. Tipos/lint/compilação aprovados;uma prova nativa/38asserções com rollback. Pacote Git e reversão conferidos no fecho. Isto não é conferência visual.
|
||||
|
||||
**Estado:** QA,ainda sem instalação;homologação permanece `bcbbe51c` e270instituições publicadas. Restam instalação/visual e recortes fiscais de base29(5),rectificação29(3),PPC excedente,períodos/regimes e outras regras de simulação;prazo legal da hipótese única não é certificado. L1–L7/restanteL8/L9–L16 e demais módulos permanecem no saldo,sem transformar o portal em sistema de execução fiscal. Próximo trabalho:primeiro residual de simulação executável com fontes aceites.
|
||||
|
||||
## 11/11/2026 — total e calendário PEC instalados embcbbe51c
|
||||
|
||||
Instalação expressamente autorizada,executada pelo processo Git existente e conferida às10:28:18Africa/Johannesburg(08:28:18UTC): `bcbbe51c2fc279d5b2d85412f7bcb6ca2423a9b6`. Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PEC**,com total ordinário2026 baseado no volume documental2025,limites antes dos PPC pagos anteriores,mesesJunho/Agosto/Outubro e dispensa no ano de início efectivo. Guardar/recuperar pelo rascunho privado e apresentar no relatório do mesmo dossier. Não cria pagamento,dedução anual ou reembolso.
|
||||
|
||||
@@ -342,6 +342,7 @@ export function SimulationIrpcAnnual({ draft, onChange }: Props) {
|
||||
</fieldset>
|
||||
<fieldset className="space-y-2 rounded border p-3">
|
||||
<legend>PPC efectivamente pagos</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})}/>
|
||||
{group('ppc', 'PPC efectuados')}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {SimulationIrpcPecPlan} from './simulation-irpc-pec-plan';
|
||||
import {
|
||||
newPecObligation,
|
||||
calculatePecObligation,
|
||||
@@ -17,7 +18,7 @@ export function SimulationIrpcPecObligation({
|
||||
if (!p)
|
||||
return (
|
||||
<section className="rounded border p-3">
|
||||
<h5>Obrigação PEC</h5>
|
||||
<h5>Estimativa PEC</h5>
|
||||
<p>
|
||||
Preparar o total e os meses previstos conserva os
|
||||
comprovativos pagos em separado.
|
||||
@@ -28,7 +29,7 @@ export function SimulationIrpcPecObligation({
|
||||
onChange(newPecObligation(book.subjectNuit, book.year))
|
||||
}
|
||||
>
|
||||
Preparar obrigação PEC
|
||||
Simular PEC
|
||||
</button>
|
||||
</section>
|
||||
);
|
||||
@@ -41,7 +42,7 @@ export function SimulationIrpcPecObligation({
|
||||
patch = (changes: Partial<PecObligation>) =>
|
||||
onChange({ ...p, ...changes });
|
||||
const field = (
|
||||
key: Exclude<keyof PecObligation, 'version' | 'scope'>,
|
||||
key: Exclude<keyof PecObligation, 'version' | 'scope' | 'plan'>,
|
||||
label: string,
|
||||
) => (
|
||||
<label className="block">
|
||||
@@ -55,10 +56,10 @@ export function SimulationIrpcPecObligation({
|
||||
);
|
||||
return (
|
||||
<section
|
||||
aria-label="Preparação da obrigação PEC"
|
||||
aria-label="Preparação da estimativa PEC"
|
||||
className="rounded border p-3"
|
||||
>
|
||||
<h5>Obrigação PEC — preparação</h5>
|
||||
<h5>Estimativa PEC — preparação</h5>
|
||||
<label className="block">
|
||||
Enquadramento PEC
|
||||
<select
|
||||
@@ -82,8 +83,8 @@ export function SimulationIrpcPecObligation({
|
||||
</label>
|
||||
{p.scope === 'ordinary_resident' && (
|
||||
<>
|
||||
{field('subjectNuit', 'NUIT — obrigação PEC')}
|
||||
{field('year', 'Exercício — obrigação PEC')}
|
||||
{field('subjectNuit', 'NUIT — estimativa PEC')}
|
||||
{field('year', 'Exercício — estimativa PEC')}
|
||||
{field(
|
||||
'activityStartYear',
|
||||
'Ano de início efectivo da actividade — PEC',
|
||||
@@ -122,7 +123,7 @@ export function SimulationIrpcPecObligation({
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
Obrigação total:{' '}
|
||||
Total estimado:{' '}
|
||||
{fractionPresentation(m.total!).decimal} MZN.
|
||||
</p>
|
||||
<ul>
|
||||
@@ -130,13 +131,10 @@ export function SimulationIrpcPecObligation({
|
||||
<li key={month}>{month}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p>
|
||||
Repartição por prestação e opção de pagamento único por
|
||||
preparar; não se presume dia limite nem arredondamento
|
||||
do PPC para o PEC.
|
||||
</p>
|
||||
|
||||
</>
|
||||
)}
|
||||
<SimulationIrpcPecPlan plan={p.plan} total={m.total} notApplicable={m.notApplicable} year={book.year} onChange={plan=>patch({plan})}/>
|
||||
<p>
|
||||
Guardar o rascunho conserva esta preparação. Calcular o fecho
|
||||
anual inclui-a no relatório. Não regista pagamentos,cria
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
newPecPlan,
|
||||
calculatePecPlan,
|
||||
pecPlanSource,
|
||||
type PecPlan,
|
||||
} from '@/lib/irpc-pec-plan';
|
||||
import {
|
||||
fractionPresentation,
|
||||
type FiscalFraction,
|
||||
} from '@/lib/fiscal-rational';
|
||||
export function SimulationIrpcPecPlan({
|
||||
plan,
|
||||
total,
|
||||
notApplicable,
|
||||
year,
|
||||
onChange,
|
||||
}: {
|
||||
plan?: PecPlan;
|
||||
total: FiscalFraction | null;
|
||||
notApplicable: boolean;
|
||||
year: string;
|
||||
onChange: (p: PecPlan) => void;
|
||||
}) {
|
||||
if (!plan)
|
||||
return (
|
||||
<div className="rounded border p-3">
|
||||
<p>
|
||||
Prepare os montantes por prestação ou a opção de pagamento
|
||||
único,sem registar pagamento.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={
|
||||
!total || notApplicable || BigInt(total.numerator) <= 0n
|
||||
}
|
||||
onClick={() => onChange(newPecPlan())}
|
||||
>
|
||||
Simular tesouraria PEC
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
const m = calculatePecPlan(plan, total, notApplicable, year),
|
||||
patch = (p: Partial<PecPlan>) => onChange({ ...plan, ...p });
|
||||
const field = (
|
||||
key: 'reference' | 'june' | 'august' | 'october',
|
||||
label: string,
|
||||
) => (
|
||||
<label className="block">
|
||||
{label}
|
||||
<input
|
||||
aria-label={label}
|
||||
value={plan[key]}
|
||||
onChange={(e) => patch({ [key]: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
return (
|
||||
<section
|
||||
aria-label="Projecção de tesouraria PEC"
|
||||
className="rounded border p-3"
|
||||
>
|
||||
<h6>Projecção PEC</h6>
|
||||
{m.pending.length ? (
|
||||
<p role="status">{m.pending.join(' ')}</p>
|
||||
) : (
|
||||
<p>
|
||||
Soma reconciliada:{' '}
|
||||
{m.rows
|
||||
.map(
|
||||
(r) =>
|
||||
r.month +
|
||||
': ' +
|
||||
fractionPresentation(r.amount).decimal +
|
||||
' MZN',
|
||||
)
|
||||
.join('; ')}
|
||||
. Prazo legal não validado.
|
||||
</p>
|
||||
)}
|
||||
<details open={m.pending.length > 0}>
|
||||
<summary>Alterar projecção PEC</summary>
|
||||
<label className="block">
|
||||
Opção de preparação PEC
|
||||
<select
|
||||
aria-label="Opção de preparação PEC"
|
||||
value={plan.mode}
|
||||
onChange={(e) =>
|
||||
patch({ mode: e.target.value as PecPlan['mode'] })
|
||||
}
|
||||
>
|
||||
<option value="">Por escolher</option>
|
||||
<option value="three">
|
||||
Distribuir a estimativa por três meses
|
||||
</option>
|
||||
<option value="single">
|
||||
Concentrar a estimativa num mês
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
{field('reference', 'Fundamento do projecção PEC')}
|
||||
{plan.mode === 'three' && (
|
||||
<>
|
||||
{field('june', 'PEC Junho — MZN')}
|
||||
{field('august', 'PEC Agosto — MZN')}
|
||||
{field('october', 'PEC Outubro — MZN')}
|
||||
</>
|
||||
)}
|
||||
{plan.mode === 'single' && (
|
||||
<label className="block">
|
||||
Mês previsto do PEC único
|
||||
<input
|
||||
aria-label="Mês previsto do PEC único"
|
||||
type="month"
|
||||
value={plan.singleMonth}
|
||||
onChange={(e) =>
|
||||
patch({ singleMonth: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</details>
|
||||
<p>
|
||||
O mês escolhido é uma hipótese de tesouraria para avaliar a
|
||||
viabilidade. A reconciliação da soma não valida os prazos
|
||||
legais,emite guia ou regista pagamento. Alterar a estimativa
|
||||
exige reconferir o plano. Guarde pelo rascunho e recalcule o
|
||||
fecho para actualizar o relatório.
|
||||
</p>
|
||||
<details>
|
||||
<summary>Fonte do projecção PEC</summary>
|
||||
<p>{pecPlanSource}</p>
|
||||
</details>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export function SimulationIrpcPpcObligation({
|
||||
if (!p)
|
||||
return (
|
||||
<section className="rounded border p-3">
|
||||
<h5>Obrigação PPC</h5>
|
||||
<h5>Estimativa PPC</h5>
|
||||
<p>
|
||||
Preparar a obrigação e os meses previstos não regista
|
||||
pagamentos nem cria deduções na liquidação.
|
||||
@@ -28,7 +28,7 @@ export function SimulationIrpcPpcObligation({
|
||||
onChange(newPpcObligation(book.subjectNuit, book.year))
|
||||
}
|
||||
>
|
||||
Preparar obrigação PPC
|
||||
Simular PPC
|
||||
</button>
|
||||
</section>
|
||||
);
|
||||
@@ -62,10 +62,10 @@ export function SimulationIrpcPpcObligation({
|
||||
);
|
||||
return (
|
||||
<section
|
||||
aria-label="Preparação da obrigação PPC"
|
||||
aria-label="Preparação da estimativa PPC"
|
||||
className="rounded border p-3"
|
||||
>
|
||||
<h5>Obrigação PPC — exercício anterior</h5>
|
||||
<h5>Estimativa PPC — exercício anterior</h5>
|
||||
<p>
|
||||
Reutiliza o titular e exercício ao iniciar. Complete a
|
||||
liquidação anterior e as retenções documentadas; campos vazios
|
||||
@@ -94,8 +94,8 @@ export function SimulationIrpcPpcObligation({
|
||||
</label>
|
||||
{p.scope === 'ordinary_resident' && (
|
||||
<>
|
||||
{field('subjectNuit', 'NUIT — obrigação PPC')}
|
||||
{field('year', 'Exercício — obrigação PPC')}
|
||||
{field('subjectNuit', 'NUIT — estimativa PPC')}
|
||||
{field('year', 'Exercício — estimativa PPC')}
|
||||
{field('previousYear', 'Exercício anterior — PPC')}
|
||||
<label className="block">
|
||||
Exercício anterior civil completo
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {pecPlanVersion} from './irpc-pec-plan.ts';
|
||||
import {validatePecObligation,pecObligationReport,pecObligationVersion,type PecObligation} from './irpc-pec-obligation.ts';
|
||||
import { validatePpcObligation, ppcObligationReport, ppcObligationVersion, type PpcObligation } from './irpc-ppc-obligation.ts';
|
||||
import {
|
||||
@@ -573,6 +574,7 @@ function key(d: Record<string, unknown>, b: AnnualBook) {
|
||||
dependencies(d, !!b.investmentCredit),
|
||||
...(b.ppcObligation ? [ppcObligationVersion] : []),
|
||||
...(b.pecObligation ? [pecObligationVersion] : []),
|
||||
...(b.pecObligation?.plan ? [pecPlanVersion] : []),
|
||||
...(b.investmentCredit && b.creditCarry?.lots.length
|
||||
? ['CBF15-credit-carry-combination/1']
|
||||
: []),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {validatePecPlan,pecPlanReport,type PecPlan} from './irpc-pec-plan.ts';
|
||||
import {
|
||||
decimalFraction,
|
||||
fraction,
|
||||
@@ -10,6 +11,7 @@ export const pecObligationSource =
|
||||
'RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee.0,5% do volume de negócios anterior,limites30.000/100.000MZN,menos PPC efectivamente pagos no ano anterior.Junho/Agosto/Outubro;opção de pagamento único prevista,sem data ou repartição inventada. Ano de início excluído29(4);sem volume anterior exige último montante29(5). Recorte2026,residente geral comercial/industrial/agrícola,ano civil,MZN. Regimes/períodos especiais,rectificação29(3),base de anos anteriores29(5),guias/juros separados.';
|
||||
export type PecObligation = {
|
||||
version: 1;
|
||||
plan?: PecPlan;
|
||||
scope: '' | 'ordinary_resident' | 'other';
|
||||
subjectNuit: string;
|
||||
year: string;
|
||||
@@ -38,6 +40,7 @@ export const newPecObligation = (
|
||||
ppcReference: '',
|
||||
});
|
||||
export function validatePecObligation(p: PecObligation) {
|
||||
if(p?.plan!==undefined)validatePecPlan(p.plan);
|
||||
if (
|
||||
!p ||
|
||||
p.version !== 1 ||
|
||||
@@ -51,7 +54,7 @@ export function validatePecObligation(p: PecObligation) {
|
||||
!['', 'ordinary_resident', 'other'].includes(p.scope)
|
||||
)
|
||||
throw Error(
|
||||
'Preparação da obrigação PEC não recuperável;original preservado.',
|
||||
'Preparação da estimativa PEC não recuperável;original preservado.',
|
||||
);
|
||||
}
|
||||
export function calculatePecObligation(
|
||||
@@ -129,7 +132,7 @@ export function calculatePecObligation(
|
||||
const residual = subtractFractions(limited, ppc);
|
||||
if (BigInt(residual.numerator) < 0n)
|
||||
pending.push(
|
||||
'PEC:PPC anteriores excedem o montante limitado;caso separado para reconciliação,não gera obrigação negativa ou reembolso.',
|
||||
'PEC:PPC anteriores excedem o montante limitado;caso separado para reconciliação,não gera estimativa negativa ou reembolso.',
|
||||
);
|
||||
else total = residual;
|
||||
}
|
||||
@@ -160,9 +163,10 @@ export function pecObligationReport(
|
||||
show = (v: FiscalFraction | null) =>
|
||||
v ? fractionPresentation(v).decimal + ' MZN' : 'Indeterminado';
|
||||
return [
|
||||
...(p.plan ? pecPlanReport(p.plan,m.total,m.notApplicable,year) : []),
|
||||
[
|
||||
'IRPC — L8',
|
||||
'PEC — obrigação preparada',
|
||||
'PEC — estimativa fiscal',
|
||||
m.pending.length
|
||||
? m.pending.join(' ')
|
||||
: m.notApplicable
|
||||
@@ -171,11 +175,11 @@ export function pecObligationReport(
|
||||
show(m.percentage) +
|
||||
';limitado antes de PPC: ' +
|
||||
show(m.limited) +
|
||||
';obrigação total: ' +
|
||||
';total estimado: ' +
|
||||
show(m.total) +
|
||||
';meses: ' +
|
||||
m.months.join(', ') +
|
||||
'. Montantes por prestação e opção de pagamento único não determinados neste recorte. Preparação não é pagamento nem dedução anual.',
|
||||
(p.plan ? '. Plano explícito apresentado separadamente;prazo legal não validado.' : '. Montantes por prestação e opção de pagamento único não determinados neste recorte.') + ' Preparação não é pagamento nem dedução anual.',
|
||||
p.activityReference +
|
||||
'; ' +
|
||||
p.turnoverReference +
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
decimalFraction,
|
||||
addFractions,
|
||||
subtractFractions,
|
||||
fractionPresentation,
|
||||
type FiscalFraction,
|
||||
} from './fiscal-rational.ts';
|
||||
export const pecPlanVersion = 'IRPC-PEC-plan/1';
|
||||
export const pecPlanSource =
|
||||
'RIRPC29(1):mesesJunho/Agosto/Outubro e opção de pagamento único. Montantes repartidos e mês único são hipótese explícita do utilizador,sem regra de igualdade,arredondamento,guia ou prazo legal presumidos. Conferência de prazo da opção única permanece separada.';
|
||||
export type PecPlan = {
|
||||
version: 1;
|
||||
mode: '' | 'three' | 'single';
|
||||
reference: string;
|
||||
singleMonth: string;
|
||||
june: string;
|
||||
august: string;
|
||||
october: string;
|
||||
};
|
||||
export const newPecPlan = (): PecPlan => ({
|
||||
version: 1,
|
||||
mode: '',
|
||||
reference: '',
|
||||
singleMonth: '',
|
||||
june: '',
|
||||
august: '',
|
||||
october: '',
|
||||
});
|
||||
export function validatePecPlan(p: PecPlan) {
|
||||
if (
|
||||
!p ||
|
||||
p.version !== 1 ||
|
||||
Object.keys(newPecPlan())
|
||||
.filter((k) => k !== 'version')
|
||||
.some(
|
||||
(k) =>
|
||||
typeof (p as unknown as Record<string, unknown>)[k] !==
|
||||
'string',
|
||||
) ||
|
||||
!['', 'three', 'single'].includes(p.mode)
|
||||
)
|
||||
throw Error('Projecção PEC não recuperável;original preservado.');
|
||||
}
|
||||
export function calculatePecPlan(
|
||||
p: PecPlan,
|
||||
total: FiscalFraction | null,
|
||||
notApplicable: boolean,
|
||||
year: string,
|
||||
) {
|
||||
validatePecPlan(p);
|
||||
const pending: string[] = [];
|
||||
let rows: Array<{ month: string; amount: FiscalFraction }> = [];
|
||||
if (!total)
|
||||
pending.push(
|
||||
'Projecção PEC:total estimado indeterminada;reconciliar a base antes de preparar os montantes.',
|
||||
);
|
||||
else if (notApplicable || BigInt(total.numerator) === 0n)
|
||||
pending.push(
|
||||
'Projecção PEC:sem estimativa positiva,não se distribuem saídas de caixa.',
|
||||
);
|
||||
if (year !== '2026')
|
||||
pending.push('Projecção PEC:exercício fora do recorte2026.');
|
||||
if (!p.reference.trim())
|
||||
pending.push(
|
||||
'Projecção PEC:identifique o fundamento da opção e dos montantes preparados.',
|
||||
);
|
||||
if (!['three', 'single'].includes(p.mode))
|
||||
pending.push(
|
||||
'Projecção PEC:escolha três prestações ou pagamento único.',
|
||||
);
|
||||
if (!pending.length && total) {
|
||||
if (p.mode === 'three') {
|
||||
const values = [p.june, p.august, p.october];
|
||||
if (!values.every((v) => /^\d{1,18}(\.\d{1,2})?$/.test(v)))
|
||||
pending.push(
|
||||
'Projecção PEC:preencha os três montantes em MZN,com até dois decimais;sem preenchimento automático.',
|
||||
);
|
||||
else {
|
||||
const amounts = values.map(decimalFraction);
|
||||
if (amounts.some((v) => BigInt(v.numerator) <= 0n))
|
||||
pending.push(
|
||||
'Projecção PEC:três prestações exigem três montantes positivos.',
|
||||
);
|
||||
else if (
|
||||
BigInt(
|
||||
subtractFractions(addFractions(amounts), total)
|
||||
.numerator,
|
||||
) !== 0n
|
||||
)
|
||||
pending.push(
|
||||
'Projecção PEC:a soma das três prestações difere do total preparado;sem arredondamento ou compensação silenciosos.',
|
||||
);
|
||||
else
|
||||
rows = amounts.map((amount, i) => ({
|
||||
month: ['2026-06', '2026-08', '2026-10'][i],
|
||||
amount,
|
||||
}));
|
||||
}
|
||||
} else if (!/^2026-(0[1-9]|1[0-2])$/.test(p.singleMonth))
|
||||
pending.push(
|
||||
'Projecção PEC:indique o mês previsto do pagamento único;isto não valida o prazo legal.',
|
||||
);
|
||||
else rows = [{ month: p.singleMonth, amount: total }];
|
||||
}
|
||||
return {
|
||||
version: pecPlanVersion,
|
||||
source: pecPlanSource,
|
||||
mode: p.mode,
|
||||
pending,
|
||||
rows,
|
||||
total,
|
||||
allocationReconciled: pending.length === 0,
|
||||
legalDeadlineValidated: false,
|
||||
paymentExecuted: false,
|
||||
annualDeductionCreated: false,
|
||||
};
|
||||
}
|
||||
export function pecPlanReport(
|
||||
p: PecPlan,
|
||||
total: FiscalFraction | null,
|
||||
notApplicable: boolean,
|
||||
year: string,
|
||||
): Array<[string, string, string, string]> {
|
||||
const m = calculatePecPlan(p, total, notApplicable, year);
|
||||
return [
|
||||
[
|
||||
'IRPC — L8',
|
||||
'PEC — projecção de tesouraria',
|
||||
m.pending.length
|
||||
? m.pending.join(' ')
|
||||
: (m.mode === 'single'
|
||||
? 'Saída de caixa concentrada (hipótese): '
|
||||
: 'Distribuição de caixa (hipótese): ') +
|
||||
m.rows
|
||||
.map(
|
||||
(r) =>
|
||||
r.month +
|
||||
': ' +
|
||||
fractionPresentation(r.amount).decimal +
|
||||
' MZN',
|
||||
)
|
||||
.join('; ') +
|
||||
'. Soma reconciliada;prazo legal não validado. Não é pagamento ou dedução.',
|
||||
p.reference + '; ' + m.source,
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -142,7 +142,7 @@ export function ppcObligationReport(
|
||||
return [
|
||||
[
|
||||
'IRPC — L8',
|
||||
'PPC — obrigação preparada',
|
||||
'PPC — estimativa fiscal',
|
||||
m.pending.length
|
||||
? m.pending.join(' ')
|
||||
: 'Base líquida anterior: ' +
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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 IrpcPecPlanDraftTest 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('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 (['three' => 138, 'single' => 138, 'mismatch' => 138, 'changed_total' => 138] as $name => $balance) {
|
||||
$snapshot = json_decode(file_get_contents(base_path('outputs/irpc-pec-plan-20261111/'.$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('SYNTHETIC cash-flow hypothesis for viability', $book['pecObligation']['plan']['reference']);
|
||||
$this->assertSame('SYNTHETIC effective activity'.($name === 'start' ? '2026' : '2020'), $book['pecObligation']['activityReference']);
|
||||
file_put_contents(base_path('outputs/irpc-pec-plan-20261111/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,9 @@ const output = ts.transpileModule(source, {
|
||||
}).outputText;
|
||||
compileFunction(output, ['require', 'module', 'exports'])(
|
||||
(name) =>
|
||||
name === '@/lib/irpc-annual-settlement'
|
||||
name === './simulation-irpc-pec-plan'
|
||||
? {SimulationIrpcPecPlan:()=>null}
|
||||
: name === '@/lib/irpc-annual-settlement'
|
||||
? annual
|
||||
: name === '@/lib/irpc-pec-obligation'
|
||||
? investment
|
||||
@@ -86,7 +88,7 @@ function harness(c) {
|
||||
test('actual PEC form reuses context, preserves hidden edits and explains first-year exception', () => {
|
||||
const c = investmentCase(),
|
||||
h = harness(c);
|
||||
h.click('Preparar obrigação PEC');
|
||||
h.click('Simular PEC');
|
||||
assert.equal(h.book().pecObligation.subjectNuit, c.b.subjectNuit);
|
||||
assert.equal(h.book().pecObligation.previousTurnover, '');
|
||||
h.change('Enquadramento PEC', 'ordinary_resident');
|
||||
|
||||
@@ -91,7 +91,7 @@ test('existing private book and annual history retain PEC preparation without ch
|
||||
before.arithmeticBalance,
|
||||
);
|
||||
assert.deepEqual(back.pecPayments, b.pecPayments);
|
||||
assert.match(annualHtml(d), /PEC — obrigação preparada/);
|
||||
assert.match(annualHtml(d), /PEC — estimativa fiscal/);
|
||||
assert.match(annualHtml(d), /2026-06/);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import { compileFunction } from 'node:vm';
|
||||
import { createRequire } from 'node:module';
|
||||
import React from 'react';
|
||||
import ts from 'typescript';
|
||||
import * as annual from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||
import * as investment from '../../resources/js/lib/irpc-pec-plan.ts';
|
||||
import * as rational from '../../resources/js/lib/fiscal-rational.ts';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
|
||||
const require = createRequire(import.meta.url),
|
||||
module = { exports: {} };
|
||||
const source = fs.readFileSync(
|
||||
new URL(
|
||||
'../../resources/js/components/simulation-irpc-pec-plan.tsx',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
const output = ts.transpileModule(source, {
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
jsx: ts.JsxEmit.ReactJSX,
|
||||
},
|
||||
}).outputText;
|
||||
compileFunction(output, ['require', 'module', 'exports'])(
|
||||
(name) =>
|
||||
name === './simulation-irpc-pec-plan'
|
||||
? { SimulationIrpcPecPlan: () => null }
|
||||
: name === '@/lib/irpc-annual-settlement'
|
||||
? annual
|
||||
: name === '@/lib/irpc-pec-plan'
|
||||
? investment
|
||||
: name === '@/lib/fiscal-rational'
|
||||
? rational
|
||||
: name === './simulation-irpc-special'
|
||||
? { QualificationFields: () => null }
|
||||
: name === './fiscal-proof-parcel'
|
||||
? { FiscalProofParcelFields: () => null }
|
||||
: require(name),
|
||||
module,
|
||||
module.exports,
|
||||
);
|
||||
const Component = module.exports.SimulationIrpcPecPlan;
|
||||
const text = (node) =>
|
||||
typeof node === 'string' || typeof node === 'number'
|
||||
? String(node)
|
||||
: React.isValidElement(node)
|
||||
? React.Children.toArray(node.props.children).map(text).join('')
|
||||
: '';
|
||||
const all = (node) =>
|
||||
React.isValidElement(node)
|
||||
? [node, ...React.Children.toArray(node.props.children).flatMap(all)]
|
||||
: [];
|
||||
test('actual plan form retains deliberate amounts across mode changes and explains legal deadline limitation', () => {
|
||||
let p;
|
||||
const total = { numerator: '40000', denominator: '1' };
|
||||
const render = () =>
|
||||
Component({
|
||||
plan: p,
|
||||
total,
|
||||
notApplicable: false,
|
||||
year: '2026',
|
||||
onChange: (next) => (p = next),
|
||||
});
|
||||
const click = (label) => {
|
||||
const n = all(render()).find(
|
||||
(x) => x.type === 'button' && text(x) === label,
|
||||
);
|
||||
assert.ok(n, label);
|
||||
n.props.onClick();
|
||||
};
|
||||
const change = (label, value) => {
|
||||
const n = all(render()).find((x) => x.props['aria-label'] === label);
|
||||
assert.ok(n, label);
|
||||
n.props.onChange({ target: { value } });
|
||||
};
|
||||
click('Simular tesouraria PEC');
|
||||
assert.equal(p.june, '');
|
||||
change('Opção de preparação PEC', 'three');
|
||||
change('Fundamento do projecção PEC', 'SYNTHETIC allocation');
|
||||
change('PEC Junho — MZN', '10000');
|
||||
change('PEC Agosto — MZN', '15000');
|
||||
change('PEC Outubro — MZN', '15000');
|
||||
assert.match(text(render()), /Soma reconciliada/);
|
||||
assert.match(text(render()), /Prazo legal não validado/);
|
||||
change('Opção de preparação PEC', 'single');
|
||||
change('Mês previsto do PEC único', '2026-06');
|
||||
assert.equal(p.june, '10000');
|
||||
assert.match(text(render()), /40000/);
|
||||
change('Opção de preparação PEC', 'three');
|
||||
assert.equal(p.singleMonth, '2026-06');
|
||||
assert.match(text(render()), /15000/);
|
||||
const html = renderToStaticMarkup(render());
|
||||
assert.match(html, /Alterar projecção PEC/);
|
||||
assert.match(html, /guia/);
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
newPecPlan,
|
||||
calculatePecPlan,
|
||||
validatePecPlan,
|
||||
} from '../../resources/js/lib/irpc-pec-plan.ts';
|
||||
import { calculatePecObligation } from '../../resources/js/lib/irpc-pec-obligation.ts';
|
||||
import { plan as obligation } from './helpers/irpc-pec-obligation-case.mjs';
|
||||
import { combinedCase } from './helpers/irpc-investment-combination-case.mjs';
|
||||
import {
|
||||
recordAnnual,
|
||||
annualCurrent,
|
||||
annualBook,
|
||||
saveAnnualBook,
|
||||
annualHtml,
|
||||
} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||
const f = (n) => ({ numerator: String(n), denominator: '1' }),
|
||||
three = () => ({
|
||||
...newPecPlan(),
|
||||
mode: 'three',
|
||||
reference: 'SYNTHETIC proposed allocation29(1)',
|
||||
june: '10000',
|
||||
august: '15000',
|
||||
october: '15000',
|
||||
}),
|
||||
calc = (p, total = f(40000), na = false) =>
|
||||
calculatePecPlan(p, total, na, '2026');
|
||||
|
||||
test('three explicit positive instalments reconcile exact total without inventing equal allocation', () => {
|
||||
const m = calc(three());
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.deepEqual(
|
||||
m.rows.map((r) => r.month),
|
||||
['2026-06', '2026-08', '2026-10'],
|
||||
);
|
||||
assert.deepEqual(
|
||||
m.rows.map((r) => r.amount),
|
||||
[f(10000), f(15000), f(15000)],
|
||||
);
|
||||
assert.equal(m.legalDeadlineValidated, false);
|
||||
assert.equal(m.paymentExecuted, false);
|
||||
assert.equal(m.annualDeductionCreated, false);
|
||||
});
|
||||
test('single option records chosen month and entire obligation but does not certify deadline', () => {
|
||||
const m = calc({ ...three(), mode: 'single', singleMonth: '2026-06' });
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.deepEqual(m.rows, [{ month: '2026-06', amount: f(40000) }]);
|
||||
assert.equal(m.legalDeadlineValidated, false);
|
||||
});
|
||||
test('unknown,total changed,zero,not applicable,invalid month and mismatched cents never release a reconciled schedule', () => {
|
||||
for (const p of [
|
||||
{ ...three(), june: '' },
|
||||
{ ...three(), june: '9999.99' },
|
||||
{ ...three(), june: '10000.01' },
|
||||
{ ...three(), june: '0' },
|
||||
{ ...three(), reference: '' },
|
||||
{ ...three(), mode: 'single', singleMonth: '2025-06' },
|
||||
]) {
|
||||
const m = calc(p);
|
||||
assert.ok(m.pending.length);
|
||||
assert.equal(m.allocationReconciled, false);
|
||||
assert.deepEqual(m.rows, []);
|
||||
}
|
||||
for (const t of [null, f(0), f(50000)])
|
||||
assert.ok(calc(three(), t).pending.length);
|
||||
assert.ok(calc(three(), f(40000), true).pending.length);
|
||||
assert.throws(() => validatePecPlan({ ...three(), june: null }));
|
||||
});
|
||||
test('schedule preserves explicit option in native book and invalidates annual snapshot on edits without new paid deductions', () => {
|
||||
const { d, b } = combinedCase();
|
||||
b.pecObligation = { ...obligation(), subjectNuit: b.subjectNuit };
|
||||
let current = recordAnnual(d, b, '2026-11-11T11:00:00Z');
|
||||
current.pecObligation.plan = three();
|
||||
assert.equal(annualCurrent(d, current), false);
|
||||
current = recordAnnual(d, current, '2026-11-11T11:01:00Z');
|
||||
d.modelo_a_snapshot = saveAnnualBook(d, current);
|
||||
const back = annualBook(d);
|
||||
assert.equal(annualCurrent(d, back), true);
|
||||
assert.deepEqual(back.result.model.arithmeticBalance, f(138));
|
||||
assert.deepEqual(back.pecPayments, b.pecPayments);
|
||||
assert.equal(back.history.length, 1);
|
||||
assert.match(annualHtml(d), /PEC — projecção de tesouraria/);
|
||||
back.pecObligation.previousTurnover = '20000000';
|
||||
assert.ok(
|
||||
calculatePecPlan(
|
||||
back.pecObligation.plan,
|
||||
calculatePecObligation(
|
||||
back.pecObligation,
|
||||
back.subjectNuit,
|
||||
back.year,
|
||||
back.assessment,
|
||||
).total,
|
||||
false,
|
||||
'2026',
|
||||
).pending.length,
|
||||
);
|
||||
assert.equal(annualCurrent(d, back), false);
|
||||
});
|
||||
@@ -84,7 +84,7 @@ function harness(c) {
|
||||
};
|
||||
}
|
||||
test('actual progressive PPC form reuses subject, retains deliberate edits and exposes calendar separately from paid proofs',()=>{
|
||||
const c=investmentCase(),h=harness(c);h.click('Preparar obrigação PPC');assert.equal(h.book().ppcObligation.subjectNuit,c.b.subjectNuit);assert.equal(h.book().ppcObligation.previousTax,'');
|
||||
const c=investmentCase(),h=harness(c);h.click('Simular PPC');assert.equal(h.book().ppcObligation.subjectNuit,c.b.subjectNuit);assert.equal(h.book().ppcObligation.previousTax,'');
|
||||
h.change('Enquadramento PPC','ordinary_resident');h.change('Exercício anterior — PPC','2025');h.change('Exercício anterior civil completo','yes');h.change('Imposto liquidado anterior21(1) — MZN','100001');h.change('Retenções na fonte anteriores — MZN','10000');h.change('Fonte da liquidação anterior — PPC','SYNTHETIC tax');h.change('Fonte das retenções anteriores — PPC','SYNTHETIC RF');
|
||||
assert.match(text(h.render()),/75000/);assert.match(text(h.render()),/2026-05/);assert.deepEqual(h.book().ppc,c.b.ppc);
|
||||
h.change('Enquadramento PPC','other');assert.equal(h.book().ppcObligation.previousTax,'100001');assert.match(text(h.render()),/Outros períodos/);h.change('Enquadramento PPC','ordinary_resident');assert.match(text(h.render()),/75000/);
|
||||
|
||||
@@ -82,6 +82,6 @@ test('preparation survives existing book, versions/history/report; obligation ne
|
||||
assert.deepEqual(recovered.ppc, b.ppc);
|
||||
assert.equal(annualCurrent(d, recovered), true);
|
||||
assert.equal(recovered.history.length, 1);
|
||||
assert.match(annualHtml(d), /PPC — obrigação preparada/);
|
||||
assert.match(annualHtml(d), /PPC — estimativa fiscal/);
|
||||
assert.match(annualHtml(d), /2026-05/);
|
||||
});
|
||||
|
||||
@@ -69,8 +69,8 @@ for (const [name, changes] of Object.entries(scenarios)) {
|
||||
selectedSections: new Set(['finance']),
|
||||
generatedAt: '2026-11-11T10:02:00Z',
|
||||
});
|
||||
assert.match(html, /PEC — obrigação preparada/);
|
||||
assert.match(JSON.stringify(report), /PEC — obrigação preparada/);
|
||||
assert.match(html, /PEC — estimativa fiscal/);
|
||||
assert.match(JSON.stringify(report), /PEC — estimativa fiscal/);
|
||||
assert.equal(m.paymentExecuted, false);
|
||||
assert.equal(m.annualDeductionCreated, false);
|
||||
const expected = {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
calculatePecPlan,
|
||||
newPecPlan,
|
||||
} from '../resources/js/lib/irpc-pec-plan.ts';
|
||||
import fs from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { combinedCase } from '../tests/frontend/helpers/irpc-investment-combination-case.mjs';
|
||||
import { plan } from '../tests/frontend/helpers/irpc-pec-obligation-case.mjs';
|
||||
import { calculatePecObligation } from '../resources/js/lib/irpc-pec-obligation.ts';
|
||||
import {
|
||||
recordAnnual,
|
||||
saveAnnualBook,
|
||||
annualBook,
|
||||
annualCurrent,
|
||||
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-pec-plan-20261111/', import.meta.url);
|
||||
fs.mkdirSync(out, { recursive: true });
|
||||
const allocation = {
|
||||
...newPecPlan(),
|
||||
mode: 'three',
|
||||
reference: 'SYNTHETIC cash-flow hypothesis for viability',
|
||||
june: '10000',
|
||||
august: '15000',
|
||||
october: '15000',
|
||||
};
|
||||
const scenarios = {
|
||||
three: { plan: allocation },
|
||||
single: { plan: { ...allocation, mode: 'single', singleMonth: '2026-06' } },
|
||||
mismatch: { plan: { ...allocation, october: '14000' } },
|
||||
changed_total: { previousTurnover: '20000000', plan: allocation },
|
||||
};
|
||||
for (const [name, changes] of Object.entries(scenarios)) {
|
||||
if (process.argv[2] === 'generate') {
|
||||
const { d, b } = combinedCase();
|
||||
const prior = recordAnnual(d, b, '2026-11-11T10:00:00Z');
|
||||
prior.pecObligation = {
|
||||
...plan(),
|
||||
subjectNuit: b.subjectNuit,
|
||||
year: b.year,
|
||||
...changes,
|
||||
};
|
||||
const next = recordAnnual(d, prior, '2026-11-11T10:01:00Z');
|
||||
d.modelo_a_snapshot = saveAnnualBook(d, next);
|
||||
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),
|
||||
m = calculatePecObligation(
|
||||
b.pecObligation,
|
||||
b.subjectNuit,
|
||||
b.year,
|
||||
b.assessment,
|
||||
);
|
||||
assert.equal(annualCurrent(d, b), true);
|
||||
assert.equal(b.history.length, 1);
|
||||
assert.deepEqual(b.result.model.arithmeticBalance, {
|
||||
numerator: '138',
|
||||
denominator: '1',
|
||||
});
|
||||
const html = annualHtml(d),
|
||||
report = createProjectDossierReport(d, {
|
||||
selectedSections: new Set(['finance']),
|
||||
generatedAt: '2026-11-11T10:02:00Z',
|
||||
});
|
||||
assert.match(html, /PEC — estimativa fiscal/);
|
||||
assert.match(JSON.stringify(report), /PEC — estimativa fiscal/);
|
||||
assert.equal(m.paymentExecuted, false);
|
||||
assert.equal(m.annualDeductionCreated, false);
|
||||
const allocationModel = calculatePecPlan(
|
||||
b.pecObligation.plan,
|
||||
m.total,
|
||||
m.notApplicable,
|
||||
b.year,
|
||||
);
|
||||
assert.equal(
|
||||
allocationModel.allocationReconciled,
|
||||
['three', 'single'].includes(name),
|
||||
);
|
||||
assert.equal(allocationModel.legalDeadlineValidated, false);
|
||||
assert.equal(allocationModel.paymentExecuted, false);
|
||||
assert.equal(allocationModel.annualDeductionCreated, false);
|
||||
assert.match(html, /PEC — projecção de tesouraria/);
|
||||
assert.match(JSON.stringify(report), /PEC — projecção de tesouraria/);
|
||||
fs.writeFileSync(new URL(name + '-report.html', out), html);
|
||||
fs.writeFileSync(
|
||||
new URL(name + '-verification.json', out),
|
||||
JSON.stringify(
|
||||
{
|
||||
synthetic: true,
|
||||
apiRecovered: true,
|
||||
current: true,
|
||||
historyPreserved: true,
|
||||
annualBalanceUnchanged: true,
|
||||
report: true,
|
||||
visualReview: false,
|
||||
model: m,
|
||||
allocationModel,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
process.argv[2] === 'generate'
|
||||
? 'Four synthetic PEC cash-flow scenarios prepared.'
|
||||
: 'Four native recovered PEC cash-flow scenarios recalculated and reported without creating deductions.',
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
if (($argv[1] ?? '') !== 'test' || basename($root) !== 'portal-investidor') {
|
||||
throw new RuntimeException('Explicit local test operation required.');
|
||||
}
|
||||
foreach (['APP_ENV' => 'testing', 'APP_KEY' => 'base64:'.base64_encode(str_repeat('Q', 32)),
|
||||
'APP_CONFIG_CACHE' => $root.'/outputs/irpc-pec-plan-20261111/no-config.php',
|
||||
'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '55441',
|
||||
'DB_DATABASE' => 'proactive_investment_20261027_test', 'DB_USERNAME' => 'investmoz_test', '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=proactive_investment_20261027_test', 'investmoz_test', '');
|
||||
$identity = $pdo->query('select current_database() as name,inet_server_port() as port')->fetch(PDO::FETCH_ASSOC);
|
||||
if ($identity['name'] !== 'proactive_investment_20261027_test' || (int) $identity['port'] !== 55441) {
|
||||
throw new RuntimeException('Isolated QA identity required.');
|
||||
}
|
||||
$argv = ['phpunit', '-c', 'phpunit.investmoz.xml', '--do-not-cache-result', 'tests/Feature/IrpcPecPlanDraftTest.php'];
|
||||
$argc = count($argv);
|
||||
$_SERVER['argv'] = $argv;
|
||||
$_SERVER['argc'] = $argc;
|
||||
require $root.'/vendor/autoload.php';
|
||||
require $root.'/vendor/phpunit/phpunit/phpunit';
|
||||
@@ -61,8 +61,8 @@ for (const [name, changes] of Object.entries(scenarios)) {
|
||||
selectedSections: new Set(['finance']),
|
||||
generatedAt: '2026-11-11T10:02:00Z',
|
||||
});
|
||||
assert.match(html, /PPC — obrigação preparada/);
|
||||
assert.match(JSON.stringify(report), /PPC — obrigação preparada/);
|
||||
assert.match(html, /PPC — estimativa fiscal/);
|
||||
assert.match(JSON.stringify(report), /PPC — estimativa fiscal/);
|
||||
assert.equal(m.paymentExecuted, false);
|
||||
assert.equal(m.annualDeductionCreated, false);
|
||||
if (name === 'ordinary') assert.equal(m.total.numerator, '72000');
|
||||
|
||||
Reference in New Issue
Block a user