feat: support public-payer treaty exemption and exact economic adjustments
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-11-10 13:54:06 +02:00
parent 60b52fc8c7
commit b10b35f308
16 changed files with 472 additions and 78 deletions
@@ -1,5 +1,19 @@
# Rastreabilidade, utilização e formação
## 10/11/2026 — CDT instalada; isenção governamental e precisão económica em QA
**Homologação: `60b52fc8c7446cbbee904a4411eca8ad284aa7ad`**, promovida às11:49:26UTC e conferida às11:50:11UTC (13:50 locais). Delta13hashes Git e revisão HTTPS iguais; portal/login/up/revision HTTP200, .env preservado, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos; ac67f9c1 disponível para reversão. Recibos `outputs/cdt-pt-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH encerrada, credencial não guardada. Royalties/dividendos/juros ordinários PT–MZ deste recorte deixam de ser pendência de instalação; não reabrir provas aceites.
**Novos fechos dirigidos apenas em QA, ainda não instalados:**
- CDT11(3)a: devedor Estado português, subdivisão política/administrativa ou autarquia, expressamente qualificado e documentado; mantém residência, fonte, definição, período e restantes condições do recorte. Limite convencional0, imposto pago10 sem crédito elegível e saldo708 MZN no exemplo. Não gera reembolso/transporte nem altera L6. Beneficiários/acordos11(3)b e outras situações permanecem separados. Prova `outputs/irpc-pt-mz-government-20261110/verification.json`, com API/gravação/recuperação/HTML/dossier e fundamento do devedor no relatório.
- L6: diferença económica conserva fracções exactas, incluindo subcêntimos e n/d, pelo mecanismo racional existente. Remove a limitação técnica de exigir cêntimos inteiros neste mapa; não cria regra de arredondamento fiscal. Montante do tópico é reconciliado por igualdade racional, evitando tratar0,006 como0 ou aceitar truncamento. Exemplo de IRPC subjacente20,01: nominal12,006, efeito112,006, base4512,006, colecta1443,84192; reconferida L8, saldo723,83592. Repetição/substituição não acumula; valores já considerados/históricos preservados. Prova `outputs/irpc-economic-fraction-20261110/verification.json`, até API/recuperação/HTML/dossier/L8. Outros mapas e arredondamento de declarações oficiais conservam âmbito próprio.
Conjunto novo agrupável sobre60b52fc8; não integra a revisão instalada. Conferência visual permanece pendente no mesmo registo S08. Resíduos L1–L7, restante L8 e L9–L16 preservados; nenhum lote ou portal declarado integral. Próximos ramos: CDT11(3)b e restantes correspondências concretas, obrigações/benefícios e demais percursos conforme saldo existente.
Conjunto posterior preparado em `outputs/entrega-isencao-precisao-20261110.zip`, base exacta60b52fc8. Tipos/lint/Pint/build QA aprovados, aplicação/reversão conferidas em índices temporários. Não instalado; provas e pacotes anteriores preservados.
## 10/11/2026 — económico instalado; CDT Portugal avançada em QA
**Homologação: `ac67f9c13a6561b66c5da788a48849beac90a905`**, promovida pelo executor Git existente às11:32:14UTC e conferida às11:33:16UTC (13:33 locais). Revisão HTTPS e27hashes Git iguais; portal/login/up/revision HTTP200; .env preservado, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos. Release c359364d conservada. Recibos `outputs/economic-special-delivery-20261110/{backup,restore,files-restore,verification}.json`. Credencial não guardada; sessão SSH encerrada. Inclui quota económica, inclusão/reconciliação ordinária e especial L6, partilha64(3) e associações64(4). Entradas anteriores «apenas QA» são históricas nestes recortes; não reinstalar por consequência.
@@ -1,3 +1,16 @@
## 10/11/2026 — CDT instalada; isenção governamental e precisão económica em QA
**Homologação: `60b52fc8c7446cbbee904a4411eca8ad284aa7ad`**, promovida às11:49:26UTC e conferida às11:50:11UTC (13:50 locais). Delta13hashes Git e revisão HTTPS iguais; portal/login/up/revision HTTP200, .env preservado, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos; ac67f9c1 disponível para reversão. Recibos `outputs/cdt-pt-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH encerrada, credencial não guardada. Royalties/dividendos/juros ordinários PT–MZ deste recorte deixam de ser pendência de instalação; não reabrir provas aceites.
**Novos fechos dirigidos apenas em QA, ainda não instalados:**
- CDT11(3)a: devedor Estado português, subdivisão política/administrativa ou autarquia, expressamente qualificado e documentado; mantém residência, fonte, definição, período e restantes condições do recorte. Limite convencional0, imposto pago10 sem crédito elegível e saldo708 MZN no exemplo. Não gera reembolso/transporte nem altera L6. Beneficiários/acordos11(3)b e outras situações permanecem separados. Prova `outputs/irpc-pt-mz-government-20261110/verification.json`, com API/gravação/recuperação/HTML/dossier e fundamento do devedor no relatório.
- L6: diferença económica conserva fracções exactas, incluindo subcêntimos e n/d, pelo mecanismo racional existente. Remove a limitação técnica de exigir cêntimos inteiros neste mapa; não cria regra de arredondamento fiscal. Montante do tópico é reconciliado por igualdade racional, evitando tratar0,006 como0 ou aceitar truncamento. Exemplo de IRPC subjacente20,01: nominal12,006, efeito112,006, base4512,006, colecta1443,84192; reconferida L8, saldo723,83592. Repetição/substituição não acumula; valores já considerados/históricos preservados. Prova `outputs/irpc-economic-fraction-20261110/verification.json`, até API/recuperação/HTML/dossier/L8. Outros mapas e arredondamento de declarações oficiais conservam âmbito próprio.
Conjunto novo agrupável sobre60b52fc8; não integra a revisão instalada. Conferência visual permanece pendente no mesmo registo S08. Resíduos L1–L7, restante L8 e L9–L16 preservados; nenhum lote ou portal declarado integral. Próximos ramos: CDT11(3)b e restantes correspondências concretas, obrigações/benefícios e demais percursos conforme saldo existente.
Conjunto posterior preparado em `outputs/entrega-isencao-precisao-20261110.zip`, base exacta60b52fc8. Tipos/lint/Pint/build QA aprovados, aplicação/reversão conferidas em índices temporários. Não instalado; provas e pacotes anteriores preservados.
## 10/11/2026 — económico instalado; CDT Portugal avançada em QA
**Homologação: `ac67f9c13a6561b66c5da788a48849beac90a905`**, promovida pelo executor Git existente às11:32:14UTC e conferida às11:33:16UTC (13:33 locais). Revisão HTTPS e27hashes Git iguais; portal/login/up/revision HTTP200; .env preservado, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos. Release c359364d conservada. Recibos `outputs/economic-special-delivery-20261110/{backup,restore,files-restore,verification}.json`. Credencial não guardada; sessão SSH encerrada. Inclui quota económica, inclusão/reconciliação ordinária e especial L6, partilha64(3) e associações64(4). Entradas anteriores «apenas QA» são históricas nestes recortes; não reinstalar por consequência.
File diff suppressed because one or more lines are too long
@@ -1,3 +1,16 @@
## 10/11/2026 — CDT instalada; isenção governamental e precisão económica em QA
**Homologação: `60b52fc8c7446cbbee904a4411eca8ad284aa7ad`**, promovida às11:49:26UTC e conferida às11:50:11UTC (13:50 locais). Delta13hashes Git e revisão HTTPS iguais; portal/login/up/revision HTTP200, .env preservado, zero migrations/importações. Backup fresco/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos; ac67f9c1 disponível para reversão. Recibos `outputs/cdt-pt-delivery-20261110/{backup,restore,files-restore,verification}.json`. Sessão SSH encerrada, credencial não guardada. Royalties/dividendos/juros ordinários PT–MZ deste recorte deixam de ser pendência de instalação; não reabrir provas aceites.
**Novos fechos dirigidos apenas em QA, ainda não instalados:**
- CDT11(3)a: devedor Estado português, subdivisão política/administrativa ou autarquia, expressamente qualificado e documentado; mantém residência, fonte, definição, período e restantes condições do recorte. Limite convencional0, imposto pago10 sem crédito elegível e saldo708 MZN no exemplo. Não gera reembolso/transporte nem altera L6. Beneficiários/acordos11(3)b e outras situações permanecem separados. Prova `outputs/irpc-pt-mz-government-20261110/verification.json`, com API/gravação/recuperação/HTML/dossier e fundamento do devedor no relatório.
- L6: diferença económica conserva fracções exactas, incluindo subcêntimos e n/d, pelo mecanismo racional existente. Remove a limitação técnica de exigir cêntimos inteiros neste mapa; não cria regra de arredondamento fiscal. Montante do tópico é reconciliado por igualdade racional, evitando tratar0,006 como0 ou aceitar truncamento. Exemplo de IRPC subjacente20,01: nominal12,006, efeito112,006, base4512,006, colecta1443,84192; reconferida L8, saldo723,83592. Repetição/substituição não acumula; valores já considerados/históricos preservados. Prova `outputs/irpc-economic-fraction-20261110/verification.json`, até API/recuperação/HTML/dossier/L8. Outros mapas e arredondamento de declarações oficiais conservam âmbito próprio.
Conjunto novo agrupável sobre60b52fc8; não integra a revisão instalada. Conferência visual permanece pendente no mesmo registo S08. Resíduos L1–L7, restante L8 e L9–L16 preservados; nenhum lote ou portal declarado integral. Próximos ramos: CDT11(3)b e restantes correspondências concretas, obrigações/benefícios e demais percursos conforme saldo existente.
Conjunto posterior preparado em `outputs/entrega-isencao-precisao-20261110.zip`, base exacta60b52fc8. Tipos/lint/Pint/build QA aprovados, aplicação/reversão conferidas em índices temporários. Não instalado; provas e pacotes anteriores preservados.
## 10/11/2026 — económico instalado; CDT Portugal avançada em QA
**Homologação: `ac67f9c13a6561b66c5da788a48849beac90a905`**, promovida pelo executor Git existente às11:32:14UTC e conferida às11:33:16UTC (13:33 locais). Revisão HTTPS e27hashes Git iguais; portal/login/up/revision HTTP200; .env preservado, zero migrations/importações. Backup/restauro190tabelas com contagens/checksums iguais e369ficheiros/configuração extraídos/conferidos. Release c359364d conservada. Recibos `outputs/economic-special-delivery-20261110/{backup,restore,files-restore,verification}.json`. Credencial não guardada; sessão SSH encerrada. Inclui quota económica, inclusão/reconciliação ordinária e especial L6, partilha64(3) e associações64(4). Entradas anteriores «apenas QA» são históricas nestes recortes; não reinstalar por consequência.
@@ -50,7 +50,9 @@ export function SimulationIrpcEconomicInclusion({
deste mapa; zero deve ser indicado expressamente. A prévia
propõe apenas a diferença e conserva o resultado anterior.
Depois de confirmar, calcule e guarde L6 pelo percurso
existente; L8 exige nova conferência da colecta.
existente; L8 exige nova conferência da colecta. Valores
derivados mantêm as fracções exactas, sem arredondamento fiscal
presumido.
</p>
<label className="block">
Origem dos lucros
@@ -52,17 +52,17 @@ export function SimulationIrpcPtCdt({
if (p.treatyMethod === 'ptMzInterest')
facts.push([
'governmentInterestExemption',
'Juros abrangidos pela isenção governamental do art.11(3) (o cálculo ordinário exige Não)',
'Juros abrangidos pela isenção governamental do art.11(3)',
]);
return (
<fieldset className="space-y-2 rounded border p-3">
<legend>Portugal — correspondência CDT</legend>
<p>
Confirme rendimento bruto em MZN, residência convencional,
natureza e período coberto pelos documentos. A taxa de 10%
limita o imposto estrangeiro elegível; não altera a parcela
líquida nacional nem prova pagamento. O limite manual anterior
fica conservado.
natureza e período coberto pelos documentos. O limite
convencional do caso qualificado restringe o imposto estrangeiro
elegível; não altera a parcela líquida nacional nem prova
pagamento. O limite manual anterior fica conservado.
</p>
{(
[
@@ -84,6 +84,63 @@ export function SimulationIrpcPtCdt({
/>
</label>
))}
{p.treatyMethod === 'ptMzInterest' &&
p.treatyPreparation?.governmentInterestExemption === 'yes' && (
<fieldset className="rounded border p-3">
<legend>
Isenção por devedor público português — art.11(3)a
</legend>
<p>
Escolha a qualificação documentada do devedor.
Beneficiários públicos e acordos entre governos do
art.11(3)b exigem correspondência própria. A isenção
na fonte não gera reembolso de imposto pago pelo
portal.
</p>
<label className="block">
Devedor dos juros
<select
aria-label={
prefix + 'Devedor público português'
}
value={
p.treatyPreparation.governmentPayer ?? ''
}
onChange={(e) =>
change('governmentPayer', e.target.value)
}
>
<option value="">Por documentar</option>
<option value="state">Estado português</option>
<option value="subdivision">
Subdivisão política ou administrativa
portuguesa
</option>
<option value="local">
Autarquia portuguesa
</option>
</select>
</label>
<label className="block">
Suporte da qualificação do devedor
<input
aria-label={
prefix + 'Suporte do devedor público'
}
value={
p.treatyPreparation
.governmentPayerReference ?? ''
}
onChange={(e) =>
change(
'governmentPayerReference',
e.target.value,
)
}
/>
</label>
</fieldset>
)}
{facts.map(([key, label]) => (
<label key={key} className="block">
{label}
+62 -30
View File
@@ -27,6 +27,8 @@ export type InternationalTaxProof = AnnualPpc & {
exerciseReference: string;
};
export type PtMzRoyaltyPreparation = {
governmentPayer?: '' | 'state' | 'subdivision' | 'local';
governmentPayerReference?: string;
capitalDefinition?: '' | 'yes' | 'no';
governmentInterestExemption?: '' | 'yes' | 'no';
gross: string;
@@ -244,6 +246,13 @@ export function validateInternational(c: InternationalCredits) {
);
},
)) ||
(p.treatyPreparation?.governmentPayer !== undefined &&
!['', 'state', 'subdivision', 'local'].includes(
p.treatyPreparation.governmentPayer,
)) ||
(p.treatyPreparation?.governmentPayerReference !== undefined &&
typeof p.treatyPreparation.governmentPayerReference !==
'string') ||
!evidenceValid(p.previousUse) ||
!Array.isArray(p.payments)
)
@@ -408,13 +417,25 @@ export function ptMzTreatyLimit(p: InternationalPosition) {
throw Error(
'Definição convencional de dividendos/juros por documentar.',
);
if (
p.treatyMethod === 'ptMzInterest' &&
q.governmentInterestExemption !== 'no'
)
throw Error(
'Juros abrangidos ou por corresponder à isenção governamental11(3) exigem tratamento próprio; não presumir limite ordinário.',
);
let governmentExemption: {
payer: 'state' | 'subdivision' | 'local';
reference: string;
} | null = null;
if (p.treatyMethod === 'ptMzInterest') {
if (q.governmentInterestExemption === 'yes') {
if (!q.governmentPayer || !q.governmentPayerReference?.trim())
throw Error(
'Isenção11(3)a exige devedor documentado: Estado português, subdivisão política/administrativa ou autarquia; beneficiários/acordos11(3)b permanecem separados.',
);
governmentExemption = {
payer: q.governmentPayer,
reference: q.governmentPayerReference,
};
} else if (q.governmentInterestExemption !== 'no')
throw Error(
'Corresponda a aplicação ou não aplicação da isenção11(3); desconhecido não significa limite ordinário10%.',
);
}
if (
!validDate(q.from) ||
!validDate(q.to) ||
@@ -433,33 +454,44 @@ export function ptMzTreatyLimit(p: InternationalPosition) {
'Rendimento bruto inferior à parcela líquida incluída; reconcilie custos/perdas e base.',
);
return {
version:
p.treatyMethod === 'ptMzRoyalty'
? 'IRPC-PT-MZ-royalty-credit/1'
: 'IRPC-PT-MZ-capital-credit/1',
version: governmentExemption
? 'IRPC-PT-MZ-government-interest/1'
: p.treatyMethod === 'ptMzRoyalty'
? 'IRPC-PT-MZ-royalty-credit/1'
: 'IRPC-PT-MZ-capital-credit/1',
...(p.treatyMethod !== 'ptMzRoyalty' ? { nature: p.treatyMethod } : {}),
gross,
rate: '10',
limit: fraction(
BigInt(gross.numerator),
BigInt(gross.denominator) * 10n,
),
...(governmentExemption ? { governmentExemption } : {}),
rate: governmentExemption ? '0' : '10',
limit: governmentExemption
? zero()
: fraction(
BigInt(gross.numerator),
BigInt(gross.denominator) * 10n,
),
from: q.from,
to: q.to,
reference: q.reference,
source:
p.treatyMethod === 'ptMzRoyalty'
? 'CDT PT–MZ, RAR36/1992 art12(2/4–6),PDF13–14 e23(3)a,PDF21; Protocolo RAR36/2009 arts1/7,PDF1–2; CIRPC65(2). Royalties ordinárias não digitais, sem EP/instalação fixa ligada nem excesso por relações especiais. Não confundir com retenção L5 paga por devedor moçambicano; sem isenção ou tax sparing presumidos.'
: 'CDT PT–MZ, RAR36/1992 art23(3)a,PDF21; CIRPC65(2). ' +
(p.treatyMethod === 'ptMzDividend'
? 'Dividendos10(2–4),PDF10; taxa10% actualizada pelo Protocolo RAR36/2009 art6,PDF2; IRPC/art2 actualizado pelo art1.'
: p.treatyMethod === 'ptMzInterest'
? 'Juros11(2–7),PDF11–12, excluída a isenção11(3); IRPC/art2 actualizado pelo Protocolo RAR36/2009 art1.'
: 'Royalties12(2/4–6),PDF13–14; definição actualizada pelo Protocolo RAR36/2009 art7,PDF2; IRPC/art2 actualizado pelo art1. Recorte ordinário não digital.') +
(p.treatyMethod === 'ptMzDividend'
? ' Sem EP/instalação fixa ligada.'
: ' Sem EP/instalação fixa ligada ou excesso convencional por relações especiais.') +
' Não confundir com retenção L5 de devedor moçambicano; sem isenção ou tax sparing presumidos.',
reference: governmentExemption
? q.reference +
'; devedor ' +
governmentExemption.payer +
'; ' +
governmentExemption.reference
: q.reference,
source: governmentExemption
? 'CDT PT–MZ, RAR36/1992 art11(3)a,PDF11, e23(3)a,PDF21; CIRPC65(2). Devedor Estado português/subdivisão/autarquia documentado. Juros isentos no Estado da fonte; limite convencional zero, sem crédito ou reembolso do imposto indevidamente pago pelo portal. Art11(3)b e demais situações permanecem separados.'
: p.treatyMethod === 'ptMzRoyalty'
? 'CDT PT–MZ, RAR36/1992 art12(2/4–6),PDF13–14 e23(3)a,PDF21; Protocolo RAR36/2009 arts1/7,PDF1–2; CIRPC65(2). Royalties ordinárias não digitais, sem EP/instalação fixa ligada nem excesso por relações especiais. Não confundir com retenção L5 paga por devedor moçambicano; sem isenção ou tax sparing presumidos.'
: 'CDT PT–MZ, RAR36/1992 art23(3)a,PDF21; CIRPC65(2). ' +
(p.treatyMethod === 'ptMzDividend'
? 'Dividendos10(2–4),PDF10; taxa10% actualizada pelo Protocolo RAR36/2009 art6,PDF2; IRPC/art2 actualizado pelo art1.'
: p.treatyMethod === 'ptMzInterest'
? 'Juros11(2–7),PDF11–12, excluída a isenção11(3); IRPC/art2 actualizado pelo Protocolo RAR36/2009 art1.'
: 'Royalties12(2/4–6),PDF13–14; definição actualizada pelo Protocolo RAR36/2009 art7,PDF2; IRPC/art2 actualizado pelo art1. Recorte ordinário não digital.') +
(p.treatyMethod === 'ptMzDividend'
? ' Sem EP/instalação fixa ligada.'
: ' Sem EP/instalação fixa ligada ou excesso convencional por relações especiais.') +
' Não confundir com retenção L5 de devedor moçambicano; sem isenção ou tax sparing presumidos.',
};
}
export function internationalPosition(
+12 -17
View File
@@ -14,6 +14,7 @@ import {
} from './irpc-international-credit.ts';
import {
addFractions,
fractionPresentation,
subtractFractions,
fraction,
type FiscalFraction,
@@ -48,20 +49,10 @@ export function validateEconomicInclusions(input: EconomicInclusions) {
});
}
export function economicInclusionMoney(value: FiscalFraction): string {
const n = BigInt(value.numerator) * 100n,
d = BigInt(value.denominator);
if (n % d !== 0n)
throw Error(
'Correcção com fracção de cêntimo exige tratamento próprio; não arredondar por presunção.',
);
const cents = n / d,
absolute = cents < 0n ? -cents : cents;
return (
(cents < 0n ? '-' : '') +
absolute / 100n +
'.' +
(absolute % 100n).toString().padStart(2, '0')
);
const presentation = fractionPresentation(value);
return presentation.approximate
? value.numerator + '/' + value.denominator
: presentation.decimal;
}
export function calculateEconomicInclusions(
input: EconomicInclusions,
@@ -148,7 +139,7 @@ export function calculateEconomicInclusions(
const targets = [creditMoney(r.income), nominal];
const included = [
creditMoney(row.profitAlreadyIncluded),
creditMoney(row.creditAlreadyIncluded),
creditLimit(row.creditAlreadyIncluded),
];
if (
r.incomeIncluded === 'yes' &&
@@ -178,7 +169,7 @@ export function calculateEconomicInclusions(
BigInt(effect.denominator),
),
effect,
reason: `${economicInclusionVersion}; CIRPC64(1–2)/53(1)a; ${index ? 'acréscimo nominal' : 'lucros'}: alvo ${economicInclusionMoney(target)}; já considerado ${economicInclusionMoney(included[index])}; diferença ${economicInclusionMoney(effect)}. ${row.reference}${specialReference ? ' ' + specialReference : ''}`,
reason: `${economicInclusionVersion}${[target, included[index], effect].some((v) => (BigInt(v.numerator) * 100n) % BigInt(v.denominator) !== 0n) ? '; IRPC-economic-inclusion-exact/1: fracções conservadas, sem arredondamento fiscal' : ''}; CIRPC64(1–2)/53(1)a; ${index ? 'acréscimo nominal' : 'lucros'}: alvo ${economicInclusionMoney(target)}; já considerado ${economicInclusionMoney(included[index])}; diferença ${economicInclusionMoney(effect)}. ${row.reference}${specialReference ? ' ' + specialReference : ''}`,
});
});
} catch (e) {
@@ -241,7 +232,11 @@ export function prepareEconomicInclusion(
'Reconcilie as outras correcções aos proveitos antes de substituir parcelas.',
);
const current =
topic.state === 'absent' ? fraction(0n) : creditMoney(topic.amount);
topic.state === 'absent'
? fraction(0n)
: old.rows.length
? creditLimit(topic.amount)
: creditMoney(topic.amount);
const remaining = subtractFractions(
current,
addFractions(
+20 -9
View File
@@ -1,3 +1,4 @@
import { creditLimit, creditCompare } from './irpc-international-credit.ts';
import type { NormalDepreciationModel } from './irpc-normal-depreciation.ts';
import type { NormalCostsModel } from './irpc-normal-costs.ts';
import type { SchedulesModel } from './irpc-normal-schedules.ts';
@@ -126,7 +127,13 @@ export type NormalProfitModel = {
source: string;
};
const zero = () => fraction(0n, 1n);
function money(s: string) {
function money(s: string, exact = false) {
if (exact) {
const v = creditLimit(s.startsWith('-') ? s.slice(1) : s);
return s.startsWith('-')
? fraction(-BigInt(v.numerator), BigInt(v.denominator))
: v;
}
if (!/^-?\d+(?:\.\d{1,2})?$/.test(s)) throw Error('Montante inválido');
const v = decimalFraction(s.startsWith('-') ? s.slice(1) : s);
return s.startsWith('-')
@@ -288,8 +295,11 @@ export function closeNormalProfit(
}
} else {
try {
value = money(t.amount);
if (units(value) !== 0n)
value = money(
t.amount,
key === 'incomeCorrections' && !!economic,
);
if (BigInt(value.numerator) !== 0n)
reason =
'Montante conhecido,mas tratamento específico ainda por corresponder;conservado sem aplicação presumida.';
else
@@ -304,23 +314,24 @@ export function closeNormalProfit(
...(costs?.effects ?? []),
...(economic?.effects ?? []),
].filter((e) => e.topic === key);
const mappedAmount = addFractions(
mapped.flatMap((e) => (e.amount ? [e.amount] : [])),
);
const covered =
(schedules || costs || economic) &&
mapped.length > 0 &&
mapped.every((e) => e.effect !== null) &&
value !== null &&
t.state === 'known' &&
units(
addFractions(
mapped.flatMap((e) => (e.amount ? [e.amount] : [])),
),
) === units(value);
(economic && key === 'incomeCorrections'
? creditCompare(mappedAmount, value) === 0n
: units(mappedAmount) === units(value));
if (covered)
reason =
'Montante reconciliado com as regras do mapa complementar; efeito fiscal discriminado por linha.';
if (
value === null ||
(units(value) !== 0n && !covered) ||
(BigInt(value.numerator) !== 0n && !covered) ||
(t.state === 'absent' && !!t.amount.trim()) ||
(mapped.length > 0 && !covered)
)
@@ -0,0 +1,36 @@
<?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 IrpcEconomicFractionDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_exact_economic_inclusion_uses_the_existing_private_draft_writer(): void
{
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('proactive_investment_20261027_test', $target->name);
$this->assertSame(55441, (int) $target->port);
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
$this->actingAs(User::findOrFail(1021));
$d = json_decode(file_get_contents(base_path('outputs/irpc-economic-fraction-20261110/fixture.json')), true, flags: JSON_THROW_ON_ERROR);
$personal = ['person_type' => $d['person_type']];
unset($d['person_type']);
$id = $this->postJson('/api/v1/simulation-drafts', ['personal_snapshot' => $personal, 'economic_snapshot' => $d], ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json('data.id');
$back = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk()->json('data.economic_snapshot');
$this->assertSame($d['modelo_a_snapshot'], $back['modelo_a_snapshot']);
$this->assertSame($d['business_plan_snapshot'], $back['business_plan_snapshot']);
$b = json_decode($back['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR)['irpcNormal'][$d['context_opportunity']];
$this->assertSame('IRPC-economic-inclusion/1', $b['result']['model']['economicInclusions']['version']);
$this->assertSame(['numerator' => '56003', 'denominator' => '500'], $b['result']['model']['economicInclusions']['net']);
$this->assertSame(['numerator' => '4512006', 'denominator' => '3125'], $b['result']['model']['colecta']);
$this->assertNotEmpty($b['history']);
file_put_contents(base_path('outputs/irpc-economic-fraction-20261110/recovered.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
}
}
@@ -0,0 +1,36 @@
<?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 IrpcPtMzGovernmentDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_government_pt_mz_credit_uses_the_existing_private_draft_writer(): void
{
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('proactive_investment_20261027_test', $target->name);
$this->assertSame(55441, (int) $target->port);
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
$this->actingAs(User::findOrFail(1021));
$d = json_decode(file_get_contents(base_path('outputs/irpc-pt-mz-government-20261110/fixture.json')), true, flags: JSON_THROW_ON_ERROR);
$personal = ['person_type' => $d['person_type']];
unset($d['person_type']);
$id = $this->postJson('/api/v1/simulation-drafts', ['personal_snapshot' => $personal, 'economic_snapshot' => $d], ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json('data.id');
$back = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk()->json('data.economic_snapshot');
$this->assertSame($d['modelo_a_snapshot'], $back['modelo_a_snapshot']);
$this->assertSame($d['business_plan_snapshot'], $back['business_plan_snapshot']);
$b = json_decode($back['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR)['irpcAnnual'][$d['context_opportunity']];
$this->assertSame('ptMzInterest', $b['internationalCredits']['positions'][0]['treatyMethod']);
$this->assertSame(['numerator' => '0', 'denominator' => '1'], $b['result']['model']['internationalCredit']['positions'][0]['treatyLimit']);
$this->assertSame(['numerator' => '708', 'denominator' => '1'], $b['result']['model']['arithmeticBalance']);
$this->assertSame('state', $b['result']['model']['internationalCredit']['positions'][0]['treatyDerivation']['governmentExemption']['payer']);
file_put_contents(base_path('outputs/irpc-pt-mz-government-20261110/recovered.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
}
}
@@ -98,7 +98,7 @@ void test('economic inclusion does not add amounts already considered and can co
});
assert.deepEqual(m.taxableProfit, { numerator: '6642', denominator: '1' });
});
void test('economic inclusion rejects contradictory prior inclusion, duplicate origins, different taxpayers and unsupported fractional cents', () => {
void test('economic inclusion rejects contradictory prior inclusion, duplicate origins, different taxpayers and invalid underlying amounts', () => {
const c = caseInput();
const contradiction = structuredClone(c.inclusion);
contradiction.source.incomeIncluded = 'yes';
@@ -125,9 +125,9 @@ void test('economic inclusion rejects contradictory prior inclusion, duplicate o
other.source.subjectNuit = '987654321';
assert.throws(() => prepareEconomicInclusion(c.normal, other));
const partial = structuredClone(c.inclusion);
partial.source.taxAmount = '20.01';
partial.source.proofAmount = '20.01';
assert.throws(() => prepareEconomicInclusion(c.normal, partial), /cêntimo/);
partial.source.taxAmount = '-20.01';
partial.source.proofAmount = '-20.01';
assert.throws(() => prepareEconomicInclusion(c.normal, partial));
});
void test('economic inclusion keeps recovery, previous result and reports while making the old L8 origin stale', () => {
const c = caseInput(),
@@ -217,3 +217,69 @@ void test('association inclusion requires its specific evidence and does not inf
{ numerator: '112', denominator: '1' },
);
});
void test('fractional-cent economic inclusion keeps exact base and collection and does not accumulate on repeat', () => {
const c = caseInput();
c.inclusion.source.taxAmount = c.inclusion.source.proofAmount = '20.01';
const b = prepareEconomicInclusion(c.normal, c.inclusion),
m = calculateNormal(c.d, b);
assert.equal(b.profit.topics.incomeCorrections.amount, '112.006');
assert.deepEqual(m.economicInclusions.net, {
numerator: '56003',
denominator: '500',
});
assert.deepEqual(m.taxableBase, {
numerator: '2256003',
denominator: '500',
});
assert.deepEqual(m.colecta, { numerator: '4512006', denominator: '3125' });
assert.deepEqual(prepareEconomicInclusion(b, c.inclusion), b);
assert.match(
m.economicInclusions.effects[1].reason,
/IRPC-economic-inclusion-exact\/1/,
);
});
void test('fractional economic reconciliation cannot treat0.006 as zero or accept a truncated topic amount', () => {
const c = caseInput();
Object.assign(c.inclusion.source, {
income: '1',
incomeIncluded: 'yes',
taxAmount: '0.01',
proofAmount: '0.01',
attributionReference:
'SYNTHETIC tax0.01 actually corresponding to included income1',
});
c.inclusion.profitAlreadyIncluded = '1';
const b = prepareEconomicInclusion(c.normal, c.inclusion);
assert.equal(b.profit.topics.incomeCorrections.amount, '0.006');
assert.deepEqual(calculateNormal(c.d, b).economicInclusions.net, {
numerator: '3',
denominator: '500',
});
b.profit.topics.incomeCorrections.amount = '0.00';
assert.equal(calculateNormal(c.d, b).taxableProfit, null);
});
void test('documented prior rational additions and repeating fractions recover without decimal rounding', () => {
const c = caseInput();
c.inclusion.source.taxAmount = c.inclusion.source.proofAmount = '20.01';
c.inclusion.creditAlreadyIncluded = '1/3';
c.inclusion.source.grossUpIncluded = '1/3';
c.inclusion.source.grossUpReference =
'SYNTHETIC previous exact1/3 already added to fiscal base';
const b = prepareEconomicInclusion(c.normal, c.inclusion);
assert.equal(b.profit.topics.incomeCorrections.amount, '167509/1500');
const d = {
...c.d,
modelo_a_snapshot: saveNormalBook(
c.d,
recordNormal(c.d, b, '2026-11-10T18:10:00+02:00'),
),
},
back = normalBook(d);
assert.equal(normalCurrent(d, back), true);
assert.deepEqual(
calculateNormal(d, back).economicInclusions,
back.result.model.economicInclusions,
);
assert.match(normalHtml(d), /IRPC-economic-inclusion-exact\/1/);
});
@@ -0,0 +1,115 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { internationalCase } from './helpers/irpc-international-case.mjs';
import { newPtMzRoyaltyPreparation } from '../../resources/js/lib/irpc-international-credit.ts';
import {
calculateAnnual,
recordAnnual,
saveAnnualBook,
annualBook,
annualCurrent,
annualHtml,
} from '../../resources/js/lib/irpc-annual-settlement.ts';
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
function governmentCase(payer = 'state') {
const c = internationalCase();
Object.assign(c.b.internationalCredits.positions[0], {
country: 'PT',
qualificationCountry: 'PT',
incomeAmount: '40',
treaty: 'yes',
treatyMethod: 'ptMzInterest',
treatyReference:
'SYNTHETIC QA PT-MZ11(3)a and23(3)a, not a real entitlement assertion',
treatyPreparation: {
...newPtMzRoyaltyPreparation(),
gross: '60',
from: '2026-01-01',
to: '2026-12-31',
reference:
'SYNTHETIC residence, interest and nonlinked EP conditions documented',
residentMz: 'yes',
beneficialOwner: 'yes',
sourcePortugal: 'yes',
payerResidentPt: 'yes',
noLinkedEstablishment: 'yes',
armLength: 'yes',
capitalDefinition: 'yes',
governmentInterestExemption: 'yes',
governmentPayer: payer,
governmentPayerReference:
'SYNTHETIC Portuguese public payer identified by supported legal status',
},
});
return c;
}
void test('documented Portuguese state,subdivision and local public payers yield zero treaty ceiling,not a tax refund', () => {
for (const payer of ['state', 'subdivision', 'local']) {
const c = governmentCase(payer),
before = c.d.modelo_a_snapshot,
m = calculateAnnual(c.d, c.b),
p = m.internationalCredit.positions[0];
assert.deepEqual(m.pending, []);
assert.deepEqual(p.treatyLimit, { numerator: '0', denominator: '1' });
assert.deepEqual(p.eligible, { numerator: '0', denominator: '1' });
assert.deepEqual(p.aboveLimit, { numerator: '10', denominator: '1' });
assert.deepEqual(m.arithmeticBalance, {
numerator: '708',
denominator: '1',
});
assert.equal(p.treatyDerivation.rate, '0');
assert.equal(c.d.modelo_a_snapshot, before);
assert.equal(m.internationalCredit.carryCreated, false);
}
});
void test('government exemption cannot be inferred from a flag,name,unknown payer or undocumented classification', () => {
for (const patch of [
{ governmentPayer: '' },
{ governmentPayerReference: '' },
{ governmentInterestExemption: '' },
{ capitalDefinition: '' },
{ noLinkedEstablishment: '' },
]) {
const c = governmentCase();
Object.assign(
c.b.internationalCredits.positions[0].treatyPreparation,
patch,
);
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
}
});
void test('documented absence of governmental exemption keeps the accepted ordinary10percent path', () => {
const c = governmentCase();
c.b.internationalCredits.positions[0].treatyPreparation.governmentInterestExemption =
'no';
const p = calculateAnnual(c.d, c.b).internationalCredit.positions[0];
assert.deepEqual(p.treatyLimit, { numerator: '6', denominator: '1' });
assert.equal(p.treatyDerivation.governmentExemption, undefined);
});
void test('government exemption and noncreditable paid tax recover into HTML and dossier without rewriting L6', () => {
const c = governmentCase(),
b = recordAnnual(c.d, c.b, '2026-11-10T18:00:00+02:00'),
d = JSON.parse(
JSON.stringify({
...c.d,
modelo_a_snapshot: saveAnnualBook(c.d, b),
}),
),
back = annualBook(d);
assert.equal(annualCurrent(d, back), true);
assert.deepEqual(
calculateAnnual(d, back).internationalCredit,
back.result.model.internationalCredit,
);
assert.match(annualHtml(d), /IRPC-PT-MZ-government-interest\/1/);
assert.match(annualHtml(d), /SYNTHETIC Portuguese public payer identified/);
assert.match(
JSON.stringify(
createProjectDossierReport(d, {
selectedSections: new Set(['finance']),
generatedAt: '2026-11-10T18:00:00+02:00',
}),
),
/IRPC-PT-MZ-government-interest\/1/,
);
});
+4 -2
View File
@@ -6,7 +6,8 @@ import { normalBook, calculateNormal, recordNormal, saveNormalBook, normalCurren
import { prepareEconomicInclusion } from '../resources/js/lib/irpc-normal-economic.ts';
import { createProjectDossierReport } from '../resources/js/lib/project-dossier-report.ts';
const special = process.argv.includes('special');
const out = new URL(special ? '../outputs/irpc-economic-special-inclusion-20261110/' : '../outputs/irpc-economic-inclusion-20261110/', import.meta.url);
const exact = process.argv.includes('fraction');
const out = new URL(exact ? '../outputs/irpc-economic-fraction-20261110/' : special ? '../outputs/irpc-economic-special-inclusion-20261110/' : '../outputs/irpc-economic-inclusion-20261110/', import.meta.url);
fs.mkdirSync(out, { recursive: true });
if (process.argv[2] === 'recovered') {
const d = JSON.parse(fs.readFileSync(new URL('recovered.json', out), 'utf8'));
@@ -29,11 +30,12 @@ if (process.argv[2] === 'recovered') {
const c=economicCase(), source=structuredClone(c.b.economicCredits.rows[0]);
Object.assign(source,{incomeIncluded:'no',incomeReference:'SYNTHETIC not yet included',grossUpIncluded:'0',grossUpReference:'SYNTHETIC no gross-up yet'});
if (special) Object.assign(source,{kind:'liquidation',income:'150',liquidation:{attributed:'200',paidInCapital:'100',reference:'SYNTHETIC QA capital100/gain50 in total taxable150; exact reconciliation'}});
if(exact) {source.taxAmount=source.proofAmount='20.01';source.attributionReference='SYNTHETIC underlying tax20.01 corresponds to income100';}
const b=recordNormal(c.d,prepareEconomicInclusion(normalBook(c.d),{source,profitAlreadyIncluded:'0',creditAlreadyIncluded:'0',reference:'SYNTHETIC QA explicit reconciliation before this map'}),'2026-11-10T15:00:00+02:00');
const d={...c.d,modelo_a_snapshot:saveNormalBook(c.d,b)};
let annual=reuseAnnualAssessment(d,c.b);
annual.internationalCredits.positions.forEach(p=>{p.assessmentKey=annual.assessmentKey;p.incomeReference='SYNTHETIC reconfirmed foreign component in new L6';});
Object.assign(annual.economicCredits.rows[0],source,{assessmentKey:annual.assessmentKey,incomeIncluded:'yes',incomeReference:'SYNTHETIC reconciled inclusion map in new L6',grossUpIncluded:'12',grossUpReference:'SYNTHETIC reconciled nominal12 in new L6',nationalMethod:'proportional',nationalBasisReference:'SYNTHETIC same112 component in new base4512'});
Object.assign(annual.economicCredits.rows[0],source,{assessmentKey:annual.assessmentKey,incomeIncluded:'yes',incomeReference:'SYNTHETIC reconciled inclusion map in new L6',grossUpIncluded:exact?'12.006':'12',grossUpReference:'SYNTHETIC reconciled nominal12 in new L6',nationalMethod:'proportional',nationalBasisReference:'SYNTHETIC same112 component in new base4512'});
assert.deepEqual(calculateAnnual(d,annual).pending,[]);
annual=recordAnnual(d,annual,'2026-11-10T15:01:00+02:00');
d.modelo_a_snapshot=saveAnnualBook(d,annual);
+1 -1
View File
@@ -18,7 +18,7 @@ $identity = $pdo->query('select current_database() as name,inet_server_port() as
if ($identity['name'] !== 'proactive_investment_20261027_test' || (int) $identity['port'] !== 55441) {
throw new RuntimeException('Isolated QA identity required.');
}
$case = ['quota' => 'IrpcEconomicQuotaDraftTest', 'inclusion' => 'IrpcEconomicInclusionDraftTest', 'liquidation' => 'IrpcEconomicLiquidationDraftTest', 'association' => 'IrpcEconomicAssociationDraftTest', 'special-inclusion' => 'IrpcEconomicSpecialInclusionDraftTest', 'pt-royalty' => 'IrpcPtMzCreditDraftTest', 'pt-capital' => 'IrpcPtMzCapitalDraftTest'][$argv[2] ?? ''] ?? throw new RuntimeException('Choose an explicit supported QA case.');
$case = ['quota' => 'IrpcEconomicQuotaDraftTest', 'inclusion' => 'IrpcEconomicInclusionDraftTest', 'liquidation' => 'IrpcEconomicLiquidationDraftTest', 'association' => 'IrpcEconomicAssociationDraftTest', 'special-inclusion' => 'IrpcEconomicSpecialInclusionDraftTest', 'pt-royalty' => 'IrpcPtMzCreditDraftTest', 'pt-capital' => 'IrpcPtMzCapitalDraftTest', 'pt-government' => 'IrpcPtMzGovernmentDraftTest', 'economic-fraction' => 'IrpcEconomicFractionDraftTest'][$argv[2] ?? ''] ?? throw new RuntimeException('Choose an explicit supported QA case.');
$argv = ['phpunit', '-c', 'phpunit.investmoz.xml', '--do-not-cache-result', 'tests/Feature/'.$case.'.php'];
$argc = count($argv);
$_SERVER['argv'] = $argv;
+6 -4
View File
@@ -3,22 +3,24 @@ import fs from 'node:fs';
import { internationalCase } from '../tests/frontend/helpers/irpc-international-case.mjs';
import { recordAnnual, saveAnnualBook, annualBook, calculateAnnual, annualHtml } from '../resources/js/lib/irpc-annual-settlement.ts';
import assert from 'node:assert/strict';
const capital = ['dividend','interest'].find(k=>process.argv.includes(k));
const out = new URL(capital ? '../outputs/irpc-pt-mz-'+capital+'-20261110/' : '../outputs/irpc-pt-mz-credit-20261110/', import.meta.url);
const government = process.argv.includes('government');
const capital = government ? 'interest' : ['dividend','interest'].find(k=>process.argv.includes(k));
const out = new URL(government ? '../outputs/irpc-pt-mz-government-20261110/' : capital ? '../outputs/irpc-pt-mz-'+capital+'-20261110/' : '../outputs/irpc-pt-mz-credit-20261110/', import.meta.url);
fs.mkdirSync(out, { recursive: true });
if (process.argv[2] === 'recovered') {
const d = JSON.parse(fs.readFileSync(new URL('recovered.json', out), 'utf8'));
const b = annualBook(d), m = b.result.model;
assert.deepEqual(calculateAnnual(d, b).internationalCredit, m.internationalCredit);
const html = annualHtml(d);assert.match(html, /IRPC-PT-MZ-(royalty|capital)-credit\/1/);
const html = annualHtml(d);assert.match(html, /IRPC-PT-MZ-(royalty-credit|capital-credit|government-interest)\/1/);
const report=createProjectDossierReport(d,{selectedSections:new Set(['finance']),generatedAt:'2026-11-10T17:10:00+02:00'});
assert.match(JSON.stringify(report),/IRPC-PT-MZ-(royalty|capital)-credit\/1/);
assert.match(JSON.stringify(report),/IRPC-PT-MZ-(royalty-credit|capital-credit|government-interest)\/1/);
fs.writeFileSync(new URL('report.html', out), html);
fs.writeFileSync(new URL('verification.json', out), JSON.stringify({ synthetic: true, apiRecovered: true, dossierReport: true, currentRecalculation: true, html: true, quota: m.internationalCredit.positions[0].treatyLimit, balance: m.arithmeticBalance, activeChanged: false }, null, 2));
} else {
const c = internationalCase();
Object.assign(c.b.internationalCredits.positions[0],{country:'PT',qualificationCountry:'PT',incomeAmount:'40',treaty:'yes',treatyMethod:'ptMzRoyalty',treatyReference:'SYNTHETIC QA qualified PT-MZ12/23(3)a ordinary royalties',treatyPreparation:{gross:'60',from:'2026-01-01',to:'2026-12-31',reference:'SYNTHETIC QA residence certificate and royalty agreement covering conditions',residentMz:'yes',beneficialOwner:'yes',sourcePortugal:'yes',payerResidentPt:'yes',noLinkedEstablishment:'yes',armLength:'yes',royaltyDefinition:'yes',nonDigital:'yes'}});
if(capital) {const p=c.b.internationalCredits.positions[0];p.treatyMethod=capital==='dividend'?'ptMzDividend':'ptMzInterest';p.treatyReference='SYNTHETIC QA explicit PT-MZ article'+(capital==='dividend'?'10 updated2009':'11 ordinary non-governmental')+'/23(3)a qualification';p.treatyPreparation.reference='SYNTHETIC QA residence certificate and '+capital+' contract, source and conditions documented';Object.assign(p.treatyPreparation,{capitalDefinition:'yes',governmentInterestExemption:'no'});}
if(government){const p=c.b.internationalCredits.positions[0];p.treatyReference='SYNTHETIC QA PT-MZ11(3)a and23(3)a';Object.assign(p.treatyPreparation,{governmentInterestExemption:'yes',governmentPayer:'state',governmentPayerReference:'SYNTHETIC Portuguese state is actual payer of documented interest'});}
const b = recordAnnual(c.d, c.b, '2026-11-10T14:00:00+02:00');
fs.writeFileSync(new URL('fixture.json', out), JSON.stringify({ ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, b) }));
}