Files
Investidor-V2/resources/js/lib/irpc-international-credit.ts
T
2026-11-10 15:09:31 +02:00

785 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
};
}