785 lines
31 KiB
TypeScript
785 lines
31 KiB
TypeScript
import type { AnnualPpc } from './irpc-annual-settlement.ts';
|
||
import {
|
||
validateProofParcel,
|
||
requireProofParcel,
|
||
} from './fiscal-proof-parcel.ts';
|
||
import { countryIsoCodes } from './country-iso-codes.ts';
|
||
import {
|
||
addFractions,
|
||
subtractFractions,
|
||
decimalFraction,
|
||
fraction,
|
||
type FiscalFraction,
|
||
} from './fiscal-rational.ts';
|
||
|
||
export const internationalCreditVersion = 'IRPC-international-credit/1';
|
||
export const internationalCreditSource =
|
||
'CIRPC art. 65(1–2), Lei 34/2007, PDF18/p.768-(72); ordem face ao PEC: art.66 e RIRPC21. Recorte: próprio exercício 2026, residente geral, ano civil, valores comprovados em MZN. Menor entre imposto sobre o rendimento pago no estrangeiro, fracção nacional documentada e limite da convenção quando aplicável; depois utilizações anteriores comprovadas e limite da colecta. Não determina a fracção nacional, a aplicabilidade da convenção ou a conversão cambial por presunção. Sem transporte, reembolso do excedente ou consumo de saldos oficiais.';
|
||
export type CreditState = '' | 'known' | 'absent';
|
||
export type CreditEvidence = {
|
||
state: CreditState;
|
||
amount: string;
|
||
reference: string;
|
||
};
|
||
export type InternationalTaxProof = AnnualPpc & {
|
||
incomeTax: '' | 'yes' | 'no';
|
||
taxReference: string;
|
||
exerciseReference: string;
|
||
};
|
||
export type PtMzRoyaltyPreparation = {
|
||
governmentBasis?: '' | 'payer' | 'agreement';
|
||
institutionRecipient?: '' | 'yes' | 'no';
|
||
financingGranted?: '' | 'yes' | 'no';
|
||
bilateralAgreement?: '' | 'yes' | 'no';
|
||
institutionReference?: string;
|
||
financingReference?: string;
|
||
agreementReference?: string;
|
||
|
||
governmentPayer?: '' | 'state' | 'subdivision' | 'local';
|
||
governmentPayerReference?: string;
|
||
capitalDefinition?: '' | 'yes' | 'no';
|
||
governmentInterestExemption?: '' | 'yes' | 'no';
|
||
gross: string;
|
||
from: string;
|
||
to: string;
|
||
reference: string;
|
||
residentMz: '' | 'yes' | 'no';
|
||
beneficialOwner: '' | 'yes' | 'no';
|
||
sourcePortugal: '' | 'yes' | 'no';
|
||
payerResidentPt: '' | 'yes' | 'no';
|
||
noLinkedEstablishment: '' | 'yes' | 'no';
|
||
armLength: '' | 'yes' | 'no';
|
||
royaltyDefinition: '' | 'yes' | 'no';
|
||
nonDigital: '' | 'yes' | 'no';
|
||
};
|
||
export const newPtMzRoyaltyPreparation = (): PtMzRoyaltyPreparation => ({
|
||
gross: '',
|
||
from: '',
|
||
to: '',
|
||
reference: '',
|
||
residentMz: '',
|
||
beneficialOwner: '',
|
||
sourcePortugal: '',
|
||
payerResidentPt: '',
|
||
noLinkedEstablishment: '',
|
||
armLength: '',
|
||
royaltyDefinition: '',
|
||
nonDigital: '',
|
||
});
|
||
export type InternationalPosition = {
|
||
id: string;
|
||
country: string;
|
||
subjectNuit: string;
|
||
year: string;
|
||
assessmentKey: string;
|
||
qualificationCountry: string;
|
||
incomeAmount: string;
|
||
incomeIncluded: '' | 'yes' | 'no';
|
||
incomeReference: string;
|
||
nationalLimit: string;
|
||
nationalLimitReference: string;
|
||
nationalMethod?: 'documented' | 'proportional';
|
||
nationalBasisReference?: string;
|
||
treatyMethod?:
|
||
| 'documented'
|
||
| 'ptMzRoyalty'
|
||
| 'ptMzDividend'
|
||
| 'ptMzInterest';
|
||
treatyPreparation?: PtMzRoyaltyPreparation;
|
||
treaty: '' | 'yes' | 'no';
|
||
treatyLimit: string;
|
||
treatyReference: string;
|
||
previousUse: CreditEvidence;
|
||
payments: InternationalTaxProof[];
|
||
};
|
||
export type InternationalCredits = {
|
||
version: 1;
|
||
state: CreditState;
|
||
reference: string;
|
||
positions: InternationalPosition[];
|
||
economic: CreditEvidence;
|
||
benefits: CreditEvidence;
|
||
};
|
||
const emptyEvidence = (): CreditEvidence => ({
|
||
state: '',
|
||
amount: '',
|
||
reference: '',
|
||
});
|
||
export const newInternationalCredits = (): InternationalCredits => ({
|
||
version: 1,
|
||
state: '',
|
||
reference: '',
|
||
positions: [],
|
||
economic: emptyEvidence(),
|
||
benefits: emptyEvidence(),
|
||
});
|
||
export const newInternationalPosition = (
|
||
id: string,
|
||
): InternationalPosition => ({
|
||
id,
|
||
country: '',
|
||
subjectNuit: '',
|
||
year: '',
|
||
assessmentKey: '',
|
||
qualificationCountry: '',
|
||
incomeAmount: '',
|
||
incomeIncluded: '',
|
||
incomeReference: '',
|
||
nationalLimit: '',
|
||
nationalLimitReference: '',
|
||
treaty: '',
|
||
treatyLimit: '',
|
||
treatyReference: '',
|
||
previousUse: emptyEvidence(),
|
||
payments: [],
|
||
});
|
||
const states = ['', 'known', 'absent'];
|
||
const yesNo = ['', 'yes', 'no'];
|
||
const evidenceValid = (e: CreditEvidence) =>
|
||
e &&
|
||
states.includes(e.state) &&
|
||
typeof e.amount === 'string' &&
|
||
typeof e.reference === 'string';
|
||
const proofKeys = [
|
||
'id',
|
||
'document',
|
||
'issuer',
|
||
'date',
|
||
'subjectNuit',
|
||
'year',
|
||
'currency',
|
||
'proofAmount',
|
||
'amount',
|
||
'effective',
|
||
'reference',
|
||
'incomeTax',
|
||
'taxReference',
|
||
'exerciseReference',
|
||
];
|
||
const positionKeys = [
|
||
'id',
|
||
'country',
|
||
'subjectNuit',
|
||
'year',
|
||
'assessmentKey',
|
||
'qualificationCountry',
|
||
'incomeAmount',
|
||
'incomeIncluded',
|
||
'incomeReference',
|
||
'nationalLimit',
|
||
'nationalLimitReference',
|
||
'treaty',
|
||
'treatyLimit',
|
||
'treatyReference',
|
||
];
|
||
export function validateInternational(c: InternationalCredits) {
|
||
if (
|
||
!c ||
|
||
c.version !== 1 ||
|
||
!states.includes(c.state) ||
|
||
typeof c.reference !== 'string' ||
|
||
!evidenceValid(c.economic) ||
|
||
!evidenceValid(c.benefits) ||
|
||
!Array.isArray(c.positions) ||
|
||
new Set(c.positions.map((p) => p?.id)).size !== c.positions.length
|
||
)
|
||
throw Error(
|
||
'Créditos internacionais não recuperáveis; originais conservados.',
|
||
);
|
||
const proofIds: string[] = [];
|
||
for (const p of c.positions) {
|
||
if (
|
||
!p ||
|
||
positionKeys.some(
|
||
(k) =>
|
||
typeof (p as unknown as Record<string, unknown>)[k] !==
|
||
'string',
|
||
) ||
|
||
!yesNo.includes(p.incomeIncluded) ||
|
||
!yesNo.includes(p.treaty) ||
|
||
(p.treatyMethod !== undefined &&
|
||
![
|
||
'documented',
|
||
'ptMzRoyalty',
|
||
'ptMzDividend',
|
||
'ptMzInterest',
|
||
].includes(p.treatyMethod)) ||
|
||
(p.treatyPreparation !== undefined &&
|
||
(!p.treatyPreparation ||
|
||
Object.keys(newPtMzRoyaltyPreparation()).some(
|
||
(k) =>
|
||
typeof (
|
||
p.treatyPreparation as unknown as Record<
|
||
string,
|
||
unknown
|
||
>
|
||
)[k] !== 'string',
|
||
) ||
|
||
[
|
||
'residentMz',
|
||
'beneficialOwner',
|
||
'sourcePortugal',
|
||
'payerResidentPt',
|
||
'noLinkedEstablishment',
|
||
'armLength',
|
||
'royaltyDefinition',
|
||
'nonDigital',
|
||
].some(
|
||
(k) =>
|
||
!yesNo.includes(
|
||
(
|
||
p.treatyPreparation as unknown as Record<
|
||
string,
|
||
string
|
||
>
|
||
)[k],
|
||
),
|
||
))) ||
|
||
(p.nationalMethod !== undefined &&
|
||
!['documented', 'proportional'].includes(p.nationalMethod)) ||
|
||
(p.nationalBasisReference !== undefined &&
|
||
typeof p.nationalBasisReference !== 'string') ||
|
||
(p.treatyPreparation &&
|
||
['capitalDefinition', 'governmentInterestExemption'].some(
|
||
(k) => {
|
||
const v = (
|
||
p.treatyPreparation as unknown as Record<
|
||
string,
|
||
unknown
|
||
>
|
||
)[k];
|
||
return (
|
||
v !== undefined &&
|
||
(typeof v !== 'string' || !yesNo.includes(v))
|
||
);
|
||
},
|
||
)) ||
|
||
(p.treatyPreparation?.governmentPayer !== undefined &&
|
||
!['', 'state', 'subdivision', 'local'].includes(
|
||
p.treatyPreparation.governmentPayer,
|
||
)) ||
|
||
(p.treatyPreparation?.governmentPayerReference !== undefined &&
|
||
typeof p.treatyPreparation.governmentPayerReference !==
|
||
'string') ||
|
||
(p.treatyPreparation?.governmentBasis !== undefined &&
|
||
!['', 'payer', 'agreement'].includes(
|
||
p.treatyPreparation.governmentBasis,
|
||
)) ||
|
||
(p.treatyPreparation &&
|
||
[
|
||
'institutionRecipient',
|
||
'financingGranted',
|
||
'bilateralAgreement',
|
||
].some((k) => {
|
||
const v = (
|
||
p.treatyPreparation as unknown as Record<
|
||
string,
|
||
unknown
|
||
>
|
||
)[k];
|
||
return (
|
||
v !== undefined &&
|
||
(typeof v !== 'string' || !yesNo.includes(v))
|
||
);
|
||
})) ||
|
||
(p.treatyPreparation &&
|
||
[
|
||
'institutionReference',
|
||
'financingReference',
|
||
'agreementReference',
|
||
].some((k) => {
|
||
const v = (
|
||
p.treatyPreparation as unknown as Record<
|
||
string,
|
||
unknown
|
||
>
|
||
)[k];
|
||
return v !== undefined && typeof v !== 'string';
|
||
})) ||
|
||
!evidenceValid(p.previousUse) ||
|
||
!Array.isArray(p.payments)
|
||
)
|
||
throw Error(
|
||
'Posição internacional não recuperável; originais conservados.',
|
||
);
|
||
for (const r of p.payments) {
|
||
validateProofParcel(r);
|
||
if (
|
||
!r ||
|
||
proofKeys.some(
|
||
(k) =>
|
||
typeof (r as unknown as Record<string, unknown>)[k] !==
|
||
'string',
|
||
) ||
|
||
!yesNo.includes(r.effective) ||
|
||
!yesNo.includes(r.incomeTax)
|
||
)
|
||
throw Error('Comprovativo internacional não recuperável.');
|
||
proofIds.push(r.id);
|
||
}
|
||
}
|
||
if (new Set(proofIds).size !== proofIds.length)
|
||
throw Error('Identificador de comprovativo internacional repetido.');
|
||
}
|
||
export const creditMoney = (raw: string) => {
|
||
if (!/^\d{1,18}(\.\d{1,2})?$/.test(raw))
|
||
throw Error(
|
||
'Montante MZN em falta/inválido; zero exige declaração própria.',
|
||
);
|
||
return decimalFraction(raw);
|
||
};
|
||
export const creditCompare = (a: FiscalFraction, b: FiscalFraction) =>
|
||
BigInt(a.numerator) * BigInt(b.denominator) -
|
||
BigInt(b.numerator) * BigInt(a.denominator);
|
||
export const creditLimit = (raw: string) => {
|
||
const rational = /^(\d{1,18})\/(\d{1,18})$/.exec(raw);
|
||
if (rational) return fraction(BigInt(rational[1]), BigInt(rational[2]));
|
||
if (!/^\d{1,18}(\.\d{1,6})?$/.test(raw))
|
||
throw Error(
|
||
'Limite documentado em falta/inválido; use decimal ou fracção exacta n/d, sem arredondamento presumido.',
|
||
);
|
||
return decimalFraction(raw);
|
||
};
|
||
const zero = () => fraction(0n);
|
||
const minimum = (...values: FiscalFraction[]) =>
|
||
values.reduce((a, b) => (creditCompare(a, b) < 0n ? a : b));
|
||
const isDate = (s: string) =>
|
||
/^20\d{2}-\d{2}-\d{2}$/.test(s) &&
|
||
Number.isFinite(Date.parse(s)) &&
|
||
new Date(s).toISOString().slice(0, 10) === s;
|
||
export function internationalProofValue(
|
||
r: InternationalTaxProof,
|
||
subjectNuit: string,
|
||
year: string,
|
||
) {
|
||
requireProofParcel(r);
|
||
if (
|
||
!r.id.trim() ||
|
||
!r.document.trim() ||
|
||
!r.issuer.trim() ||
|
||
!r.reference.trim() ||
|
||
!r.taxReference.trim() ||
|
||
r.incomeTax !== 'yes' ||
|
||
r.effective !== 'yes' ||
|
||
r.currency !== 'MZN' ||
|
||
!isDate(r.date) ||
|
||
r.subjectNuit !== subjectNuit ||
|
||
r.year !== year ||
|
||
year !== '2026'
|
||
)
|
||
throw Error(
|
||
'Crédito internacional: exige imposto sobre o rendimento efectivamente pago, prova/titular/exercício 2026 e MZN; previsões, outras naturezas e conversões não correspondidas ficam separados.',
|
||
);
|
||
if (!r.date.startsWith(year + '-') && !r.exerciseReference.trim())
|
||
throw Error(
|
||
'Data de pagamento fora do exercício: falta correspondência documental com o rendimento e imposto deste exercício; não é transporte presumido.',
|
||
);
|
||
const value = creditMoney(r.amount),
|
||
proofAmount = creditMoney(r.proofAmount);
|
||
if (creditCompare(value, proofAmount) > 0n)
|
||
throw Error('Parcela estrangeira ultrapassa o montante comprovado.');
|
||
return { value, proofAmount };
|
||
}
|
||
export function internationalGroupPending(
|
||
c: InternationalCredits,
|
||
economicDetailed = false,
|
||
benefitsDetailed = false,
|
||
) {
|
||
const pending: string[] = [];
|
||
if (!c.state || !c.reference.trim())
|
||
pending.push(
|
||
'Crédito internacional: declare pagamentos conhecidos, ausência ou informação em falta, com suporte.',
|
||
);
|
||
if (c.state === 'absent' && c.positions.length)
|
||
pending.push(
|
||
'Ausência de crédito internacional contradiz posições presentes; conserve e reconcilie.',
|
||
);
|
||
if (c.state === 'known' && !c.positions.length)
|
||
pending.push(
|
||
'Crédito internacional conhecido sem posição/comprovativo; zero não é ausência presumida.',
|
||
);
|
||
for (const [label, e] of [
|
||
...(economicDetailed
|
||
? []
|
||
: [['Dupla tributação económica', c.economic] as const]),
|
||
...(benefitsDetailed
|
||
? []
|
||
: [['Benefícios/créditos próprios', c.benefits] as const]),
|
||
]) {
|
||
try {
|
||
if (!e.state || !e.reference.trim())
|
||
throw Error('estado e suporte em falta');
|
||
if (e.state === 'absent') {
|
||
if (e.amount.trim()) throw Error('ausência contradiz montante');
|
||
} else if (creditCompare(creditMoney(e.amount), zero()) !== 0n)
|
||
throw Error(
|
||
'valor positivo exige regra própria; não é deduzido nem ignorado',
|
||
);
|
||
} catch (e) {
|
||
pending.push(label + ': ' + String(e));
|
||
}
|
||
}
|
||
return pending;
|
||
}
|
||
const validDate = (s: string) =>
|
||
/^20\d{2}-\d{2}-\d{2}$/.test(s) &&
|
||
Number.isFinite(Date.parse(s)) &&
|
||
new Date(s).toISOString().slice(0, 10) === s;
|
||
export function ptMzTreatyLimit(p: InternationalPosition) {
|
||
const q = p.treatyPreparation;
|
||
if (
|
||
p.treaty !== 'yes' ||
|
||
p.country !== 'PT' ||
|
||
p.qualificationCountry !== 'PT' ||
|
||
p.year !== '2026' ||
|
||
!q?.reference.trim()
|
||
)
|
||
throw Error(
|
||
'CDT PT–MZ12/23(3)a: royalties de fonte portuguesa de residente em Moçambique, exercício2026 e suporte por corresponder.',
|
||
);
|
||
for (const key of [
|
||
'residentMz',
|
||
'beneficialOwner',
|
||
'sourcePortugal',
|
||
'payerResidentPt',
|
||
'noLinkedEstablishment',
|
||
] as const)
|
||
if (q[key] !== 'yes')
|
||
throw Error(
|
||
'CDT PT–MZ royalties: condição por documentar — ' + key + '.',
|
||
);
|
||
if (p.treatyMethod !== 'ptMzDividend' && q.armLength !== 'yes')
|
||
throw Error(
|
||
'Excesso por relações especiais em juros/royalties por corresponder.',
|
||
);
|
||
if (
|
||
p.treatyMethod === 'ptMzRoyalty' &&
|
||
(q.royaltyDefinition !== 'yes' || q.nonDigital !== 'yes')
|
||
)
|
||
throw Error(
|
||
'Royalties ordinárias não digitais e definição convencional por documentar.',
|
||
);
|
||
if (p.treatyMethod !== 'ptMzRoyalty' && q.capitalDefinition !== 'yes')
|
||
throw Error(
|
||
'Definição convencional de dividendos/juros por documentar.',
|
||
);
|
||
let governmentExemption:
|
||
| { payer: 'state' | 'subdivision' | 'local'; reference: string }
|
||
| {
|
||
basis: 'agreement';
|
||
recipient: 'institution';
|
||
reference: string;
|
||
financingReference: string;
|
||
agreementReference: string;
|
||
}
|
||
| null = null;
|
||
if (p.treatyMethod === 'ptMzInterest') {
|
||
if (q.governmentInterestExemption === 'yes') {
|
||
if (q.governmentBasis === 'agreement') {
|
||
if (
|
||
q.institutionRecipient !== 'yes' ||
|
||
q.financingGranted !== 'yes' ||
|
||
q.bilateralAgreement !== 'yes' ||
|
||
!q.institutionReference?.trim() ||
|
||
!q.financingReference?.trim() ||
|
||
!q.agreementReference?.trim()
|
||
)
|
||
throw Error(
|
||
'Art11(3)b: instituição/organismo beneficiário corrente, financiamento por ele concedido e acordo entre os governos PT–MZ exigem correspondência documental própria.',
|
||
);
|
||
governmentExemption = {
|
||
basis: 'agreement',
|
||
recipient: 'institution',
|
||
reference: q.institutionReference,
|
||
financingReference: q.financingReference,
|
||
agreementReference: q.agreementReference,
|
||
};
|
||
} else if (
|
||
q.governmentBasis === undefined ||
|
||
q.governmentBasis === 'payer'
|
||
) {
|
||
if (!q.governmentPayer || !q.governmentPayerReference?.trim())
|
||
throw Error(
|
||
'Isenção11(3)a exige devedor Estado português/subdivisão/autarquia documentado; para11(3)b escolha e documente o fundamento próprio.',
|
||
);
|
||
governmentExemption = {
|
||
payer: q.governmentPayer,
|
||
reference: q.governmentPayerReference,
|
||
};
|
||
} else
|
||
throw Error(
|
||
'Escolha o fundamento da isenção; uma selecção limpa não reutiliza a decisão anterior.',
|
||
);
|
||
} 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) ||
|
||
q.from > q.to ||
|
||
!p.payments.length ||
|
||
p.payments.some(
|
||
(r) => !validDate(r.date) || r.date < q.from || r.date > q.to,
|
||
)
|
||
)
|
||
throw Error(
|
||
'Comprovativos fora do período documentado da CDT/residência; não presumir cobertura.',
|
||
);
|
||
const gross = creditMoney(q.gross);
|
||
if (creditCompare(gross, creditMoney(p.incomeAmount)) < 0n)
|
||
throw Error(
|
||
'Rendimento bruto inferior à parcela líquida incluída; reconcilie custos/perdas e base.',
|
||
);
|
||
return {
|
||
version: governmentExemption
|
||
? 'basis' in governmentExemption
|
||
? 'IRPC-PT-MZ-agreement-interest/1'
|
||
: '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,
|
||
...(governmentExemption ? { governmentExemption } : {}),
|
||
rate: governmentExemption ? '0' : '10',
|
||
limit: governmentExemption
|
||
? zero()
|
||
: fraction(
|
||
BigInt(gross.numerator),
|
||
BigInt(gross.denominator) * 10n,
|
||
),
|
||
from: q.from,
|
||
to: q.to,
|
||
reference: governmentExemption
|
||
? q.reference +
|
||
('payer' in governmentExemption
|
||
? '; devedor ' +
|
||
governmentExemption.payer +
|
||
'; ' +
|
||
governmentExemption.reference
|
||
: '; instituição ' +
|
||
governmentExemption.reference +
|
||
'; financiamento ' +
|
||
governmentExemption.financingReference +
|
||
'; acordo PT–MZ ' +
|
||
governmentExemption.agreementReference)
|
||
: q.reference,
|
||
source: governmentExemption
|
||
? 'basis' in governmentExemption
|
||
? 'CDT PT–MZ, RAR36/1992 art11(3)b,PDF11, e23(3)a,PDF21; CIRPC65(2). Instituição/organismo beneficiário corrente, financiamento por ele concedido no âmbito de acordo entre governos PT–MZ documentados. Juros isentos na fonte, limite0; não gera crédito ou reembolso no portal de imposto pago em desconformidade. Outros beneficiários/regimes mantêm correspondência própria.'
|
||
: '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(
|
||
p: InternationalPosition,
|
||
paid: FiscalFraction,
|
||
proofIssues: string[],
|
||
subjectNuit: string,
|
||
year: string,
|
||
assessmentKey: string,
|
||
nationalBasis?: {
|
||
ordinary: boolean;
|
||
taxableBase: FiscalFraction | null;
|
||
colecta: FiscalFraction | null;
|
||
},
|
||
) {
|
||
const pending = [...proofIssues];
|
||
let nationalLimit: FiscalFraction | null = null,
|
||
treatyLimit: FiscalFraction | null = null,
|
||
previousUse: FiscalFraction | null = null,
|
||
eligible: FiscalFraction | null = null,
|
||
available: FiscalFraction | null = null,
|
||
aboveLimit: FiscalFraction | null = null;
|
||
let treatyDerivation: ReturnType<typeof ptMzTreatyLimit> | null = null;
|
||
let nationalDerivation: {
|
||
version: string;
|
||
income: FiscalFraction;
|
||
taxableBase: FiscalFraction;
|
||
colecta: FiscalFraction;
|
||
reference: string;
|
||
} | null = null;
|
||
try {
|
||
if (
|
||
!p.id.trim() ||
|
||
!countryIsoCodes.includes(p.country) ||
|
||
p.country === 'MZ' ||
|
||
p.subjectNuit !== subjectNuit ||
|
||
p.year !== year ||
|
||
year !== '2026'
|
||
)
|
||
throw Error(
|
||
'País estrangeiro do catálogo, titular e exercício não correspondem.',
|
||
);
|
||
if (p.qualificationCountry !== p.country)
|
||
throw Error(
|
||
'O país mudou; confirme a inclusão e os limites documentados para o país seleccionado.',
|
||
);
|
||
if (p.assessmentKey !== assessmentKey || !p.assessmentKey)
|
||
throw Error(
|
||
'Confirme a inclusão e a fracção nacional na colecta corrente; a origem mudou.',
|
||
);
|
||
if (p.incomeIncluded !== 'yes' || !p.incomeReference.trim())
|
||
throw Error(
|
||
'Falta inclusão documentada do rendimento estrangeiro na matéria colectável.',
|
||
);
|
||
const income = creditMoney(p.incomeAmount);
|
||
if (p.nationalMethod === 'proportional') {
|
||
const basis = nationalBasis;
|
||
if (
|
||
!basis?.ordinary ||
|
||
!basis.taxableBase ||
|
||
!basis.colecta ||
|
||
creditCompare(basis.taxableBase, zero()) <= 0n ||
|
||
creditCompare(basis.colecta, zero()) < 0n
|
||
)
|
||
throw Error(
|
||
'Derivação da fracção nacional exige matéria colectável positiva e colecta L6 ordinária corrente; efeitos L7 e casos especiais conservam correspondência própria.',
|
||
);
|
||
if (!p.nationalBasisReference?.trim())
|
||
throw Error(
|
||
'Identifique a correspondência da parcela líquida estrangeira na matéria colectável, incluindo custos/perdas/deduções aplicáveis; o portal não reparte esses valores por presunção.',
|
||
);
|
||
if (creditCompare(income, basis.taxableBase) > 0n)
|
||
throw Error(
|
||
'Parcela estrangeira excede a matéria colectável corrente; reconcilie custos, perdas e deduções antes de derivar a fracção.',
|
||
);
|
||
nationalLimit = fraction(
|
||
BigInt(basis.colecta.numerator) *
|
||
BigInt(income.numerator) *
|
||
BigInt(basis.taxableBase.denominator),
|
||
BigInt(basis.colecta.denominator) *
|
||
BigInt(income.denominator) *
|
||
BigInt(basis.taxableBase.numerator),
|
||
);
|
||
nationalDerivation = {
|
||
version: 'IRPC-national-fraction/1',
|
||
income,
|
||
taxableBase: structuredClone(basis.taxableBase),
|
||
colecta: structuredClone(basis.colecta),
|
||
reference: p.nationalBasisReference,
|
||
};
|
||
} else nationalLimit = creditLimit(p.nationalLimit);
|
||
if (
|
||
p.nationalMethod !== 'proportional' &&
|
||
!p.nationalLimitReference.trim()
|
||
)
|
||
throw Error(
|
||
'Fracção nacional exige suporte próprio; não é calculada nem presumida pelo portal.',
|
||
);
|
||
if (
|
||
creditCompare(income, zero()) === 0n &&
|
||
creditCompare(nationalLimit, zero()) !== 0n
|
||
)
|
||
throw Error(
|
||
'Rendimento incluído zero contradiz fracção nacional positiva.',
|
||
);
|
||
if (!p.treaty || !p.treatyReference.trim())
|
||
throw Error('Aplicabilidade da convenção e suporte em falta.');
|
||
if (p.treatyMethod && p.treatyMethod !== 'documented') {
|
||
treatyDerivation = ptMzTreatyLimit(p);
|
||
treatyLimit = treatyDerivation.limit;
|
||
} else if (p.treaty === 'yes') treatyLimit = creditLimit(p.treatyLimit);
|
||
else if (p.treatyLimit.trim())
|
||
throw Error('Ausência de convenção contradiz limite preenchido.');
|
||
if (!p.previousUse.state || !p.previousUse.reference.trim())
|
||
throw Error(
|
||
'Utilizações anteriores do crédito em falta; histórico de simulação não é consumo oficial.',
|
||
);
|
||
if (p.previousUse.state === 'absent') {
|
||
if (p.previousUse.amount.trim())
|
||
throw Error('Ausência de uso anterior contradiz montante');
|
||
previousUse = zero();
|
||
} else previousUse = creditMoney(p.previousUse.amount);
|
||
if (!p.payments.length)
|
||
throw Error(
|
||
'Sem comprovativos de imposto estrangeiro nesta posição.',
|
||
);
|
||
if (!pending.length) {
|
||
eligible = minimum(
|
||
paid,
|
||
nationalLimit,
|
||
...(treatyLimit ? [treatyLimit] : []),
|
||
);
|
||
aboveLimit = subtractFractions(paid, eligible);
|
||
if (creditCompare(previousUse, eligible) > 0n)
|
||
throw Error(
|
||
'Utilização anterior excede o crédito elegível; reconcilie o saldo.',
|
||
);
|
||
available = subtractFractions(eligible, previousUse);
|
||
}
|
||
} catch (e) {
|
||
pending.push(String(e));
|
||
available = null;
|
||
}
|
||
return {
|
||
id: p.id,
|
||
country: p.country,
|
||
paid,
|
||
nationalLimit,
|
||
...(nationalDerivation ? { nationalDerivation } : {}),
|
||
treatyLimit,
|
||
...(treatyDerivation ? { treatyDerivation } : {}),
|
||
previousUse,
|
||
eligible,
|
||
available,
|
||
aboveLimit,
|
||
pending,
|
||
input: structuredClone(p),
|
||
};
|
||
}
|
||
export function internationalDeduction(
|
||
positions: ReturnType<typeof internationalPosition>[],
|
||
colecta: FiscalFraction | null,
|
||
ready: boolean,
|
||
selected?: FiscalFraction | null,
|
||
) {
|
||
const available = addFractions(
|
||
positions.flatMap((p) =>
|
||
p.available && !p.pending.length ? [p.available] : [],
|
||
),
|
||
);
|
||
const deducted =
|
||
ready && colecta ? (selected ?? minimum(available, colecta)) : null;
|
||
return {
|
||
version: internationalCreditVersion,
|
||
positions,
|
||
available,
|
||
deducted,
|
||
afterCredit:
|
||
deducted && colecta ? subtractFractions(colecta, deducted) : null,
|
||
insufficientCollecta:
|
||
selected === undefined && deducted
|
||
? subtractFractions(available, deducted)
|
||
: null,
|
||
...(selected !== undefined
|
||
? {
|
||
notSelected: deducted
|
||
? subtractFractions(available, deducted)
|
||
: null,
|
||
}
|
||
: {}),
|
||
source:
|
||
internationalCreditSource +
|
||
(positions.some((p) => p.nationalDerivation)
|
||
? ' Modo IRPC-national-fraction/1: no caso L6 ordinário corrente com parcela líquida documentada e correspondida, fracção = colecta antes de créditos × parcela líquida estrangeira / matéria colectável. Não efectua repartição de custos/perdas ou qualificação automática de CDT.'
|
||
: ''),
|
||
countryAllocationCreated: false,
|
||
carryCreated: false,
|
||
officialBalancesConsumed: false,
|
||
};
|
||
}
|