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
|
# Instruções permanentes do projeto InvesteMoz
|
||||||
|
|
||||||
## Encerramento e reabertura do dossier — decisão do proprietário, 03/11/2026
|
## 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
|
## 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.
|
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
|
## 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.
|
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
|
## 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.
|
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>
|
||||||
<fieldset className="space-y-2 rounded border p-3">
|
<fieldset className="space-y-2 rounded border p-3">
|
||||||
<legend>PPC efectivamente pagos</legend>
|
<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})}/>
|
<SimulationIrpcPecObligation book={b} onChange={pecObligation=>patch({pecObligation})}/>
|
||||||
<SimulationIrpcPpcObligation book={b} onChange={ppcObligation=>patch({ppcObligation})}/>
|
<SimulationIrpcPpcObligation book={b} onChange={ppcObligation=>patch({ppcObligation})}/>
|
||||||
{group('ppc', 'PPC efectuados')}
|
{group('ppc', 'PPC efectuados')}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {SimulationIrpcPecPlan} from './simulation-irpc-pec-plan';
|
||||||
import {
|
import {
|
||||||
newPecObligation,
|
newPecObligation,
|
||||||
calculatePecObligation,
|
calculatePecObligation,
|
||||||
@@ -17,7 +18,7 @@ export function SimulationIrpcPecObligation({
|
|||||||
if (!p)
|
if (!p)
|
||||||
return (
|
return (
|
||||||
<section className="rounded border p-3">
|
<section className="rounded border p-3">
|
||||||
<h5>Obrigação PEC</h5>
|
<h5>Estimativa PEC</h5>
|
||||||
<p>
|
<p>
|
||||||
Preparar o total e os meses previstos conserva os
|
Preparar o total e os meses previstos conserva os
|
||||||
comprovativos pagos em separado.
|
comprovativos pagos em separado.
|
||||||
@@ -28,7 +29,7 @@ export function SimulationIrpcPecObligation({
|
|||||||
onChange(newPecObligation(book.subjectNuit, book.year))
|
onChange(newPecObligation(book.subjectNuit, book.year))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Preparar obrigação PEC
|
Simular PEC
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@@ -41,7 +42,7 @@ export function SimulationIrpcPecObligation({
|
|||||||
patch = (changes: Partial<PecObligation>) =>
|
patch = (changes: Partial<PecObligation>) =>
|
||||||
onChange({ ...p, ...changes });
|
onChange({ ...p, ...changes });
|
||||||
const field = (
|
const field = (
|
||||||
key: Exclude<keyof PecObligation, 'version' | 'scope'>,
|
key: Exclude<keyof PecObligation, 'version' | 'scope' | 'plan'>,
|
||||||
label: string,
|
label: string,
|
||||||
) => (
|
) => (
|
||||||
<label className="block">
|
<label className="block">
|
||||||
@@ -55,10 +56,10 @@ export function SimulationIrpcPecObligation({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
aria-label="Preparação da obrigação PEC"
|
aria-label="Preparação da estimativa PEC"
|
||||||
className="rounded border p-3"
|
className="rounded border p-3"
|
||||||
>
|
>
|
||||||
<h5>Obrigação PEC — preparação</h5>
|
<h5>Estimativa PEC — preparação</h5>
|
||||||
<label className="block">
|
<label className="block">
|
||||||
Enquadramento PEC
|
Enquadramento PEC
|
||||||
<select
|
<select
|
||||||
@@ -82,8 +83,8 @@ export function SimulationIrpcPecObligation({
|
|||||||
</label>
|
</label>
|
||||||
{p.scope === 'ordinary_resident' && (
|
{p.scope === 'ordinary_resident' && (
|
||||||
<>
|
<>
|
||||||
{field('subjectNuit', 'NUIT — obrigação PEC')}
|
{field('subjectNuit', 'NUIT — estimativa PEC')}
|
||||||
{field('year', 'Exercício — obrigação PEC')}
|
{field('year', 'Exercício — estimativa PEC')}
|
||||||
{field(
|
{field(
|
||||||
'activityStartYear',
|
'activityStartYear',
|
||||||
'Ano de início efectivo da actividade — PEC',
|
'Ano de início efectivo da actividade — PEC',
|
||||||
@@ -122,7 +123,7 @@ export function SimulationIrpcPecObligation({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
Obrigação total:{' '}
|
Total estimado:{' '}
|
||||||
{fractionPresentation(m.total!).decimal} MZN.
|
{fractionPresentation(m.total!).decimal} MZN.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -130,13 +131,10 @@ export function SimulationIrpcPecObligation({
|
|||||||
<li key={month}>{month}</li>
|
<li key={month}>{month}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</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>
|
<p>
|
||||||
Guardar o rascunho conserva esta preparação. Calcular o fecho
|
Guardar o rascunho conserva esta preparação. Calcular o fecho
|
||||||
anual inclui-a no relatório. Não regista pagamentos,cria
|
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)
|
if (!p)
|
||||||
return (
|
return (
|
||||||
<section className="rounded border p-3">
|
<section className="rounded border p-3">
|
||||||
<h5>Obrigação PPC</h5>
|
<h5>Estimativa PPC</h5>
|
||||||
<p>
|
<p>
|
||||||
Preparar a obrigação e os meses previstos não regista
|
Preparar a obrigação e os meses previstos não regista
|
||||||
pagamentos nem cria deduções na liquidação.
|
pagamentos nem cria deduções na liquidação.
|
||||||
@@ -28,7 +28,7 @@ export function SimulationIrpcPpcObligation({
|
|||||||
onChange(newPpcObligation(book.subjectNuit, book.year))
|
onChange(newPpcObligation(book.subjectNuit, book.year))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Preparar obrigação PPC
|
Simular PPC
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@@ -62,10 +62,10 @@ export function SimulationIrpcPpcObligation({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
aria-label="Preparação da obrigação PPC"
|
aria-label="Preparação da estimativa PPC"
|
||||||
className="rounded border p-3"
|
className="rounded border p-3"
|
||||||
>
|
>
|
||||||
<h5>Obrigação PPC — exercício anterior</h5>
|
<h5>Estimativa PPC — exercício anterior</h5>
|
||||||
<p>
|
<p>
|
||||||
Reutiliza o titular e exercício ao iniciar. Complete a
|
Reutiliza o titular e exercício ao iniciar. Complete a
|
||||||
liquidação anterior e as retenções documentadas; campos vazios
|
liquidação anterior e as retenções documentadas; campos vazios
|
||||||
@@ -94,8 +94,8 @@ export function SimulationIrpcPpcObligation({
|
|||||||
</label>
|
</label>
|
||||||
{p.scope === 'ordinary_resident' && (
|
{p.scope === 'ordinary_resident' && (
|
||||||
<>
|
<>
|
||||||
{field('subjectNuit', 'NUIT — obrigação PPC')}
|
{field('subjectNuit', 'NUIT — estimativa PPC')}
|
||||||
{field('year', 'Exercício — obrigação PPC')}
|
{field('year', 'Exercício — estimativa PPC')}
|
||||||
{field('previousYear', 'Exercício anterior — PPC')}
|
{field('previousYear', 'Exercício anterior — PPC')}
|
||||||
<label className="block">
|
<label className="block">
|
||||||
Exercício anterior civil completo
|
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 {validatePecObligation,pecObligationReport,pecObligationVersion,type PecObligation} from './irpc-pec-obligation.ts';
|
||||||
import { validatePpcObligation, ppcObligationReport, ppcObligationVersion, type PpcObligation } from './irpc-ppc-obligation.ts';
|
import { validatePpcObligation, ppcObligationReport, ppcObligationVersion, type PpcObligation } from './irpc-ppc-obligation.ts';
|
||||||
import {
|
import {
|
||||||
@@ -573,6 +574,7 @@ function key(d: Record<string, unknown>, b: AnnualBook) {
|
|||||||
dependencies(d, !!b.investmentCredit),
|
dependencies(d, !!b.investmentCredit),
|
||||||
...(b.ppcObligation ? [ppcObligationVersion] : []),
|
...(b.ppcObligation ? [ppcObligationVersion] : []),
|
||||||
...(b.pecObligation ? [pecObligationVersion] : []),
|
...(b.pecObligation ? [pecObligationVersion] : []),
|
||||||
|
...(b.pecObligation?.plan ? [pecPlanVersion] : []),
|
||||||
...(b.investmentCredit && b.creditCarry?.lots.length
|
...(b.investmentCredit && b.creditCarry?.lots.length
|
||||||
? ['CBF15-credit-carry-combination/1']
|
? ['CBF15-credit-carry-combination/1']
|
||||||
: []),
|
: []),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {validatePecPlan,pecPlanReport,type PecPlan} from './irpc-pec-plan.ts';
|
||||||
import {
|
import {
|
||||||
decimalFraction,
|
decimalFraction,
|
||||||
fraction,
|
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.';
|
'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 = {
|
export type PecObligation = {
|
||||||
version: 1;
|
version: 1;
|
||||||
|
plan?: PecPlan;
|
||||||
scope: '' | 'ordinary_resident' | 'other';
|
scope: '' | 'ordinary_resident' | 'other';
|
||||||
subjectNuit: string;
|
subjectNuit: string;
|
||||||
year: string;
|
year: string;
|
||||||
@@ -38,6 +40,7 @@ export const newPecObligation = (
|
|||||||
ppcReference: '',
|
ppcReference: '',
|
||||||
});
|
});
|
||||||
export function validatePecObligation(p: PecObligation) {
|
export function validatePecObligation(p: PecObligation) {
|
||||||
|
if(p?.plan!==undefined)validatePecPlan(p.plan);
|
||||||
if (
|
if (
|
||||||
!p ||
|
!p ||
|
||||||
p.version !== 1 ||
|
p.version !== 1 ||
|
||||||
@@ -51,7 +54,7 @@ export function validatePecObligation(p: PecObligation) {
|
|||||||
!['', 'ordinary_resident', 'other'].includes(p.scope)
|
!['', 'ordinary_resident', 'other'].includes(p.scope)
|
||||||
)
|
)
|
||||||
throw Error(
|
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(
|
export function calculatePecObligation(
|
||||||
@@ -129,7 +132,7 @@ export function calculatePecObligation(
|
|||||||
const residual = subtractFractions(limited, ppc);
|
const residual = subtractFractions(limited, ppc);
|
||||||
if (BigInt(residual.numerator) < 0n)
|
if (BigInt(residual.numerator) < 0n)
|
||||||
pending.push(
|
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;
|
else total = residual;
|
||||||
}
|
}
|
||||||
@@ -160,9 +163,10 @@ export function pecObligationReport(
|
|||||||
show = (v: FiscalFraction | null) =>
|
show = (v: FiscalFraction | null) =>
|
||||||
v ? fractionPresentation(v).decimal + ' MZN' : 'Indeterminado';
|
v ? fractionPresentation(v).decimal + ' MZN' : 'Indeterminado';
|
||||||
return [
|
return [
|
||||||
|
...(p.plan ? pecPlanReport(p.plan,m.total,m.notApplicable,year) : []),
|
||||||
[
|
[
|
||||||
'IRPC — L8',
|
'IRPC — L8',
|
||||||
'PEC — obrigação preparada',
|
'PEC — estimativa fiscal',
|
||||||
m.pending.length
|
m.pending.length
|
||||||
? m.pending.join(' ')
|
? m.pending.join(' ')
|
||||||
: m.notApplicable
|
: m.notApplicable
|
||||||
@@ -171,11 +175,11 @@ export function pecObligationReport(
|
|||||||
show(m.percentage) +
|
show(m.percentage) +
|
||||||
';limitado antes de PPC: ' +
|
';limitado antes de PPC: ' +
|
||||||
show(m.limited) +
|
show(m.limited) +
|
||||||
';obrigação total: ' +
|
';total estimado: ' +
|
||||||
show(m.total) +
|
show(m.total) +
|
||||||
';meses: ' +
|
';meses: ' +
|
||||||
m.months.join(', ') +
|
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.activityReference +
|
||||||
'; ' +
|
'; ' +
|
||||||
p.turnoverReference +
|
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 [
|
return [
|
||||||
[
|
[
|
||||||
'IRPC — L8',
|
'IRPC — L8',
|
||||||
'PPC — obrigação preparada',
|
'PPC — estimativa fiscal',
|
||||||
m.pending.length
|
m.pending.length
|
||||||
? m.pending.join(' ')
|
? m.pending.join(' ')
|
||||||
: 'Base líquida anterior: ' +
|
: '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;
|
}).outputText;
|
||||||
compileFunction(output, ['require', 'module', 'exports'])(
|
compileFunction(output, ['require', 'module', 'exports'])(
|
||||||
(name) =>
|
(name) =>
|
||||||
name === '@/lib/irpc-annual-settlement'
|
name === './simulation-irpc-pec-plan'
|
||||||
|
? {SimulationIrpcPecPlan:()=>null}
|
||||||
|
: name === '@/lib/irpc-annual-settlement'
|
||||||
? annual
|
? annual
|
||||||
: name === '@/lib/irpc-pec-obligation'
|
: name === '@/lib/irpc-pec-obligation'
|
||||||
? investment
|
? investment
|
||||||
@@ -86,7 +88,7 @@ function harness(c) {
|
|||||||
test('actual PEC form reuses context, preserves hidden edits and explains first-year exception', () => {
|
test('actual PEC form reuses context, preserves hidden edits and explains first-year exception', () => {
|
||||||
const c = investmentCase(),
|
const c = investmentCase(),
|
||||||
h = harness(c);
|
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.subjectNuit, c.b.subjectNuit);
|
||||||
assert.equal(h.book().pecObligation.previousTurnover, '');
|
assert.equal(h.book().pecObligation.previousTurnover, '');
|
||||||
h.change('Enquadramento PEC', 'ordinary_resident');
|
h.change('Enquadramento PEC', 'ordinary_resident');
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ test('existing private book and annual history retain PEC preparation without ch
|
|||||||
before.arithmeticBalance,
|
before.arithmeticBalance,
|
||||||
);
|
);
|
||||||
assert.deepEqual(back.pecPayments, b.pecPayments);
|
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/);
|
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',()=>{
|
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');
|
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);
|
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/);
|
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.deepEqual(recovered.ppc, b.ppc);
|
||||||
assert.equal(annualCurrent(d, recovered), true);
|
assert.equal(annualCurrent(d, recovered), true);
|
||||||
assert.equal(recovered.history.length, 1);
|
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/);
|
assert.match(annualHtml(d), /2026-05/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ for (const [name, changes] of Object.entries(scenarios)) {
|
|||||||
selectedSections: new Set(['finance']),
|
selectedSections: new Set(['finance']),
|
||||||
generatedAt: '2026-11-11T10:02:00Z',
|
generatedAt: '2026-11-11T10:02:00Z',
|
||||||
});
|
});
|
||||||
assert.match(html, /PEC — obrigação preparada/);
|
assert.match(html, /PEC — estimativa fiscal/);
|
||||||
assert.match(JSON.stringify(report), /PEC — obrigação preparada/);
|
assert.match(JSON.stringify(report), /PEC — estimativa fiscal/);
|
||||||
assert.equal(m.paymentExecuted, false);
|
assert.equal(m.paymentExecuted, false);
|
||||||
assert.equal(m.annualDeductionCreated, false);
|
assert.equal(m.annualDeductionCreated, false);
|
||||||
const expected = {
|
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']),
|
selectedSections: new Set(['finance']),
|
||||||
generatedAt: '2026-11-11T10:02:00Z',
|
generatedAt: '2026-11-11T10:02:00Z',
|
||||||
});
|
});
|
||||||
assert.match(html, /PPC — obrigação preparada/);
|
assert.match(html, /PPC — estimativa fiscal/);
|
||||||
assert.match(JSON.stringify(report), /PPC — obrigação preparada/);
|
assert.match(JSON.stringify(report), /PPC — estimativa fiscal/);
|
||||||
assert.equal(m.paymentExecuted, false);
|
assert.equal(m.paymentExecuted, false);
|
||||||
assert.equal(m.annualDeductionCreated, false);
|
assert.equal(m.annualDeductionCreated, false);
|
||||||
if (name === 'ordinary') assert.equal(m.total.numerator, '72000');
|
if (name === 'ordinary') assert.equal(m.total.numerator, '72000');
|
||||||
|
|||||||
Reference in New Issue
Block a user