Prepare PEC obligation total and calendar with private recovery and reports
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -1,3 +1,13 @@
|
|||||||
|
## 11/11/2026 — total e calendário PEC ordinário em QA
|
||||||
|
|
||||||
|
**Novo recorte L8:** preparação do total PEC2026 pelo volume de negócios documental2025:0,5%,limites30.000/100.000MZN aplicados antes de deduzir PPC efectivamente pagos no ano anterior. MesesJunho/Agosto/Outubro,sem inventar dia limite ou importar o arredondamento PPC. Dispensa do ano de início efectivo da actividade29(4) fundamentada separadamente. Fonte:RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo.
|
||||||
|
|
||||||
|
Formulário progressivo reutiliza titular/exercício e conserva valores ao ocultar campos sem aplicação; rascunho privado e relatório do mesmo dossier reutilizados. Total preparado não é PEC pago,nem cria dedução anual,reembolso,guia ou consumo de saldo oficial. Ausência de informação não equivale a zero. Sem volume no ano anterior exige base29(5);PPC superiores ao montante limitado ficam por reconciliar,sem obrigação negativa. Esses casos não impedem o cálculo anual independente.
|
||||||
|
|
||||||
|
**Percurso sintético:** cálculo→API privadaPOST/PUT→GET→recálculo→HTML/relatório:40.000MZN ordinário,20.000mínimo antes de PPC,90.000máximo antes de PPC,dispensa inicial0,volumezero e desconhecido indeterminados. Histórico e saldo anual138MZN preservados. QA55441 existente,uma prova nativa/56asserções com rollback,sem nova base/tabela/porta/papel/migração. Seis verificações novas de cálculo/interface e29afectadas aprovadas; tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-obligation-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho.
|
||||||
|
|
||||||
|
**Saldo:** implementado em QA,ainda não instalado. Homologação mantém `fbff2660` e270instituições publicadas. Restam entrega Git e visual;montantes por prestação/opção de pagamento único,última base29(5),rectificação29(3),PPC acima do limite,períodos/regimes especiais,guias/juros permanecem abertos. L8 não é integralmente concluído;resíduos L1–L7/restanteL8/L9–L16 e restantes módulos conservados. Próximo trabalho:primeiro desses casos executável com as fontes aceites,sem reabrir os recortes entregues.
|
||||||
|
|
||||||
## 11/11/2026 — obrigação PPC instalada emfbff2660
|
## 11/11/2026 — obrigação PPC instalada emfbff2660
|
||||||
|
|
||||||
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
|
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
## 11/11/2026 — total e calendário PEC ordinário em QA
|
||||||
|
|
||||||
|
**Novo recorte L8:** preparação do total PEC2026 pelo volume de negócios documental2025:0,5%,limites30.000/100.000MZN aplicados antes de deduzir PPC efectivamente pagos no ano anterior. MesesJunho/Agosto/Outubro,sem inventar dia limite ou importar o arredondamento PPC. Dispensa do ano de início efectivo da actividade29(4) fundamentada separadamente. Fonte:RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo.
|
||||||
|
|
||||||
|
Formulário progressivo reutiliza titular/exercício e conserva valores ao ocultar campos sem aplicação; rascunho privado e relatório do mesmo dossier reutilizados. Total preparado não é PEC pago,nem cria dedução anual,reembolso,guia ou consumo de saldo oficial. Ausência de informação não equivale a zero. Sem volume no ano anterior exige base29(5);PPC superiores ao montante limitado ficam por reconciliar,sem obrigação negativa. Esses casos não impedem o cálculo anual independente.
|
||||||
|
|
||||||
|
**Percurso sintético:** cálculo→API privadaPOST/PUT→GET→recálculo→HTML/relatório:40.000MZN ordinário,20.000mínimo antes de PPC,90.000máximo antes de PPC,dispensa inicial0,volumezero e desconhecido indeterminados. Histórico e saldo anual138MZN preservados. QA55441 existente,uma prova nativa/56asserções com rollback,sem nova base/tabela/porta/papel/migração. Seis verificações novas de cálculo/interface e29afectadas aprovadas; tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-obligation-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho.
|
||||||
|
|
||||||
|
**Saldo:** implementado em QA,ainda não instalado. Homologação mantém `fbff2660` e270instituições publicadas. Restam entrega Git e visual;montantes por prestação/opção de pagamento único,última base29(5),rectificação29(3),PPC acima do limite,períodos/regimes especiais,guias/juros permanecem abertos. L8 não é integralmente concluído;resíduos L1–L7/restanteL8/L9–L16 e restantes módulos conservados. Próximo trabalho:primeiro desses casos executável com as fontes aceites,sem reabrir os recortes entregues.
|
||||||
|
|
||||||
## 11/11/2026 — obrigação PPC instalada emfbff2660
|
## 11/11/2026 — obrigação PPC instalada emfbff2660
|
||||||
|
|
||||||
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
|
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,13 @@
|
|||||||
|
## 11/11/2026 — total e calendário PEC ordinário em QA
|
||||||
|
|
||||||
|
**Novo recorte L8:** preparação do total PEC2026 pelo volume de negócios documental2025:0,5%,limites30.000/100.000MZN aplicados antes de deduzir PPC efectivamente pagos no ano anterior. MesesJunho/Agosto/Outubro,sem inventar dia limite ou importar o arredondamento PPC. Dispensa do ano de início efectivo da actividade29(4) fundamentada separadamente. Fonte:RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 `fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee`, já preservado no acervo.
|
||||||
|
|
||||||
|
Formulário progressivo reutiliza titular/exercício e conserva valores ao ocultar campos sem aplicação; rascunho privado e relatório do mesmo dossier reutilizados. Total preparado não é PEC pago,nem cria dedução anual,reembolso,guia ou consumo de saldo oficial. Ausência de informação não equivale a zero. Sem volume no ano anterior exige base29(5);PPC superiores ao montante limitado ficam por reconciliar,sem obrigação negativa. Esses casos não impedem o cálculo anual independente.
|
||||||
|
|
||||||
|
**Percurso sintético:** cálculo→API privadaPOST/PUT→GET→recálculo→HTML/relatório:40.000MZN ordinário,20.000mínimo antes de PPC,90.000máximo antes de PPC,dispensa inicial0,volumezero e desconhecido indeterminados. Histórico e saldo anual138MZN preservados. QA55441 existente,uma prova nativa/56asserções com rollback,sem nova base/tabela/porta/papel/migração. Seis verificações novas de cálculo/interface e29afectadas aprovadas; tipos/lint/compilação aprovados. Provas `outputs/irpc-pec-obligation-20261111/*-verification.json`;pacote Git com aplicação/reversão conferidas no fecho.
|
||||||
|
|
||||||
|
**Saldo:** implementado em QA,ainda não instalado. Homologação mantém `fbff2660` e270instituições publicadas. Restam entrega Git e visual;montantes por prestação/opção de pagamento único,última base29(5),rectificação29(3),PPC acima do limite,períodos/regimes especiais,guias/juros permanecem abertos. L8 não é integralmente concluído;resíduos L1–L7/restanteL8/L9–L16 e restantes módulos conservados. Próximo trabalho:primeiro desses casos executável com as fontes aceites,sem reabrir os recortes entregues.
|
||||||
|
|
||||||
## 11/11/2026 — obrigação PPC instalada emfbff2660
|
## 11/11/2026 — obrigação PPC instalada emfbff2660
|
||||||
|
|
||||||
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
|
Instalação expressamente autorizada e executada pelo processo Git existente: `fbff2660f280f53fe079221164bb21917032ac70`, conferida às09:04:59Africa/Johannesburg(07:04:59UTC). Utilizável no simulador fiscal,fecho anual L8: **Preparar obrigação PPC**,com imposto anterior documental líquido de retenções,80%,três prestações arredondadas individualmente por excesso ao milhar e calendárioMaio/Julho/Setembro. Guarda e recuperação pelo rascunho privado existente e inclusão no relatório do dossier. Obrigação preparada não equivale a pagamento,nem cria dedução de PPC.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {SimulationIrpcPecObligation} from './simulation-irpc-pec-obligation';
|
||||||
import { SimulationIrpcPpcObligation } from './simulation-irpc-ppc-obligation';
|
import { SimulationIrpcPpcObligation } from './simulation-irpc-ppc-obligation';
|
||||||
import { SimulationIrpcInvestment } from './simulation-irpc-investment';
|
import { SimulationIrpcInvestment } from './simulation-irpc-investment';
|
||||||
import { SimulationIrpcEconomic } from './simulation-irpc-economic';
|
import { SimulationIrpcEconomic } from './simulation-irpc-economic';
|
||||||
@@ -341,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>
|
||||||
|
<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')}
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import {
|
||||||
|
newPecObligation,
|
||||||
|
calculatePecObligation,
|
||||||
|
pecObligationSource,
|
||||||
|
type PecObligation,
|
||||||
|
} from '@/lib/irpc-pec-obligation';
|
||||||
|
import { fractionPresentation } from '@/lib/fiscal-rational';
|
||||||
|
import type { AnnualBook } from '@/lib/irpc-annual-settlement';
|
||||||
|
export function SimulationIrpcPecObligation({
|
||||||
|
book,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
book: AnnualBook;
|
||||||
|
onChange: (p: PecObligation) => void;
|
||||||
|
}) {
|
||||||
|
const p = book.pecObligation;
|
||||||
|
if (!p)
|
||||||
|
return (
|
||||||
|
<section className="rounded border p-3">
|
||||||
|
<h5>Obrigação PEC</h5>
|
||||||
|
<p>
|
||||||
|
Preparar o total e os meses previstos conserva os
|
||||||
|
comprovativos pagos em separado.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
onChange(newPecObligation(book.subjectNuit, book.year))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Preparar obrigação PEC
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
const m = calculatePecObligation(
|
||||||
|
p,
|
||||||
|
book.subjectNuit,
|
||||||
|
book.year,
|
||||||
|
book.assessment,
|
||||||
|
),
|
||||||
|
patch = (changes: Partial<PecObligation>) =>
|
||||||
|
onChange({ ...p, ...changes });
|
||||||
|
const field = (
|
||||||
|
key: Exclude<keyof PecObligation, 'version' | 'scope'>,
|
||||||
|
label: string,
|
||||||
|
) => (
|
||||||
|
<label className="block">
|
||||||
|
{label}
|
||||||
|
<input
|
||||||
|
aria-label={label}
|
||||||
|
value={p[key]}
|
||||||
|
onChange={(e) => patch({ [key]: e.target.value })}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
aria-label="Preparação da obrigação PEC"
|
||||||
|
className="rounded border p-3"
|
||||||
|
>
|
||||||
|
<h5>Obrigação PEC — preparação</h5>
|
||||||
|
<label className="block">
|
||||||
|
Enquadramento PEC
|
||||||
|
<select
|
||||||
|
aria-label="Enquadramento PEC"
|
||||||
|
value={p.scope}
|
||||||
|
onChange={(e) =>
|
||||||
|
patch({
|
||||||
|
scope: e.target.value as PecObligation['scope'],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="">Por confirmar</option>
|
||||||
|
<option value="ordinary_resident">
|
||||||
|
Residente geral — comercial,industrial ou agrícola,ano
|
||||||
|
civil
|
||||||
|
</option>
|
||||||
|
<option value="other">
|
||||||
|
Outro enquadramento — separado
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{p.scope === 'ordinary_resident' && (
|
||||||
|
<>
|
||||||
|
{field('subjectNuit', 'NUIT — obrigação PEC')}
|
||||||
|
{field('year', 'Exercício — obrigação PEC')}
|
||||||
|
{field(
|
||||||
|
'activityStartYear',
|
||||||
|
'Ano de início efectivo da actividade — PEC',
|
||||||
|
)}
|
||||||
|
{field(
|
||||||
|
'activityReference',
|
||||||
|
'Fonte do início de actividade — PEC',
|
||||||
|
)}
|
||||||
|
{p.activityStartYear !== book.year && (
|
||||||
|
<>
|
||||||
|
{field('previousYear', 'Exercício anterior — PEC')}
|
||||||
|
{field(
|
||||||
|
'previousTurnover',
|
||||||
|
'Volume de negócios anterior — MZN',
|
||||||
|
)}
|
||||||
|
{field(
|
||||||
|
'turnoverReference',
|
||||||
|
'Fonte do volume de negócios — PEC',
|
||||||
|
)}
|
||||||
|
{field(
|
||||||
|
'previousPpcPaid',
|
||||||
|
'PPC efectivamente pagos no ano anterior — MZN',
|
||||||
|
)}
|
||||||
|
{field(
|
||||||
|
'ppcReference',
|
||||||
|
'Fonte dos PPC anteriores — PEC',
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{m.pending.length ? (
|
||||||
|
<p role="status">{m.pending.join(' ')}</p>
|
||||||
|
) : m.notApplicable ? (
|
||||||
|
<p>Não aplicável no ano de início da actividade29(4).</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Obrigação total:{' '}
|
||||||
|
{fractionPresentation(m.total!).decimal} MZN.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{m.months.map((month) => (
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
Guardar o rascunho conserva esta preparação. Calcular o fecho
|
||||||
|
anual inclui-a no relatório. Não regista pagamentos,cria
|
||||||
|
deduções ou executa guias. Os comprovativos efectivos permanecem
|
||||||
|
no detalhe PEC existente.
|
||||||
|
</p>
|
||||||
|
<details>
|
||||||
|
<summary>Fonte e limites PEC</summary>
|
||||||
|
<p>{pecObligationSource}</p>
|
||||||
|
</details>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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 {
|
||||||
newInvestmentCredit,
|
newInvestmentCredit,
|
||||||
@@ -124,6 +125,7 @@ export type AnnualBook = {
|
|||||||
economicCredits?: EconomicCredits;
|
economicCredits?: EconomicCredits;
|
||||||
investmentCredit?: InvestmentCredit;
|
investmentCredit?: InvestmentCredit;
|
||||||
ppcObligation?: PpcObligation;
|
ppcObligation?: PpcObligation;
|
||||||
|
pecObligation?: PecObligation;
|
||||||
result?: AnnualRecord;
|
result?: AnnualRecord;
|
||||||
history: AnnualRecord[];
|
history: AnnualRecord[];
|
||||||
};
|
};
|
||||||
@@ -293,6 +295,7 @@ function books(d: Record<string, unknown>): Record<string, AnnualBook> {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
export function validateAnnual(b: AnnualBook) {
|
export function validateAnnual(b: AnnualBook) {
|
||||||
|
if(b?.pecObligation!==undefined)validatePecObligation(b.pecObligation);
|
||||||
if (b?.ppcObligation !== undefined) validatePpcObligation(b.ppcObligation);
|
if (b?.ppcObligation !== undefined) validatePpcObligation(b.ppcObligation);
|
||||||
if (b?.investmentCredit !== undefined)
|
if (b?.investmentCredit !== undefined)
|
||||||
validateInvestmentCredit(b.investmentCredit);
|
validateInvestmentCredit(b.investmentCredit);
|
||||||
@@ -569,6 +572,7 @@ function key(d: Record<string, unknown>, b: AnnualBook) {
|
|||||||
input,
|
input,
|
||||||
dependencies(d, !!b.investmentCredit),
|
dependencies(d, !!b.investmentCredit),
|
||||||
...(b.ppcObligation ? [ppcObligationVersion] : []),
|
...(b.ppcObligation ? [ppcObligationVersion] : []),
|
||||||
|
...(b.pecObligation ? [pecObligationVersion] : []),
|
||||||
...(b.investmentCredit && b.creditCarry?.lots.length
|
...(b.investmentCredit && b.creditCarry?.lots.length
|
||||||
? ['CBF15-credit-carry-combination/1']
|
? ['CBF15-credit-carry-combination/1']
|
||||||
: []),
|
: []),
|
||||||
@@ -1716,6 +1720,7 @@ export function annualReportRows(
|
|||||||
indeterminate: 'Indeterminado',
|
indeterminate: 'Indeterminado',
|
||||||
}[m.status];
|
}[m.status];
|
||||||
return [
|
return [
|
||||||
|
...(m.input.pecObligation ? pecObligationReport(m.input.pecObligation,m.subjectNuit,m.year,m.input.assessment) : []),
|
||||||
...(m.input.ppcObligation ? ppcObligationReport(m.input.ppcObligation,m.subjectNuit,m.year,m.input.assessment) : []),
|
...(m.input.ppcObligation ? ppcObligationReport(m.input.ppcObligation,m.subjectNuit,m.year,m.input.assessment) : []),
|
||||||
[
|
[
|
||||||
'IRPC — L8',
|
'IRPC — L8',
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import {
|
||||||
|
decimalFraction,
|
||||||
|
fraction,
|
||||||
|
subtractFractions,
|
||||||
|
fractionPresentation,
|
||||||
|
type FiscalFraction,
|
||||||
|
} from './fiscal-rational.ts';
|
||||||
|
export const pecObligationVersion = 'IRPC-PEC-obligation/1';
|
||||||
|
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;
|
||||||
|
scope: '' | 'ordinary_resident' | 'other';
|
||||||
|
subjectNuit: string;
|
||||||
|
year: string;
|
||||||
|
activityStartYear: string;
|
||||||
|
activityReference: string;
|
||||||
|
previousYear: string;
|
||||||
|
previousTurnover: string;
|
||||||
|
turnoverReference: string;
|
||||||
|
previousPpcPaid: string;
|
||||||
|
ppcReference: string;
|
||||||
|
};
|
||||||
|
export const newPecObligation = (
|
||||||
|
subjectNuit = '',
|
||||||
|
year = '',
|
||||||
|
): PecObligation => ({
|
||||||
|
version: 1,
|
||||||
|
scope: '',
|
||||||
|
subjectNuit,
|
||||||
|
year,
|
||||||
|
activityStartYear: '',
|
||||||
|
activityReference: '',
|
||||||
|
previousYear: '',
|
||||||
|
previousTurnover: '',
|
||||||
|
turnoverReference: '',
|
||||||
|
previousPpcPaid: '',
|
||||||
|
ppcReference: '',
|
||||||
|
});
|
||||||
|
export function validatePecObligation(p: PecObligation) {
|
||||||
|
if (
|
||||||
|
!p ||
|
||||||
|
p.version !== 1 ||
|
||||||
|
Object.keys(newPecObligation())
|
||||||
|
.filter((k) => k !== 'version')
|
||||||
|
.some(
|
||||||
|
(k) =>
|
||||||
|
typeof (p as unknown as Record<string, unknown>)[k] !==
|
||||||
|
'string',
|
||||||
|
) ||
|
||||||
|
!['', 'ordinary_resident', 'other'].includes(p.scope)
|
||||||
|
)
|
||||||
|
throw Error(
|
||||||
|
'Preparação da obrigação PEC não recuperável;original preservado.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function calculatePecObligation(
|
||||||
|
p: PecObligation,
|
||||||
|
subjectNuit: string,
|
||||||
|
year: string,
|
||||||
|
assessment: string,
|
||||||
|
) {
|
||||||
|
validatePecObligation(p);
|
||||||
|
const pending: string[] = [];
|
||||||
|
let percentage: FiscalFraction | null = null,
|
||||||
|
limited: FiscalFraction | null = null,
|
||||||
|
total: FiscalFraction | null = null,
|
||||||
|
notApplicable = false;
|
||||||
|
if (
|
||||||
|
p.scope !== 'ordinary_resident' ||
|
||||||
|
assessment !== 'normal' ||
|
||||||
|
year !== '2026' ||
|
||||||
|
p.year !== year ||
|
||||||
|
p.subjectNuit !== subjectNuit ||
|
||||||
|
!/^\d{9}$/.test(subjectNuit)
|
||||||
|
)
|
||||||
|
pending.push(
|
||||||
|
'PEC: confirmar titular2026,residente geral com actividade comercial/industrial/agrícola,ano civil e MZN;outros regimes/períodos separados.',
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!/^\d{4}$/.test(p.activityStartYear) ||
|
||||||
|
Number(p.activityStartYear) < 1 ||
|
||||||
|
Number(p.activityStartYear) > 2026 ||
|
||||||
|
!p.activityReference.trim()
|
||||||
|
)
|
||||||
|
pending.push(
|
||||||
|
'PEC: falta ano de início efectivo da actividade e suporte;constituição da empresa não substitui início.',
|
||||||
|
);
|
||||||
|
if (!pending.length && p.activityStartYear === '2026') {
|
||||||
|
notApplicable = true;
|
||||||
|
total = fraction(0n);
|
||||||
|
} else if (!pending.length) {
|
||||||
|
if (
|
||||||
|
p.previousYear !== '2025' ||
|
||||||
|
!p.turnoverReference.trim() ||
|
||||||
|
!p.ppcReference.trim()
|
||||||
|
)
|
||||||
|
pending.push(
|
||||||
|
'PEC: falta exercício anterior2025 e fontes do volume de negócios e PPC efectivamente pagos,mesmo quando zero.',
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
![p.previousTurnover, p.previousPpcPaid].every((v) =>
|
||||||
|
/^\d{1,18}(\.\d{1,2})?$/.test(v),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pending.push(
|
||||||
|
'PEC: valores anteriores desconhecidos/inválidos;zero deve ser explícito.',
|
||||||
|
);
|
||||||
|
if (!pending.length) {
|
||||||
|
const v = decimalFraction(p.previousTurnover),
|
||||||
|
ppc = decimalFraction(p.previousPpcPaid);
|
||||||
|
if (BigInt(v.numerator) === 0n)
|
||||||
|
pending.push(
|
||||||
|
'PEC29(5):sem volume de negócios em2025,recuperar último montante dos anos anteriores;zero não determina automaticamente o mínimo.',
|
||||||
|
);
|
||||||
|
else {
|
||||||
|
percentage = fraction(
|
||||||
|
BigInt(v.numerator),
|
||||||
|
BigInt(v.denominator) * 200n,
|
||||||
|
);
|
||||||
|
const n = BigInt(percentage.numerator),
|
||||||
|
d = BigInt(percentage.denominator);
|
||||||
|
limited =
|
||||||
|
n < 30000n * d
|
||||||
|
? fraction(30000n)
|
||||||
|
: n > 100000n * d
|
||||||
|
? fraction(100000n)
|
||||||
|
: percentage;
|
||||||
|
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.',
|
||||||
|
);
|
||||||
|
else total = residual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
version: pecObligationVersion,
|
||||||
|
source: pecObligationSource,
|
||||||
|
pending,
|
||||||
|
percentage,
|
||||||
|
limited,
|
||||||
|
total,
|
||||||
|
notApplicable,
|
||||||
|
months:
|
||||||
|
total && !notApplicable ? ['2026-06', '2026-08', '2026-10'] : [],
|
||||||
|
instalmentAmountsDetermined: false,
|
||||||
|
paymentExecuted: false,
|
||||||
|
annualDeductionCreated: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function pecObligationReport(
|
||||||
|
p: PecObligation,
|
||||||
|
subjectNuit: string,
|
||||||
|
year: string,
|
||||||
|
assessment: string,
|
||||||
|
): Array<[string, string, string, string]> {
|
||||||
|
const m = calculatePecObligation(p, subjectNuit, year, assessment),
|
||||||
|
show = (v: FiscalFraction | null) =>
|
||||||
|
v ? fractionPresentation(v).decimal + ' MZN' : 'Indeterminado';
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'IRPC — L8',
|
||||||
|
'PEC — obrigação preparada',
|
||||||
|
m.pending.length
|
||||||
|
? m.pending.join(' ')
|
||||||
|
: m.notApplicable
|
||||||
|
? 'Não aplicável no ano de início da actividade29(4);total0 MZN.'
|
||||||
|
: '0,5%: ' +
|
||||||
|
show(m.percentage) +
|
||||||
|
';limitado antes de PPC: ' +
|
||||||
|
show(m.limited) +
|
||||||
|
';obrigação total: ' +
|
||||||
|
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.activityReference +
|
||||||
|
'; ' +
|
||||||
|
p.turnoverReference +
|
||||||
|
'; ' +
|
||||||
|
p.ppcReference +
|
||||||
|
'; ' +
|
||||||
|
m.source,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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 IrpcPecObligationDraftTest 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 (['ordinary' => 138, 'minimum' => 138, 'maximum' => 138, 'start' => 138, 'zero_turnover' => 138, 'unknown' => 138] as $name => $balance) {
|
||||||
|
$snapshot = json_decode(file_get_contents(base_path('outputs/irpc-pec-obligation-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('2025', $book['pecObligation']['previousYear']);
|
||||||
|
$this->assertSame('SYNTHETIC effective activity'.($name === 'start' ? '2026' : '2020'), $book['pecObligation']['activityReference']);
|
||||||
|
file_put_contents(base_path('outputs/irpc-pec-obligation-20261111/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { newPecObligation } from '../../../resources/js/lib/irpc-pec-obligation.ts';
|
||||||
|
export const plan = () => ({
|
||||||
|
...newPecObligation('123456789', '2026'),
|
||||||
|
scope: 'ordinary_resident',
|
||||||
|
activityStartYear: '2020',
|
||||||
|
activityReference: 'SYNTHETIC effective activity2020',
|
||||||
|
previousYear: '2025',
|
||||||
|
previousTurnover: '10000000',
|
||||||
|
turnoverReference: 'SYNTHETIC sales2025',
|
||||||
|
previousPpcPaid: '10000',
|
||||||
|
ppcReference: 'SYNTHETIC effective PPC2025',
|
||||||
|
});
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
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-obligation.ts';
|
||||||
|
import * as rational from '../../resources/js/lib/fiscal-rational.ts';
|
||||||
|
import { renderToStaticMarkup } from 'react-dom/server';
|
||||||
|
import { plan } from './helpers/irpc-pec-obligation-case.mjs';
|
||||||
|
import { investmentCase } from './helpers/irpc-investment-case.mjs';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url),
|
||||||
|
module = { exports: {} };
|
||||||
|
const source = fs.readFileSync(
|
||||||
|
new URL(
|
||||||
|
'../../resources/js/components/simulation-irpc-pec-obligation.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 === '@/lib/irpc-annual-settlement'
|
||||||
|
? annual
|
||||||
|
: name === '@/lib/irpc-pec-obligation'
|
||||||
|
? 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.SimulationIrpcPecObligation;
|
||||||
|
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)]
|
||||||
|
: [];
|
||||||
|
function harness(c) {
|
||||||
|
let b = c.b;
|
||||||
|
return {
|
||||||
|
book: () => b,
|
||||||
|
render: () =>
|
||||||
|
Component({
|
||||||
|
draft: c.d,
|
||||||
|
book: b,
|
||||||
|
onChange: (next) => {
|
||||||
|
b = { ...b, pecObligation: next };
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
click(label) {
|
||||||
|
const button = all(this.render()).find(
|
||||||
|
(n) => n.type === 'button' && text(n) === label,
|
||||||
|
);
|
||||||
|
assert.ok(button, label);
|
||||||
|
button.props.onClick();
|
||||||
|
},
|
||||||
|
change(label, value) {
|
||||||
|
const input = all(this.render()).find(
|
||||||
|
(n) => n.props['aria-label'] === label,
|
||||||
|
);
|
||||||
|
assert.ok(input, label);
|
||||||
|
input.props.onChange({ target: { value } });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
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');
|
||||||
|
assert.equal(h.book().pecObligation.subjectNuit, c.b.subjectNuit);
|
||||||
|
assert.equal(h.book().pecObligation.previousTurnover, '');
|
||||||
|
h.change('Enquadramento PEC', 'ordinary_resident');
|
||||||
|
h.change('Ano de início efectivo da actividade — PEC', '2020');
|
||||||
|
h.change('Fonte do início de actividade — PEC', 'SYNTHETIC activity');
|
||||||
|
h.change('Exercício anterior — PEC', '2025');
|
||||||
|
h.change('Volume de negócios anterior — MZN', '10000000');
|
||||||
|
h.change('Fonte do volume de negócios — PEC', 'SYNTHETIC volume');
|
||||||
|
h.change('PPC efectivamente pagos no ano anterior — MZN', '10000');
|
||||||
|
h.change('Fonte dos PPC anteriores — PEC', 'SYNTHETIC actual PPC');
|
||||||
|
assert.match(text(h.render()), /40000/);
|
||||||
|
assert.match(text(h.render()), /2026-06/);
|
||||||
|
assert.deepEqual(h.book().pecPayments, c.b.pecPayments);
|
||||||
|
h.change('Ano de início efectivo da actividade — PEC', '2026');
|
||||||
|
assert.match(text(h.render()), /Não aplicável/);
|
||||||
|
assert.equal(h.book().pecObligation.previousTurnover, '10000000');
|
||||||
|
h.change('Ano de início efectivo da actividade — PEC', '2020');
|
||||||
|
assert.match(text(h.render()), /40000/);
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
Component({
|
||||||
|
book: {
|
||||||
|
...c.b,
|
||||||
|
pecObligation: { ...plan(), subjectNuit: c.b.subjectNuit },
|
||||||
|
},
|
||||||
|
onChange: () => {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert.match(html, /2026-10/);
|
||||||
|
assert.match(html, /Fonte e limites PEC/);
|
||||||
|
});
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {
|
||||||
|
calculatePecObligation,
|
||||||
|
validatePecObligation,
|
||||||
|
} from '../../resources/js/lib/irpc-pec-obligation.ts';
|
||||||
|
import { plan } from './helpers/irpc-pec-obligation-case.mjs';
|
||||||
|
import { combinedCase } from './helpers/irpc-investment-combination-case.mjs';
|
||||||
|
import {
|
||||||
|
calculateAnnual,
|
||||||
|
recordAnnual,
|
||||||
|
annualCurrent,
|
||||||
|
annualBook,
|
||||||
|
saveAnnualBook,
|
||||||
|
annualHtml,
|
||||||
|
} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||||
|
const f = (n) => ({ numerator: String(n), denominator: '1' }),
|
||||||
|
calc = (p) => calculatePecObligation(p, '123456789', '2026', 'normal');
|
||||||
|
test('PEC caps percentage before subtracting actual previous PPC; no PPC instalment rounding reused', () => {
|
||||||
|
let m = calc(plan());
|
||||||
|
assert.deepEqual(m.percentage, f(50000));
|
||||||
|
assert.deepEqual(m.total, f(40000));
|
||||||
|
assert.deepEqual(m.months, ['2026-06', '2026-08', '2026-10']);
|
||||||
|
assert.equal(m.paymentExecuted, false);
|
||||||
|
assert.equal(m.annualDeductionCreated, false);
|
||||||
|
assert.equal(m.instalmentAmountsDetermined, false);
|
||||||
|
assert.deepEqual(
|
||||||
|
calc({ ...plan(), previousTurnover: '1000000' }).total,
|
||||||
|
f(20000),
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
calc({ ...plan(), previousTurnover: '50000000' }).total,
|
||||||
|
f(90000),
|
||||||
|
);
|
||||||
|
assert.deepEqual(calc({ ...plan(), previousPpcPaid: '50000' }).total, f(0));
|
||||||
|
});
|
||||||
|
test('documented first activity year is not applicable without fabricating previous business figures', () => {
|
||||||
|
const m = calc({
|
||||||
|
...plan(),
|
||||||
|
activityStartYear: '2026',
|
||||||
|
previousTurnover: '',
|
||||||
|
previousPpcPaid: '',
|
||||||
|
turnoverReference: '',
|
||||||
|
ppcReference: '',
|
||||||
|
});
|
||||||
|
assert.deepEqual(m.pending, []);
|
||||||
|
assert.equal(m.notApplicable, true);
|
||||||
|
assert.deepEqual(m.total, f(0));
|
||||||
|
assert.deepEqual(m.months, []);
|
||||||
|
});
|
||||||
|
test('unknown turnover is distinct from zero; no turnover requires29(5),excess PPC never becomes refund', () => {
|
||||||
|
for (const change of [
|
||||||
|
{ previousTurnover: '' },
|
||||||
|
{ previousTurnover: '0' },
|
||||||
|
{ previousPpcPaid: '' },
|
||||||
|
{ previousPpcPaid: '60000' },
|
||||||
|
{ scope: 'other' },
|
||||||
|
{ subjectNuit: '999999999' },
|
||||||
|
{ previousYear: '2024' },
|
||||||
|
{ activityStartYear: '2027' },
|
||||||
|
{ activityReference: '' },
|
||||||
|
{ turnoverReference: '' },
|
||||||
|
{ ppcReference: '' },
|
||||||
|
]) {
|
||||||
|
const m = calc({ ...plan(), ...change });
|
||||||
|
assert.ok(m.pending.length);
|
||||||
|
assert.equal(m.total, null);
|
||||||
|
}
|
||||||
|
assert.match(
|
||||||
|
calc({ ...plan(), previousTurnover: '0' }).pending.join(' '),
|
||||||
|
/29\(5\)/,
|
||||||
|
);
|
||||||
|
assert.throws(() =>
|
||||||
|
validatePecObligation({ ...plan(), previousTurnover: null }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('existing private book and annual history retain PEC preparation without changing paid deductions or balance', () => {
|
||||||
|
const { d, b } = combinedCase(),
|
||||||
|
before = calculateAnnual(d, b);
|
||||||
|
const prior = recordAnnual(d, b, '2026-11-11T10:30:00Z');
|
||||||
|
prior.pecObligation = { ...plan(), subjectNuit: b.subjectNuit };
|
||||||
|
assert.equal(annualCurrent(d, prior), false);
|
||||||
|
const next = recordAnnual(d, prior, '2026-11-11T10:31:00Z');
|
||||||
|
d.modelo_a_snapshot = saveAnnualBook(d, next);
|
||||||
|
const back = annualBook(d);
|
||||||
|
assert.equal(back.history.length, 1);
|
||||||
|
assert.equal(annualCurrent(d, back), true);
|
||||||
|
assert.deepEqual(back.pecObligation, next.pecObligation);
|
||||||
|
assert.deepEqual(
|
||||||
|
back.result.model.arithmeticBalance,
|
||||||
|
before.arithmeticBalance,
|
||||||
|
);
|
||||||
|
assert.deepEqual(back.pecPayments, b.pecPayments);
|
||||||
|
assert.match(annualHtml(d), /PEC — obrigação preparada/);
|
||||||
|
assert.match(annualHtml(d), /2026-06/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('documented old activity is not excluded by an invented1900 cut-off',()=>{assert.deepEqual(calc({...plan(),activityStartYear:'1899'}).total,f(40000));});
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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-obligation-20261111/',
|
||||||
|
import.meta.url,
|
||||||
|
);
|
||||||
|
fs.mkdirSync(out, { recursive: true });
|
||||||
|
const scenarios = {
|
||||||
|
ordinary: {},
|
||||||
|
minimum: { previousTurnover: '1000000' },
|
||||||
|
maximum: { previousTurnover: '50000000' },
|
||||||
|
start: {
|
||||||
|
activityStartYear: '2026',
|
||||||
|
activityReference: 'SYNTHETIC effective activity2026',
|
||||||
|
previousTurnover: '',
|
||||||
|
previousPpcPaid: '',
|
||||||
|
turnoverReference: '',
|
||||||
|
ppcReference: '',
|
||||||
|
},
|
||||||
|
zero_turnover: { previousTurnover: '0' },
|
||||||
|
unknown: { previousTurnover: '' },
|
||||||
|
};
|
||||||
|
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 — obrigação preparada/);
|
||||||
|
assert.match(JSON.stringify(report), /PEC — obrigação preparada/);
|
||||||
|
assert.equal(m.paymentExecuted, false);
|
||||||
|
assert.equal(m.annualDeductionCreated, false);
|
||||||
|
const expected = {
|
||||||
|
ordinary: '40000',
|
||||||
|
minimum: '20000',
|
||||||
|
maximum: '90000',
|
||||||
|
start: '0',
|
||||||
|
zero_turnover: null,
|
||||||
|
unknown: null,
|
||||||
|
}[name];
|
||||||
|
assert.equal(m.total?.numerator ?? null, expected);
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
process.argv[2] === 'generate'
|
||||||
|
? 'Six synthetic PEC scenarios prepared.'
|
||||||
|
: 'Six native recovered PEC 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-obligation-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/IrpcPecObligationDraftTest.php'];
|
||||||
|
$argc = count($argv);
|
||||||
|
$_SERVER['argv'] = $argv;
|
||||||
|
$_SERVER['argc'] = $argc;
|
||||||
|
require $root.'/vendor/autoload.php';
|
||||||
|
require $root.'/vendor/phpunit/phpunit/phpunit';
|
||||||
Reference in New Issue
Block a user