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
|
||||
|
||||
## 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}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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/,
|
||||
);
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user