feat: support public-payer treaty exemption and exact economic adjustments
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -1,5 +1,19 @@
|
|||||||
# Rastreabilidade, utilização e formação
|
# 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
|
## 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.
|
**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
|
## 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.
|
**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
|
## 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.
|
**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
|
deste mapa; zero deve ser indicado expressamente. A prévia
|
||||||
propõe apenas a diferença e conserva o resultado anterior.
|
propõe apenas a diferença e conserva o resultado anterior.
|
||||||
Depois de confirmar, calcule e guarde L6 pelo percurso
|
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>
|
</p>
|
||||||
<label className="block">
|
<label className="block">
|
||||||
Origem dos lucros
|
Origem dos lucros
|
||||||
|
|||||||
@@ -52,17 +52,17 @@ export function SimulationIrpcPtCdt({
|
|||||||
if (p.treatyMethod === 'ptMzInterest')
|
if (p.treatyMethod === 'ptMzInterest')
|
||||||
facts.push([
|
facts.push([
|
||||||
'governmentInterestExemption',
|
'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 (
|
return (
|
||||||
<fieldset className="space-y-2 rounded border p-3">
|
<fieldset className="space-y-2 rounded border p-3">
|
||||||
<legend>Portugal — correspondência CDT</legend>
|
<legend>Portugal — correspondência CDT</legend>
|
||||||
<p>
|
<p>
|
||||||
Confirme rendimento bruto em MZN, residência convencional,
|
Confirme rendimento bruto em MZN, residência convencional,
|
||||||
natureza e período coberto pelos documentos. A taxa de 10%
|
natureza e período coberto pelos documentos. O limite
|
||||||
limita o imposto estrangeiro elegível; não altera a parcela
|
convencional do caso qualificado restringe o imposto estrangeiro
|
||||||
líquida nacional nem prova pagamento. O limite manual anterior
|
elegível; não altera a parcela líquida nacional nem prova
|
||||||
fica conservado.
|
pagamento. O limite manual anterior fica conservado.
|
||||||
</p>
|
</p>
|
||||||
{(
|
{(
|
||||||
[
|
[
|
||||||
@@ -84,6 +84,63 @@ export function SimulationIrpcPtCdt({
|
|||||||
/>
|
/>
|
||||||
</label>
|
</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]) => (
|
{facts.map(([key, label]) => (
|
||||||
<label key={key} className="block">
|
<label key={key} className="block">
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export type InternationalTaxProof = AnnualPpc & {
|
|||||||
exerciseReference: string;
|
exerciseReference: string;
|
||||||
};
|
};
|
||||||
export type PtMzRoyaltyPreparation = {
|
export type PtMzRoyaltyPreparation = {
|
||||||
|
governmentPayer?: '' | 'state' | 'subdivision' | 'local';
|
||||||
|
governmentPayerReference?: string;
|
||||||
capitalDefinition?: '' | 'yes' | 'no';
|
capitalDefinition?: '' | 'yes' | 'no';
|
||||||
governmentInterestExemption?: '' | 'yes' | 'no';
|
governmentInterestExemption?: '' | 'yes' | 'no';
|
||||||
gross: string;
|
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) ||
|
!evidenceValid(p.previousUse) ||
|
||||||
!Array.isArray(p.payments)
|
!Array.isArray(p.payments)
|
||||||
)
|
)
|
||||||
@@ -408,13 +417,25 @@ export function ptMzTreatyLimit(p: InternationalPosition) {
|
|||||||
throw Error(
|
throw Error(
|
||||||
'Definição convencional de dividendos/juros por documentar.',
|
'Definição convencional de dividendos/juros por documentar.',
|
||||||
);
|
);
|
||||||
if (
|
let governmentExemption: {
|
||||||
p.treatyMethod === 'ptMzInterest' &&
|
payer: 'state' | 'subdivision' | 'local';
|
||||||
q.governmentInterestExemption !== 'no'
|
reference: string;
|
||||||
)
|
} | null = null;
|
||||||
throw Error(
|
if (p.treatyMethod === 'ptMzInterest') {
|
||||||
'Juros abrangidos ou por corresponder à isenção governamental11(3) exigem tratamento próprio; não presumir limite ordinário.',
|
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 (
|
if (
|
||||||
!validDate(q.from) ||
|
!validDate(q.from) ||
|
||||||
!validDate(q.to) ||
|
!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.',
|
'Rendimento bruto inferior à parcela líquida incluída; reconcilie custos/perdas e base.',
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
version:
|
version: governmentExemption
|
||||||
p.treatyMethod === 'ptMzRoyalty'
|
? 'IRPC-PT-MZ-government-interest/1'
|
||||||
? 'IRPC-PT-MZ-royalty-credit/1'
|
: p.treatyMethod === 'ptMzRoyalty'
|
||||||
: 'IRPC-PT-MZ-capital-credit/1',
|
? 'IRPC-PT-MZ-royalty-credit/1'
|
||||||
|
: 'IRPC-PT-MZ-capital-credit/1',
|
||||||
...(p.treatyMethod !== 'ptMzRoyalty' ? { nature: p.treatyMethod } : {}),
|
...(p.treatyMethod !== 'ptMzRoyalty' ? { nature: p.treatyMethod } : {}),
|
||||||
gross,
|
gross,
|
||||||
rate: '10',
|
...(governmentExemption ? { governmentExemption } : {}),
|
||||||
limit: fraction(
|
rate: governmentExemption ? '0' : '10',
|
||||||
BigInt(gross.numerator),
|
limit: governmentExemption
|
||||||
BigInt(gross.denominator) * 10n,
|
? zero()
|
||||||
),
|
: fraction(
|
||||||
|
BigInt(gross.numerator),
|
||||||
|
BigInt(gross.denominator) * 10n,
|
||||||
|
),
|
||||||
from: q.from,
|
from: q.from,
|
||||||
to: q.to,
|
to: q.to,
|
||||||
reference: q.reference,
|
reference: governmentExemption
|
||||||
source:
|
? q.reference +
|
||||||
p.treatyMethod === 'ptMzRoyalty'
|
'; devedor ' +
|
||||||
? '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.'
|
governmentExemption.payer +
|
||||||
: 'CDT PT–MZ, RAR36/1992 art23(3)a,PDF21; CIRPC65(2). ' +
|
'; ' +
|
||||||
(p.treatyMethod === 'ptMzDividend'
|
governmentExemption.reference
|
||||||
? 'Dividendos10(2–4),PDF10; taxa10% actualizada pelo Protocolo RAR36/2009 art6,PDF2; IRPC/art2 actualizado pelo art1.'
|
: q.reference,
|
||||||
: p.treatyMethod === 'ptMzInterest'
|
source: governmentExemption
|
||||||
? 'Juros11(2–7),PDF11–12, excluída a isenção11(3); IRPC/art2 actualizado pelo Protocolo RAR36/2009 art1.'
|
? '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.'
|
||||||
: '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 === 'ptMzRoyalty'
|
||||||
(p.treatyMethod === 'ptMzDividend'
|
? '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.'
|
||||||
? ' Sem EP/instalação fixa ligada.'
|
: 'CDT PT–MZ, RAR36/1992 art23(3)a,PDF21; CIRPC65(2). ' +
|
||||||
: ' Sem EP/instalação fixa ligada ou excesso convencional por relações especiais.') +
|
(p.treatyMethod === 'ptMzDividend'
|
||||||
' Não confundir com retenção L5 de devedor moçambicano; sem isenção ou tax sparing presumidos.',
|
? '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(
|
export function internationalPosition(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from './irpc-international-credit.ts';
|
} from './irpc-international-credit.ts';
|
||||||
import {
|
import {
|
||||||
addFractions,
|
addFractions,
|
||||||
|
fractionPresentation,
|
||||||
subtractFractions,
|
subtractFractions,
|
||||||
fraction,
|
fraction,
|
||||||
type FiscalFraction,
|
type FiscalFraction,
|
||||||
@@ -48,20 +49,10 @@ export function validateEconomicInclusions(input: EconomicInclusions) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
export function economicInclusionMoney(value: FiscalFraction): string {
|
export function economicInclusionMoney(value: FiscalFraction): string {
|
||||||
const n = BigInt(value.numerator) * 100n,
|
const presentation = fractionPresentation(value);
|
||||||
d = BigInt(value.denominator);
|
return presentation.approximate
|
||||||
if (n % d !== 0n)
|
? value.numerator + '/' + value.denominator
|
||||||
throw Error(
|
: presentation.decimal;
|
||||||
'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')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
export function calculateEconomicInclusions(
|
export function calculateEconomicInclusions(
|
||||||
input: EconomicInclusions,
|
input: EconomicInclusions,
|
||||||
@@ -148,7 +139,7 @@ export function calculateEconomicInclusions(
|
|||||||
const targets = [creditMoney(r.income), nominal];
|
const targets = [creditMoney(r.income), nominal];
|
||||||
const included = [
|
const included = [
|
||||||
creditMoney(row.profitAlreadyIncluded),
|
creditMoney(row.profitAlreadyIncluded),
|
||||||
creditMoney(row.creditAlreadyIncluded),
|
creditLimit(row.creditAlreadyIncluded),
|
||||||
];
|
];
|
||||||
if (
|
if (
|
||||||
r.incomeIncluded === 'yes' &&
|
r.incomeIncluded === 'yes' &&
|
||||||
@@ -178,7 +169,7 @@ export function calculateEconomicInclusions(
|
|||||||
BigInt(effect.denominator),
|
BigInt(effect.denominator),
|
||||||
),
|
),
|
||||||
effect,
|
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) {
|
} catch (e) {
|
||||||
@@ -241,7 +232,11 @@ export function prepareEconomicInclusion(
|
|||||||
'Reconcilie as outras correcções aos proveitos antes de substituir parcelas.',
|
'Reconcilie as outras correcções aos proveitos antes de substituir parcelas.',
|
||||||
);
|
);
|
||||||
const current =
|
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(
|
const remaining = subtractFractions(
|
||||||
current,
|
current,
|
||||||
addFractions(
|
addFractions(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { creditLimit, creditCompare } from './irpc-international-credit.ts';
|
||||||
import type { NormalDepreciationModel } from './irpc-normal-depreciation.ts';
|
import type { NormalDepreciationModel } from './irpc-normal-depreciation.ts';
|
||||||
import type { NormalCostsModel } from './irpc-normal-costs.ts';
|
import type { NormalCostsModel } from './irpc-normal-costs.ts';
|
||||||
import type { SchedulesModel } from './irpc-normal-schedules.ts';
|
import type { SchedulesModel } from './irpc-normal-schedules.ts';
|
||||||
@@ -126,7 +127,13 @@ export type NormalProfitModel = {
|
|||||||
source: string;
|
source: string;
|
||||||
};
|
};
|
||||||
const zero = () => fraction(0n, 1n);
|
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');
|
if (!/^-?\d+(?:\.\d{1,2})?$/.test(s)) throw Error('Montante inválido');
|
||||||
const v = decimalFraction(s.startsWith('-') ? s.slice(1) : s);
|
const v = decimalFraction(s.startsWith('-') ? s.slice(1) : s);
|
||||||
return s.startsWith('-')
|
return s.startsWith('-')
|
||||||
@@ -288,8 +295,11 @@ export function closeNormalProfit(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
value = money(t.amount);
|
value = money(
|
||||||
if (units(value) !== 0n)
|
t.amount,
|
||||||
|
key === 'incomeCorrections' && !!economic,
|
||||||
|
);
|
||||||
|
if (BigInt(value.numerator) !== 0n)
|
||||||
reason =
|
reason =
|
||||||
'Montante conhecido,mas tratamento específico ainda por corresponder;conservado sem aplicação presumida.';
|
'Montante conhecido,mas tratamento específico ainda por corresponder;conservado sem aplicação presumida.';
|
||||||
else
|
else
|
||||||
@@ -304,23 +314,24 @@ export function closeNormalProfit(
|
|||||||
...(costs?.effects ?? []),
|
...(costs?.effects ?? []),
|
||||||
...(economic?.effects ?? []),
|
...(economic?.effects ?? []),
|
||||||
].filter((e) => e.topic === key);
|
].filter((e) => e.topic === key);
|
||||||
|
const mappedAmount = addFractions(
|
||||||
|
mapped.flatMap((e) => (e.amount ? [e.amount] : [])),
|
||||||
|
);
|
||||||
const covered =
|
const covered =
|
||||||
(schedules || costs || economic) &&
|
(schedules || costs || economic) &&
|
||||||
mapped.length > 0 &&
|
mapped.length > 0 &&
|
||||||
mapped.every((e) => e.effect !== null) &&
|
mapped.every((e) => e.effect !== null) &&
|
||||||
value !== null &&
|
value !== null &&
|
||||||
t.state === 'known' &&
|
t.state === 'known' &&
|
||||||
units(
|
(economic && key === 'incomeCorrections'
|
||||||
addFractions(
|
? creditCompare(mappedAmount, value) === 0n
|
||||||
mapped.flatMap((e) => (e.amount ? [e.amount] : [])),
|
: units(mappedAmount) === units(value));
|
||||||
),
|
|
||||||
) === units(value);
|
|
||||||
if (covered)
|
if (covered)
|
||||||
reason =
|
reason =
|
||||||
'Montante reconciliado com as regras do mapa complementar; efeito fiscal discriminado por linha.';
|
'Montante reconciliado com as regras do mapa complementar; efeito fiscal discriminado por linha.';
|
||||||
if (
|
if (
|
||||||
value === null ||
|
value === null ||
|
||||||
(units(value) !== 0n && !covered) ||
|
(BigInt(value.numerator) !== 0n && !covered) ||
|
||||||
(t.state === 'absent' && !!t.amount.trim()) ||
|
(t.state === 'absent' && !!t.amount.trim()) ||
|
||||||
(mapped.length > 0 && !covered)
|
(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' });
|
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 c = caseInput();
|
||||||
const contradiction = structuredClone(c.inclusion);
|
const contradiction = structuredClone(c.inclusion);
|
||||||
contradiction.source.incomeIncluded = 'yes';
|
contradiction.source.incomeIncluded = 'yes';
|
||||||
@@ -125,9 +125,9 @@ void test('economic inclusion rejects contradictory prior inclusion, duplicate o
|
|||||||
other.source.subjectNuit = '987654321';
|
other.source.subjectNuit = '987654321';
|
||||||
assert.throws(() => prepareEconomicInclusion(c.normal, other));
|
assert.throws(() => prepareEconomicInclusion(c.normal, other));
|
||||||
const partial = structuredClone(c.inclusion);
|
const partial = structuredClone(c.inclusion);
|
||||||
partial.source.taxAmount = '20.01';
|
partial.source.taxAmount = '-20.01';
|
||||||
partial.source.proofAmount = '20.01';
|
partial.source.proofAmount = '-20.01';
|
||||||
assert.throws(() => prepareEconomicInclusion(c.normal, partial), /cêntimo/);
|
assert.throws(() => prepareEconomicInclusion(c.normal, partial));
|
||||||
});
|
});
|
||||||
void test('economic inclusion keeps recovery, previous result and reports while making the old L8 origin stale', () => {
|
void test('economic inclusion keeps recovery, previous result and reports while making the old L8 origin stale', () => {
|
||||||
const c = caseInput(),
|
const c = caseInput(),
|
||||||
@@ -217,3 +217,69 @@ void test('association inclusion requires its specific evidence and does not inf
|
|||||||
{ numerator: '112', denominator: '1' },
|
{ 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/,
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -6,7 +6,8 @@ import { normalBook, calculateNormal, recordNormal, saveNormalBook, normalCurren
|
|||||||
import { prepareEconomicInclusion } from '../resources/js/lib/irpc-normal-economic.ts';
|
import { prepareEconomicInclusion } from '../resources/js/lib/irpc-normal-economic.ts';
|
||||||
import { createProjectDossierReport } from '../resources/js/lib/project-dossier-report.ts';
|
import { createProjectDossierReport } from '../resources/js/lib/project-dossier-report.ts';
|
||||||
const special = process.argv.includes('special');
|
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 });
|
fs.mkdirSync(out, { recursive: true });
|
||||||
if (process.argv[2] === 'recovered') {
|
if (process.argv[2] === 'recovered') {
|
||||||
const d = JSON.parse(fs.readFileSync(new URL('recovered.json', out), 'utf8'));
|
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]);
|
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'});
|
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 (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 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)};
|
const d={...c.d,modelo_a_snapshot:saveNormalBook(c.d,b)};
|
||||||
let annual=reuseAnnualAssessment(d,c.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';});
|
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,[]);
|
assert.deepEqual(calculateAnnual(d,annual).pending,[]);
|
||||||
annual=recordAnnual(d,annual,'2026-11-10T15:01:00+02:00');
|
annual=recordAnnual(d,annual,'2026-11-10T15:01:00+02:00');
|
||||||
d.modelo_a_snapshot=saveAnnualBook(d,annual);
|
d.modelo_a_snapshot=saveAnnualBook(d,annual);
|
||||||
|
|||||||
@@ -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) {
|
if ($identity['name'] !== 'proactive_investment_20261027_test' || (int) $identity['port'] !== 55441) {
|
||||||
throw new RuntimeException('Isolated QA identity required.');
|
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'];
|
$argv = ['phpunit', '-c', 'phpunit.investmoz.xml', '--do-not-cache-result', 'tests/Feature/'.$case.'.php'];
|
||||||
$argc = count($argv);
|
$argc = count($argv);
|
||||||
$_SERVER['argv'] = $argv;
|
$_SERVER['argv'] = $argv;
|
||||||
|
|||||||
@@ -3,22 +3,24 @@ import fs from 'node:fs';
|
|||||||
import { internationalCase } from '../tests/frontend/helpers/irpc-international-case.mjs';
|
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 { recordAnnual, saveAnnualBook, annualBook, calculateAnnual, annualHtml } from '../resources/js/lib/irpc-annual-settlement.ts';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
const capital = ['dividend','interest'].find(k=>process.argv.includes(k));
|
const government = process.argv.includes('government');
|
||||||
const out = new URL(capital ? '../outputs/irpc-pt-mz-'+capital+'-20261110/' : '../outputs/irpc-pt-mz-credit-20261110/', import.meta.url);
|
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 });
|
fs.mkdirSync(out, { recursive: true });
|
||||||
if (process.argv[2] === 'recovered') {
|
if (process.argv[2] === 'recovered') {
|
||||||
const d = JSON.parse(fs.readFileSync(new URL('recovered.json', out), 'utf8'));
|
const d = JSON.parse(fs.readFileSync(new URL('recovered.json', out), 'utf8'));
|
||||||
const b = annualBook(d), m = b.result.model;
|
const b = annualBook(d), m = b.result.model;
|
||||||
assert.deepEqual(calculateAnnual(d, b).internationalCredit, m.internationalCredit);
|
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'});
|
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('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));
|
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 {
|
} else {
|
||||||
const c = internationalCase();
|
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'}});
|
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(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');
|
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) }));
|
fs.writeFileSync(new URL('fixture.json', out), JSON.stringify({ ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, b) }));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user