Files
Investidor-V2/resources/js/lib/irpc-annual-settlement.ts
T
2026-11-10 16:17:48 +02:00

2388 lines
99 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 {
newInvestmentCredit,
validateInvestmentCredit,
calculateInvestmentCredit,
deductInvestmentCredit,
investmentCreditSource,
investmentAssetIdentity,
investmentAssetClaimed,
type InvestmentCredit,
} from './irpc-investment-credit.ts';
import { inputText } from './input-text.ts';
import {
carryPlan,
carryIdentity,
carrySource,
newCreditCarry,
validateCreditCarry,
type CreditCarry,
} from './irpc-credit-carry.ts';
import {
economicTaxProof,
economicGroupPending,
economicDistributionIdentity,
economicPosition,
economicDeduction,
economicSource,
newEconomicCredits,
validateEconomic,
type EconomicCredits,
} from './irpc-economic-credit.ts';
import {
validateProofParcel,
requireProofParcel,
parcelProjection,
overlappingProofParcels,
canonicalParcel,
type FiscalProofParcel,
} from './fiscal-proof-parcel.ts';
import {
validateInternational,
internationalProofValue,
internationalGroupPending,
internationalPosition,
internationalDeduction,
internationalCreditSource,
creditMoney,
creditCompare,
newInternationalCredits,
type InternationalCredits,
type InternationalTaxProof,
} from './irpc-international-credit.ts';
import {
validatePec,
pecPaymentValue,
pecDeduction,
pecSource,
type PecPayments,
} from './irpc-pec.ts';
import { normalBook, normalCurrent } from './irpc-normal.ts';
import { irpcBook, irpcCurrent } from './irpc-withholdings.ts';
import { acquisitionScope } from './iva-acquisitions.ts';
import { closureFingerprint } from './iva-closure.ts';
import {
addFractions,
subtractFractions,
decimalFraction,
fraction,
fractionPresentation,
type FiscalFraction,
} from './fiscal-rational.ts';
export const annualSettlementVersion = 'IRPC-annual-settlement/1';
export const annualSettlementSource =
'CIRPC, arts. 63 e 67 (Lei 34/2007 e natureza da retenção na redacção aplicável da Lei 12/2025); Regulamento, Decreto 9/2008, arts. 21(2–5) e 27(1–2/6). Recorte: 2026, residente no regime geral, ano civil e MZN. Colecta corrente válida, retenções efectivamente sofridas por conta e PPC comprovados. PEC/créditos positivos e tributação autónoma ficam fora deste fecho inicial. Art. 27(6): montante inferior a 100 MZN sem cobrança/reembolso; saldo aritmético exacto conservado. Saldo simulado por colecta projectada; não é declaração submetida, pagamento ou reembolso recebido.';
type State = '' | 'known' | 'absent';
type Evidence = { state: State; amount: string; reference: string };
type Group = { state: State; reference: string };
export type AnnualWithholding = FiscalProofParcel & {
id: string;
rowId: string;
paymentId: string;
sourceKey: string;
subjectNuit: string;
year: string;
amount: string;
incomeIncluded: '' | 'yes' | 'no';
reference: string;
};
export type AnnualPpc = FiscalProofParcel & {
id: string;
document: string;
issuer: string;
date: string;
subjectNuit: string;
year: string;
currency: string;
proofAmount: string;
amount: string;
effective: '' | 'yes' | 'no';
reference: string;
};
type AnnualRecord = {
version: typeof annualSettlementVersion;
key: string;
createdAt: string;
model: AnnualModel;
};
export type AnnualBook = {
version: 1 | 2 | 3;
subjectNuit: string;
year: string;
reference: string;
assessment: '' | 'normal' | 'special';
assessmentKey: string;
assessmentReference: string;
withholdings: Group & { rows: AnnualWithholding[] };
ppc: Group & { rows: AnnualPpc[] };
pec: Evidence;
pecPayments?: PecPayments;
credits: Evidence;
internationalCredits?: InternationalCredits;
creditCarry?: CreditCarry;
economicCredits?: EconomicCredits;
investmentCredit?: InvestmentCredit;
result?: AnnualRecord;
history: AnnualRecord[];
};
export const annualSource = (b: AnnualBook) => {
const excluded = b.internationalCredits
? b.pecPayments
? 'Créditos económicos/benefícios positivos'
: 'PEC/créditos económicos/benefícios positivos'
: b.pecPayments
? 'Créditos próprios positivos'
: 'PEC/créditos positivos';
return (
annualSettlementSource.replace(
'PEC/créditos positivos',
b.economicCredits
? b.pecPayments
? 'Benefícios positivos'
: 'PEC/benefícios positivos'
: excluded,
) +
(b.pecPayments ? ' ' + pecSource : '') +
(b.internationalCredits ? ' ' + internationalCreditSource : '') +
(b.creditCarry ? ' ' + carrySource : '') +
(b.economicCredits ? ' ' + economicSource : '') +
(b.investmentCredit ? ' ' + investmentCreditSource : '')
);
};
export function prepareInvestmentCredit(
d: Record<string, unknown>,
b: AnnualBook,
): AnnualBook {
if (b.investmentCredit) return b;
const next = prepareInternationalBook(b),
c = newInvestmentCredit(),
a = annualAssessment(d);
c.subjectNuit = b.subjectNuit;
c.year = b.year;
c.assessmentKey = b.assessmentKey;
c.qualification.from = a.period?.from ?? '';
c.qualification.to = a.period?.to ?? '';
return { ...next, version: 2, investmentCredit: c };
}
export function prepareInvestmentCarry(
d: Record<string, unknown>,
b: AnnualBook,
): AnnualBook {
const next = prepareInvestmentCredit(d, b);
if (next.investmentCredit!.carryForward) return next;
return {
...next,
version: 3,
investmentCredit: {
...next.investmentCredit!,
version: 2,
carryForward: {
reference: '',
useReconciled: '',
currentState: '',
currentReference: '',
},
},
};
}
export function prepareEconomicBook(b: AnnualBook): AnnualBook {
if (b.economicCredits) return b;
const next = prepareInternationalBook(b),
e = newEconomicCredits(),
old = next.internationalCredits!.economic;
if (old.state === 'absent') {
e.state = 'absent';
e.reference = old.reference;
}
return { ...next, economicCredits: e };
}
export function prepareCreditCarry(b: AnnualBook): AnnualBook {
return b.creditCarry ? b : { ...b, creditCarry: newCreditCarry() };
}
export function prepareInternationalBook(b: AnnualBook): AnnualBook {
if (b.internationalCredits) return b;
const c = newInternationalCredits(),
old = b.credits;
if (
old.state === 'absent' ||
(old.state === 'known' && old.amount === '0')
) {
c.economic = structuredClone(old);
c.benefits = structuredClone(old);
if (old.state === 'absent') {
c.state = 'absent';
c.reference = old.reference;
}
}
return { ...b, internationalCredits: c };
}
const evidence = (): Evidence => ({ state: '', amount: '', reference: '' });
export const newAnnualBook = (): AnnualBook => ({
version: 1,
subjectNuit: '',
year: '',
reference: '',
assessment: '',
assessmentKey: '',
assessmentReference: '',
withholdings: { state: '', reference: '', rows: [] },
ppc: { state: '', reference: '', rows: [] },
pec: evidence(),
credits: evidence(),
history: [],
});
export const newAnnualPpc = (id: string): AnnualPpc => ({
id,
document: '',
issuer: '',
date: '',
subjectNuit: '',
year: '',
currency: 'MZN',
proofAmount: '',
amount: '',
effective: '',
reference: '',
});
export const newInternationalProof = (id: string): InternationalTaxProof => ({
...newAnnualPpc(id),
incomeTax: '',
taxReference: '',
exerciseReference: '',
});
const zero = () => fraction(0n),
cmp = (a: FiscalFraction, b: FiscalFraction) =>
BigInt(a.numerator) * BigInt(b.denominator) -
BigInt(b.numerator) * BigInt(a.denominator);
const iso = (s: string) =>
/^20\d{2}-\d{2}-\d{2}$/.test(s) &&
Number.isFinite(Date.parse(s)) &&
new Date(s).toISOString().slice(0, 10) === s;
const moneyInput = (s: string) => {
if (!/^\d{1,18}(\.\d{1,2})?$/.test(s))
throw Error(
'MontanteMZN em falta/inválido;máximo dois decimais,sem zero presumido.',
);
return decimalFraction(s);
};
const canonical = (s: string) =>
s.normalize('NFKC').trim().toLocaleLowerCase('pt').replace(/\s+/g, ' ');
const proofIdentity = (issuer: string, document: string, _date: string) =>
JSON.stringify([canonical(issuer), canonical(document)]);
function store(d: Record<string, unknown>) {
const s = JSON.parse(
inputText(d.modelo_a_snapshot || '{"version":1,"books":{}}'),
);
if (
s.version !== 1 ||
!s.books ||
typeof s.books !== 'object' ||
Array.isArray(s.books)
)
throw Error('Livros fiscais não recuperáveis;originais conservados.');
return s;
}
function books(d: Record<string, unknown>): Record<string, AnnualBook> {
const m = store(d).irpcAnnual;
if (m === undefined) return {};
if (!m || typeof m !== 'object' || Array.isArray(m))
throw Error('MapaL8 não recuperável;originais conservados.');
return m;
}
export function validateAnnual(b: AnnualBook) {
if (b?.investmentCredit !== undefined)
validateInvestmentCredit(b.investmentCredit);
if (b?.economicCredits !== undefined) validateEconomic(b.economicCredits);
if (b?.creditCarry !== undefined) validateCreditCarry(b.creditCarry);
if (b?.pecPayments !== undefined) validatePec(b.pecPayments);
if (b?.internationalCredits !== undefined)
validateInternational(b.internationalCredits);
if (
!b ||
![1, 2, 3].includes(b.version) ||
(b.version >= 2 && b.investmentCredit === undefined) ||
(b.investmentCredit !== undefined &&
b.version !== (b.investmentCredit.carryForward ? 3 : 2)) ||
[
'subjectNuit',
'year',
'reference',
'assessmentKey',
'assessmentReference',
].some((k) => typeof b[k as 'year'] !== 'string') ||
!['', 'normal', 'special'].includes(b.assessment) ||
!Array.isArray(b.history)
)
throw Error('PreparaçãoL8 não recuperável.');
for (const g of [b.withholdings, b.ppc, b.pec, b.credits])
if (
!g ||
!['', 'known', 'absent'].includes(g.state) ||
typeof g.reference !== 'string'
)
throw Error('EstadoL8 não recuperável.');
for (const g of [b.pec, b.credits])
if (typeof g.amount !== 'string')
throw Error('CréditoL8 não recuperável.');
const w = [
'id',
'rowId',
'paymentId',
'sourceKey',
'subjectNuit',
'year',
'amount',
'incomeIncluded',
'reference',
],
p = Object.keys(newAnnualPpc(''));
for (const [rows, keys] of [
[b.withholdings.rows, w],
[b.ppc.rows, p],
] as const)
if (
!Array.isArray(rows) ||
new Set(rows.map((r) => r?.id)).size !== rows.length ||
rows.some(
(r) =>
!r ||
keys.some(
(k) =>
typeof (r as unknown as Record<string, unknown>)[
k
] !== 'string',
),
)
)
throw Error(
'ComprovativosL8 não recuperáveis ou identificador repetido.',
);
for (const r of [...b.withholdings.rows, ...b.ppc.rows])
validateProofParcel(r);
if (
b.withholdings.rows.some(
(r) => !['', 'yes', 'no'].includes(r.incomeIncluded),
) ||
b.ppc.rows.some((r) => !['', 'yes', 'no'].includes(r.effective))
)
throw Error('QualificaçãoL8 não recuperável.');
for (const r of [b.result, ...b.history])
if (
r &&
(r.version !== annualSettlementVersion ||
typeof r.key !== 'string' ||
typeof r.createdAt !== 'string' ||
!r.model ||
!Array.isArray(r.model.pending))
)
throw Error('HistóricoL8 não recuperável.');
}
export function annualBook(d: Record<string, unknown>) {
const b = books(d)[acquisitionScope(d)];
if (b) validateAnnual(b);
return b;
}
export function saveAnnualBook(d: Record<string, unknown>, b: AnnualBook) {
validateAnnual(b);
return JSON.stringify({
...store(d),
irpcAnnual: { ...books(d), [acquisitionScope(d)]: b },
});
}
export function annualAssessment(d: Record<string, unknown>) {
const b = normalBook(d),
r = b.result,
m = r?.model;
return {
book: b,
record: r,
current: normalCurrent(d, b),
key: r ? closureFingerprint(r) : '',
subjectNuit: b.base?.subjectNuit ?? '',
year: b.year,
period: m?.periodsFx
? { from: m.periodsFx.input.from, to: m.periodsFx.input.to }
: (m?.fiscalPeriod ?? m?.assessment?.period ?? null),
kind: b.special ? ('special' as const) : ('normal' as const),
colecta: b.special
? (m?.special?.colecta ?? null)
: (m?.colecta ?? null),
autonomous: b.special
? (m?.special?.expenses.autonomousTax ?? null)
: zero(),
};
}
export function reuseAnnualAssessment(
d: Record<string, unknown>,
b: AnnualBook,
): AnnualBook {
const a = annualAssessment(d);
return {
...b,
subjectNuit: b.subjectNuit || a.subjectNuit,
year: b.year || a.year,
assessment: a.kind,
assessmentKey: a.key,
assessmentReference:
b.assessmentReference || a.book.assessment?.reference || '',
};
}
export function annualWithholdingOptions(d: Record<string, unknown>) {
const b = irpcBook(d),
current = irpcCurrent(d, b);
return b.rows.flatMap((r) =>
r.allocations.map((a) => {
const p = b.payments.find((p) => p.id === a.paymentId),
v = b.result?.rows.find((v) => v.id === r.id),
issues: string[] = [];
if (!current || !b.result)
issues.push('Recalcule e guardeL5 com as entradas actuais.');
if (
r.direction !== 'received' ||
r.origin !== 'effective' ||
v?.direction !== 'received' ||
v.status !== 'calculated' ||
v.nature !== 'on_account' ||
r.settlement !== 'complete' ||
v.settled === null
)
issues.push(
'Apenas retenção recebida/efectiva por conta,calculada e reconciliada emL5;remessa por terceiros,definitiva ou projecção não é crédito próprio.',
);
if (
!p ||
p.kind !== 'withholding_proof' ||
p.currency !== 'MZN' ||
r.currency !== 'MZN' ||
!iso(p.date) ||
!p.reference.trim()
)
issues.push(
'Comprovativo de retenção sofrida emMZN em falta ou fora do recorte.',
);
return {
rowId: r.id,
paymentId: a.paymentId,
document: p?.reference ?? '',
issuer: r.counterparty,
date: p?.date ?? '',
eventDate: v?.eventDate ?? '',
year: v?.eventDate.slice(0, 4) ?? '',
amount: a.amount,
proofAmount: p?.amount ?? '',
identity: proofIdentity(
r.counterparty,
p?.reference ?? '',
p?.date ?? '',
),
key: closureFingerprint([r, v, p, a]),
ready: !issues.length,
reason: issues.join(' '),
};
}),
);
}
export function reuseAnnualWithholding(
d: Record<string, unknown>,
b: AnnualBook,
rowId: string,
paymentId: string,
id: string,
): AnnualBook {
const o = annualWithholdingOptions(d).find(
(o) => o.rowId === rowId && o.paymentId === paymentId,
);
if (!o) throw Error('ComprovativoL5 não encontrado;originais conservados.');
if (
b.withholdings.rows.some(
(r) => r.rowId === rowId && r.paymentId === paymentId,
)
)
throw Error(
'Retenção/prova já seleccionada;altere a parcela existente sem duplicar.',
);
return {
...b,
withholdings: {
...b.withholdings,
rows: [
...b.withholdings.rows,
{
id,
rowId,
paymentId,
sourceKey: o.key,
subjectNuit: b.subjectNuit,
year: o.year,
amount: o.amount,
incomeIncluded: '',
reference: '',
},
],
},
};
}
function dependencies(d: Record<string, unknown>, includeInvestment = false) {
let normal: unknown, L5: unknown;
try {
const a = annualAssessment(d);
normal = [a.record, a.current];
} catch (e) {
normal = String(e);
}
try {
const b = irpcBook(d);
L5 = [b.rows, b.payments, b.result, irpcCurrent(d, b)];
} catch (e) {
L5 = String(e);
}
return [
normal,
L5,
Object.entries(books(d))
.filter(([k]) => k !== acquisitionScope(d))
.map(([k, b]) => [
k,
b.subjectNuit,
b.year,
b.withholdings,
b.ppc,
...(b.pecPayments ? [b.pecPayments] : []),
...(b.internationalCredits ? [b.internationalCredits] : []),
...(b.creditCarry ? [b.creditCarry] : []),
...(b.economicCredits ? [b.economicCredits] : []),
...(includeInvestment && b.investmentCredit
? [b.investmentCredit]
: []),
]),
];
}
function key(d: Record<string, unknown>, b: AnnualBook) {
const { result: _, history: __, ...input } = b;
return closureFingerprint([
annualSettlementVersion,
acquisitionScope(d),
input,
dependencies(d, !!b.investmentCredit),
]);
}
export function annualCurrent(d: Record<string, unknown>, b: AnnualBook) {
return !!b.result && b.result.key === key(d, b);
}
export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
validateAnnual(b);
const { result: _result, history: _history, ...input } = b;
const pending: string[] = [],
warn: string[] = [];
let assessment: ReturnType<typeof annualAssessment> | null = null,
colecta: FiscalFraction | null = null;
let options: ReturnType<typeof annualWithholdingOptions> = [];
try {
assessment = annualAssessment(d);
const a = assessment,
m = a.record?.model;
if (
!a.record ||
!a.current ||
a.key !== b.assessmentKey ||
b.assessment !== a.kind
)
pending.push(
'Colecta de origem ausente,alterada ou não corrente;reutilize a execuçãoL6/L7 aplicável,não some nem contorne efeitosL7.',
);
if (
!/^\d{9}$/.test(b.subjectNuit) ||
b.subjectNuit !== a.subjectNuit ||
b.year !== '2026' ||
b.year !== a.year ||
a.book.scope !== 'resident_general' ||
m?.currency !== 'MZN' ||
a.period?.from !== '2026-01-01' ||
a.period?.to !== '2026-12-31'
)
pending.push(
'Entidade/NUIT,exercício ou âmbito fora do recorte residente/ano civil2026/MZN;períodos especiais permanecem separados.',
);
if (!b.reference.trim() || !b.assessmentReference.trim())
pending.push(
'Falta suporte do fecho do sujeito/exercício e da colecta seleccionada.',
);
if (!a.colecta || cmp(a.colecta, zero()) < 0n)
pending.push(
'Colecta aplicável indeterminada;uma parcelaL6 não substitui totalL7 pendente.',
);
else colecta = structuredClone(a.colecta);
if (
a.kind === 'special' &&
(!m?.special || !a.autonomous || cmp(a.autonomous, zero()) !== 0n)
)
pending.push(
'Tributação autónoma positiva/indeterminada exige liquidação própria;este primeiro saldo não a ignora nem a compensa.',
);
} catch (e) {
pending.push('Origem da colecta não recuperável: ' + String(e));
}
try {
options = annualWithholdingOptions(d);
} catch (e) {
if (b.withholdings.state === 'known' || b.withholdings.rows.length)
pending.push('OrigemL5 não recuperável: ' + String(e));
}
const group = (g: Group, rows: unknown[], label: string) => {
if (!g.state || !g.reference.trim())
pending.push(
label +
':identifique valor conhecido,ausência declarada ou informação em falta com suporte.',
);
if (g.state === 'absent' && rows.length)
pending.push(
label +
':ausência declarada contradiz parcelas presentes;conserve e reconcilie.',
);
if (g.state === 'known' && !rows.length)
pending.push(
label +
':não há comprovativos seleccionados;ausência exige declaração própria.',
);
};
group(b.withholdings, b.withholdings.rows, 'Retenções sofridas por conta');
group(b.ppc, b.ppc.rows, 'PPC efectuados');
try {
if (
b.withholdings.state === 'absent' &&
options.some(
(o) =>
o.ready &&
o.year === b.year &&
cmp(moneyInput(o.amount), zero()) > 0n,
)
)
pending.push(
'Ausência de retenções sofridas contradiz provaL5 efectiva por conta no exercício;reconcilie sem a ignorar.',
);
} catch (e) {
pending.push('Reconciliação da ausência de retenções: ' + String(e));
}
for (const [label, e] of [
...(b.pecPayments ? [] : [['PEC', b.pec] as const]),
...(b.internationalCredits
? []
: [['Créditos com regras próprias', b.credits] as const]),
]) {
try {
if (!e.state || !e.reference.trim())
throw Error('presença/ausência e suporte em falta');
if (e.state === 'absent') {
if (e.amount.trim())
throw Error('ausência contradiz montante preenchido');
} else if (cmp(moneyInput(e.amount), zero()) !== 0n)
throw Error(
'montante positivo fora deste recorte:corresponder ordem,limite e transporte próprios;sem abatimento ou reembolso automático',
);
} catch (err) {
pending.push(label + ': ' + String(err));
}
}
const claims: Array<
FiscalProofParcel & {
id: string;
kind:
| 'withholding'
| 'ppc'
| 'pec'
| 'foreign_credit'
| 'economic_credit';
country?: string;
positionId?: string;
previouslyUsed?: FiscalFraction;
identity: string;
proofAmount: FiscalFraction | null;
value: FiscalFraction | null;
sourceKey: string;
allocationAmount: FiscalFraction | null;
document: string;
issuer: string;
date: string;
subjectNuit: string;
year: string;
reason: string;
}
> = [];
for (const r of b.withholdings.rows) {
const o = options.find(
(o) => o.rowId === r.rowId && o.paymentId === r.paymentId,
),
issues: string[] = [];
let value: FiscalFraction | null = null,
proofAmount: FiscalFraction | null = null;
try {
requireProofParcel(r);
if (!o || !o.ready || r.sourceKey !== o.key)
throw Error(
o?.reason ||
'provaL5 ausente ou alterada;reutilize sem duplicar',
);
if (
r.subjectNuit !== b.subjectNuit ||
r.year !== b.year ||
o.year !== b.year ||
!o.date.startsWith(b.year + '-')
)
throw Error(
'entidade/exercício da retenção não correspondem ao fecho',
);
if (r.incomeIncluded !== 'yes' || !r.reference.trim())
throw Error(
'confirme o próprio beneficiário/NUIT e inclusão do rendimento na base do exercício com suporte',
);
value = moneyInput(r.amount);
proofAmount = moneyInput(o.proofAmount);
if (cmp(value, moneyInput(o.amount)) > 0n)
throw Error(
'parcela ultrapassa a retenção efectivamente comprovada e afectada emL5',
);
if (
b.withholdings.rows.filter(
(x) => x.rowId === r.rowId && x.paymentId === r.paymentId,
).length !== 1
)
throw Error(
'retenção/prova repetida;uma afectação por linhaL5',
);
if (
assessment?.record?.model.special?.cooperative.status ===
'calculated' &&
cmp(value, zero()) > 0n
)
throw Error(
'cooperativa integralmente elegível declara ausência de rendimentos retidos;segregue o casoL7 antes do crédito anual',
);
} catch (e) {
issues.push(String(e));
value = null;
}
claims.push({
...parcelProjection(r),
id: r.id,
kind: 'withholding',
identity: o?.identity ?? '',
proofAmount,
value,
sourceKey: r.sourceKey,
allocationAmount: o && value ? moneyInput(o.amount) : null,
document: o?.document ?? r.paymentId,
issuer: o?.issuer ?? '',
date: o?.date ?? '',
subjectNuit: r.subjectNuit,
year: r.year,
reason: issues.join(' '),
});
}
for (const r of b.ppc.rows) {
const issues: string[] = [];
let value: FiscalFraction | null = null,
proofAmount: FiscalFraction | null = null;
try {
requireProofParcel(r);
if (
!r.document.trim() ||
!r.issuer.trim() ||
!r.reference.trim() ||
r.effective !== 'yes' ||
r.currency !== 'MZN' ||
!iso(r.date) ||
!r.date.startsWith(b.year + '-') ||
r.subjectNuit !== b.subjectNuit ||
r.year !== b.year
)
throw Error(
'PPC exige comprovativo efectivo,MZN,data,entidade e exercício correspondentes;projecção/tesouraria não é pagamento',
);
value = moneyInput(r.amount);
proofAmount = moneyInput(r.proofAmount);
if (cmp(value, proofAmount) > 0n)
throw Error('parcelaPPC ultrapassa o montante comprovado');
} catch (e) {
issues.push(String(e));
value = null;
}
claims.push({
...parcelProjection(r),
id: r.id,
kind: 'ppc',
identity: proofIdentity(r.issuer, r.document, r.date),
proofAmount,
value,
sourceKey: '',
allocationAmount: null,
document: r.document,
issuer: r.issuer,
date: r.date,
subjectNuit: r.subjectNuit,
year: r.year,
reason: issues.join(' '),
});
}
if (b.pecPayments) {
group(
b.pecPayments,
b.pecPayments.rows,
'PEC pagos no próprio exercício',
);
for (const r of b.pecPayments.rows) {
let value: FiscalFraction | null = null,
proofAmount: FiscalFraction | null = null,
previouslyUsed: FiscalFraction | undefined;
let reason = '';
try {
({ value, proofAmount, previouslyUsed } = pecPaymentValue(
r,
b.subjectNuit,
b.year,
));
} catch (e) {
reason = String(e);
}
claims.push({
...parcelProjection(r),
id: r.id,
kind: 'pec',
identity: proofIdentity(r.issuer, r.document, r.date),
value,
proofAmount,
previouslyUsed,
sourceKey: '',
allocationAmount: null,
document: r.document,
issuer: r.issuer,
date: r.date,
subjectNuit: r.subjectNuit,
year: r.year,
reason,
});
}
}
if (b.internationalCredits) {
pending.push(
...internationalGroupPending(
b.internationalCredits,
!!b.economicCredits,
!!b.investmentCredit,
),
);
const countries = b.internationalCredits.positions.map(
(p) => p.country,
);
if (new Set(countries).size !== countries.length)
pending.push(
'Um país/exercício tem várias posições;consolide rendimentos e comprovativos sob uma única fracção nacional,sem repetir o limite.',
);
for (const p of b.internationalCredits.positions)
for (const r of p.payments) {
let value: FiscalFraction | null = null,
proofAmount: FiscalFraction | null = null,
reason = '';
try {
({ value, proofAmount } = internationalProofValue(
r,
b.subjectNuit,
b.year,
));
} catch (e) {
reason = String(e);
}
claims.push({
...parcelProjection(r),
id: r.id,
kind: 'foreign_credit',
country: p.country,
positionId: p.id,
identity: proofIdentity(r.issuer, r.document, r.date),
proofAmount,
value,
sourceKey: '',
allocationAmount: null,
document: r.document,
issuer: r.issuer,
date: r.date,
subjectNuit: r.subjectNuit,
year: r.year,
reason,
});
}
}
if (b.economicCredits) {
pending.push(...economicGroupPending(b.economicCredits));
if (!b.internationalCredits)
pending.push(
'Distinguir o detalhe económico do inventário internacional e dos benefícios; use a preparação existente sem apagar o resumo anterior.',
);
for (const r of b.economicCredits.rows) {
let value: FiscalFraction | null = null,
proofAmount: FiscalFraction | null = null,
previouslyUsed: FiscalFraction | undefined,
reason = '';
try {
({ value, proofAmount, previouslyUsed } = economicTaxProof(r));
} catch (e) {
reason = String(e);
}
claims.push({
id: r.id,
kind: 'economic_credit',
positionId: r.id,
identity: proofIdentity(
r.taxIssuer,
r.taxDocument,
r.distributionDate,
),
parcel: r.taxParcel,
parcelReference: r.parcelReference,
proofAmount,
value,
previouslyUsed,
sourceKey: '',
allocationAmount: null,
document: r.taxDocument,
issuer: r.taxIssuer,
date: r.distributionDate,
subjectNuit: r.subjectNuit,
year: r.year,
reason,
});
}
}
const elsewhere: Array<
FiscalProofParcel & {
identity: string;
subjectNuit: string;
year: string;
value: FiscalFraction;
proofAmount: FiscalFraction;
sourceKey: string;
kind:
| 'ppc'
| 'pec'
| 'withholding'
| 'foreign_credit'
| 'economic_credit';
country?: string;
previouslyUsed?: FiscalFraction;
}
> = [];
for (const [scope, other] of Object.entries(books(d))) {
if (scope === acquisitionScope(d)) continue;
try {
validateAnnual(other);
for (const r of other.economicCredits?.rows ?? []) {
if (
b.economicCredits?.rows.some(
(x) =>
economicDistributionIdentity(x) ===
economicDistributionIdentity(r),
)
)
pending.push(
'Parcela de lucros económicos já preparada noutro mapa;consolide sem duplicar o crédito, mesmo com documentos de imposto diferentes.',
);
const v = economicTaxProof(r);
elsewhere.push({
identity: proofIdentity(
r.taxIssuer,
r.taxDocument,
r.distributionDate,
),
parcel: r.taxParcel,
parcelReference: r.parcelReference,
subjectNuit: r.subjectNuit,
year: r.year,
...v,
sourceKey: '',
kind: 'economic_credit',
});
}
for (const p of other.internationalCredits?.positions ?? []) {
if (
other.subjectNuit === b.subjectNuit &&
other.year === b.year &&
b.internationalCredits?.positions.some(
(x) => x.country === p.country,
)
)
pending.push(
'País ' +
p.country +
' já preparado noutro mapa da entidade/exercício;consolide a fracção nacional e usos sem duplicar quotas.',
);
for (const r of p.payments) {
const v = internationalProofValue(
r,
other.subjectNuit,
other.year,
);
elsewhere.push({
...parcelProjection(r),
identity: proofIdentity(r.issuer, r.document, r.date),
subjectNuit: r.subjectNuit,
year: r.year,
...v,
sourceKey: '',
kind: 'foreign_credit',
country: p.country,
});
}
}
for (const r of other.pecPayments?.rows ?? []) {
const p = pecPaymentValue(r, other.subjectNuit, other.year);
elsewhere.push({
...parcelProjection(r),
identity: proofIdentity(r.issuer, r.document, r.date),
subjectNuit: r.subjectNuit,
year: r.year,
...p,
sourceKey: '',
kind: 'pec',
});
}
for (const r of other.ppc.rows)
elsewhere.push({
...parcelProjection(r),
identity: proofIdentity(r.issuer, r.document, r.date),
subjectNuit: r.subjectNuit,
year: r.year,
value: moneyInput(r.amount),
proofAmount: moneyInput(r.proofAmount),
sourceKey: '',
kind: 'ppc',
});
for (const r of other.withholdings.rows) {
const source = (store(d).irpcWithholdings ?? {})[scope];
const row = source?.rows?.find(
(x: { id: string }) => x.id === r.rowId,
),
p = source?.payments?.find(
(x: { id: string }) => x.id === r.paymentId,
);
if (row && p)
elsewhere.push({
...parcelProjection(r),
identity: proofIdentity(
row.counterparty,
p.reference,
p.date,
),
subjectNuit: r.subjectNuit,
year: r.year,
value: moneyInput(r.amount),
proofAmount: moneyInput(p.amount),
sourceKey: r.sourceKey,
kind: 'withholding',
});
}
} catch (e) {
if (other?.subjectNuit === b.subjectNuit)
pending.push(
'Outro mapa da mesma entidade não recuperável para conferir duplicações: ' +
scope +
';' +
String(e),
);
}
}
for (const c of claims) {
if (!c.value || !c.proofAmount) continue;
const same = claims.filter(
(x) => overlappingProofParcels(x, c) && x.value,
),
other = elsewhere.filter((x) => overlappingProofParcels(x, c));
let issue = '';
if (
(same.filter((x) => x.kind !== 'withholding').length > 1 &&
!same.every((x) => x.kind === 'economic_credit')) ||
new Set(same.map((x) => x.kind)).size > 1
)
issue =
'Mesmo comprovativo repetido ou usado com naturezas diferentes (retenção/PPC/PEC/crédito internacional);reconcilie sem dedução dupla.';
if (
other.some(
(x) => x.subjectNuit !== b.subjectNuit || x.year !== b.year,
)
)
issue =
'Mesmo comprovativo já associado a outra entidade/exercício;reconcilie a titularidade e a afectação.';
if (
[...same, ...other].some(
(x) =>
x.proofAmount && cmp(x.proofAmount, c.proofAmount!) !== 0n,
) ||
cmp(
addFractions([
...same.flatMap((x) => (x.value ? [x.value] : [])),
...other.map((x) => x.value),
]),
c.proofAmount,
) > 0n
)
issue =
'Comprovativo utilizado além do montante disponível ou com montantes divergentes neste/outro mapa.';
if (
(c.kind === 'pec' || c.kind === 'economic_credit') &&
c.previouslyUsed
) {
if (
other.some((x) => x.kind !== c.kind) ||
[...same, ...other].some(
(x) =>
!x.previouslyUsed ||
cmp(x.previouslyUsed, c.previouslyUsed!) !== 0n,
) ||
cmp(
addFractions([
c.previouslyUsed,
...same.flatMap((x) => (x.value ? [x.value] : [])),
...other.map((x) => x.value),
]),
c.proofAmount,
) > 0n
)
issue =
'PEC/IRPC económico com utilização/atribuição anterior divergente, natureza incompatível ou parcelas acima do saldo disponível.';
}
if (other.some((x) => x.kind !== c.kind))
issue =
'Comprovativo já afectado a outra natureza (retenção/PPC/PEC/crédito internacional);sem dupla dedução.';
if (
c.kind === 'foreign_credit' &&
other.some((x) => x.country !== c.country)
)
issue =
'Comprovativo estrangeiro associado a outro país/natureza;reconcilie sem duplicação.';
if (
c.kind === 'withholding' &&
c.sourceKey &&
c.allocationAmount &&
cmp(
addFractions([
...claims
.filter(
(x) =>
x.kind === 'withholding' &&
x.sourceKey === c.sourceKey,
)
.flatMap((x) => (x.value ? [x.value] : [])),
...elsewhere
.filter(
(x) =>
x.kind === 'withholding' &&
x.sourceKey === c.sourceKey,
)
.map((x) => x.value),
]),
c.allocationAmount,
) > 0n
)
issue =
'Mesma afectação L5 reutilizada além da parcela disponível, ainda que o comprovativo global seja maior.';
if (issue) c.reason = [c.reason, issue].filter(Boolean).join(' ');
}
for (const c of claims)
if (c.reason) pending.push((c.document || c.id) + ': ' + c.reason);
const knownWithholdings = addFractions(
claims
.filter((c) => c.kind === 'withholding' && !c.reason && c.value)
.map((c) => c.value!),
),
knownPpc = addFractions(
claims
.filter((c) => c.kind === 'ppc' && !c.reason && c.value)
.map((c) => c.value!),
);
const positions =
b.internationalCredits?.positions.map((p) => {
const own = claims.filter(
(c) => c.kind === 'foreign_credit' && c.positionId === p.id,
);
const m = internationalPosition(
p,
addFractions(
own
.filter((c) => !c.reason && c.value)
.map((c) => c.value!),
),
own.filter((c) => c.reason).map((c) => c.reason),
b.subjectNuit,
b.year,
b.assessmentKey,
{
ordinary:
assessment?.kind === 'normal' &&
assessment.current &&
assessment.key === b.assessmentKey,
taxableBase: assessment?.record?.model.taxableBase ?? null,
colecta: assessment?.colecta ?? null,
},
);
pending.push(
...m.pending.map((e) => 'País ' + p.country + ': ' + e),
);
return m;
}) ?? [];
const derivedNational = positions.flatMap((p) =>
p.nationalDerivation ? [p.nationalDerivation.income] : [],
);
const currentBase = assessment?.record?.model.taxableBase;
if (currentBase && cmp(addFractions(derivedNational), currentBase) > 0n)
pending.push(
'As parcelas estrangeiras com fracção derivada excedem em conjunto a matéria colectável corrente; reconcilie a afectação por país.',
);
const economicRows =
b.economicCredits?.rows.map((r) => {
const issues = claims
.filter(
(c) =>
c.kind === 'economic_credit' &&
c.positionId === r.id &&
c.reason,
)
.map((c) => c.reason);
const m = economicPosition(
r,
b.subjectNuit,
b.year,
b.assessmentKey,
colecta,
issues,
{
ordinary:
assessment?.kind === 'normal' &&
assessment.current &&
assessment.key === b.assessmentKey,
taxableBase: assessment?.record?.model.taxableBase ?? null,
},
);
pending.push(
...m.pending.map(
(e) => 'Lucros ' + r.distributionDocument + ': ' + e,
),
);
return m;
}) ?? [];
if (b.economicCredits) {
if (
currentBase &&
cmp(
addFractions(
economicRows.flatMap((r) =>
r.nationalDerivation
? [r.nationalDerivation.included]
: [],
),
),
currentBase,
) > 0n
)
pending.push(
'As parcelas económicas com quotas derivadas excedem em conjunto a matéria colectável; reconcilie os lucros e acréscimos por origem.',
);
if (b.economicCredits.rows.length && assessment?.kind !== 'normal')
pending.push(
'Crédito económico delimitado à colecta normal corrente;efeitos L7 exigem correspondência própria.',
);
}
const investmentPreview =
b.investmentCredit && assessment?.record
? calculateInvestmentCredit(
b.investmentCredit,
assessment.book,
assessment.record.model,
assessment.key,
assessment.current,
)
: null;
if (b.investmentCredit && !investmentPreview)
pending.push('CréditoCBF15 exige colecta L6 recuperável.');
if (investmentPreview) pending.push(...investmentPreview.pending);
if (
investmentPreview?.available &&
cmp(investmentPreview.available, zero()) > 0n &&
(!investmentPreview.projectCeiling ||
cmp(investmentPreview.projectCeiling, zero()) > 0n) &&
b.creditCarry?.lots.length
)
pending.push(
'CombinaçãoCBF15 com saldos anteriores exige correspondência própria; transportes anteriores preservados.',
);
if (
b.investmentCredit?.qualification.state === 'known' &&
b.investmentCredit.qualification.eligible === 'yes' &&
investmentPreview?.nominal &&
cmp(investmentPreview.nominal, zero()) > 0n
) {
for (const [scope, other] of Object.entries(books(d))) {
if (
scope === acquisitionScope(d) ||
other.subjectNuit !== b.subjectNuit ||
other.year !== b.year ||
!other.investmentCredit
)
continue;
try {
validateInvestmentCredit(other.investmentCredit);
const c = other.investmentCredit;
if (
c.qualification.state !== 'known' ||
c.qualification.eligible !== 'yes'
)
continue;
if (!c.assets.some(investmentAssetClaimed)) continue;
for (const r of b.investmentCredit.assets.filter(
investmentAssetClaimed,
)) {
const id = investmentAssetIdentity(r),
others = c.assets.filter(
(x) =>
investmentAssetClaimed(x) &&
investmentAssetIdentity(x) === id,
);
if (!others.length) continue;
const proof = creditMoney(r.proofAmount);
if (
others.some(
(x) =>
creditCompare(
creditMoney(x.proofAmount),
proof,
) !== 0n,
)
)
pending.push(
'Montante documentalCBF15 contraditório entre mapas; reconcilie a prova.',
);
const own = b.investmentCredit.assets.filter(
(x) =>
investmentAssetClaimed(x) &&
investmentAssetIdentity(x) === id,
);
if (
creditCompare(
addFractions(
[...own, ...others].map((x) =>
creditMoney(x.amount),
),
),
proof,
) > 0n
)
pending.push(
'InvestimentoCBF15 atribuído entre mapas ultrapassa o documento efectivo; não duplicar capacidade.',
);
}
const sameProject =
canonicalParcel(c.projectReference) ===
canonicalParcel(b.investmentCredit.projectReference);
const sharedAsset = c.assets
.filter(investmentAssetClaimed)
.some((r) =>
b.investmentCredit!.assets.some((p) =>
overlappingProofParcels(
{ ...r, identity: investmentAssetIdentity(r) },
{ ...p, identity: investmentAssetIdentity(p) },
),
),
);
if (sameProject || sharedAsset)
pending.push(
'CBF15 já preparado noutro mapa da entidade; consolide projecto/documentos sem duplicar: ' +
scope,
);
} catch (e) {
pending.push(
'Outro mapaCBF15 não recuperável para conferir duplicação: ' +
scope +
'; ' +
String(e),
);
}
}
}
const economicPreview = b.economicCredits
? economicDeduction(economicRows, colecta, true)
: null;
const remainingForCarry = economicPreview?.afterCredit ?? colecta;
const pecAvailable = addFractions(
claims
.filter((c) => c.kind === 'pec' && !c.reason && c.value)
.map((c) => c.value!),
);
const carryDuplicates: string[] = [];
if (b.creditCarry)
for (const [scope, other] of Object.entries(books(d))) {
if (
scope === acquisitionScope(d) ||
other.subjectNuit !== b.subjectNuit
)
continue;
try {
validateAnnual(other);
if (
other.creditCarry?.lots.some((r) =>
b.creditCarry!.lots.some(
(x) => carryIdentity(x) === carryIdentity(r),
),
)
)
carryDuplicates.push(
'Saldo anterior já preparado noutro mapa da entidade;consolide a origem e usos sem duplicar: ' +
scope,
);
} catch (e) {
carryDuplicates.push(
'Outro mapa da entidade não recuperável para conferir saldos anteriores: ' +
scope +
';' +
String(e),
);
}
}
const carry = b.creditCarry
? carryPlan(
b.creditCarry,
b.subjectNuit,
b.year,
b.assessmentKey,
addFractions(
positions.flatMap((p) =>
p.available && !p.pending.length ? [p.available] : [],
),
),
pecAvailable,
remainingForCarry,
carryDuplicates,
)
: null;
if (carry) pending.push(...carry.pending);
if (investmentPreview?.projectCeiling && !pending.length) {
const beforeInternational = economicPreview?.afterCredit ?? colecta;
const preview = b.internationalCredits
? internationalDeduction(
positions,
beforeInternational,
true,
carry?.international ?? undefined,
)
: null;
const remaining = carry
? subtractFractions(
preview?.afterCredit ?? beforeInternational!,
carry.priorInternational,
)
: (preview?.afterCredit ?? beforeInternational);
if (!remaining || cmp(investmentPreview.projectCeiling, remaining) > 0n)
pending.push(
'Quota residualCBF15 do projecto excede a colecta após créditos económicos/internacionais; reconcilie a atribuição antes de deduzir.',
);
}
const ready = pending.length === 0 && colecta !== null,
economic = b.economicCredits
? economicDeduction(economicRows, colecta, ready)
: null,
afterEconomic = economic?.afterCredit ?? colecta;
const international = b.internationalCredits
? internationalDeduction(
positions,
afterEconomic,
ready,
carry?.international ?? undefined,
)
: null;
const creditCollecta =
ready && carry
? subtractFractions(
international?.afterCredit ?? afterEconomic!,
carry.priorInternational,
)
: (international?.afterCredit ?? afterEconomic);
const investment = investmentPreview
? deductInvestmentCredit(investmentPreview, creditCollecta, ready)
: null;
const afterInvestment = investment?.afterCredit ?? creditCollecta;
const pec = b.pecPayments
? pecDeduction(
pecAvailable,
afterInvestment,
ready,
carry?.pec ?? undefined,
)
: null;
const afterPec =
ready && carry
? subtractFractions(
pec?.afterPec ?? afterInvestment!,
carry.priorPec,
)
: (pec?.afterPec ?? afterInvestment);
const arithmeticBalance = ready
? subtractFractions(
subtractFractions(afterPec!, knownWithholdings),
knownPpc,
)
: null;
if (
international &&
international.insufficientCollecta &&
cmp(international.insufficientCollecta, zero()) > 0n
)
warn.push(
'Crédito elegível sem colecta suficiente:sem reembolso,transporte ou escolha automática da afectação por país.',
);
let payable: FiscalFraction | null = null,
recoverable: FiscalFraction | null = null,
belowMinimum: FiscalFraction | null = null;
let status:
| 'payable'
| 'recoverable'
| 'balanced'
| 'below_minimum'
| 'indeterminate' = 'indeterminate';
if (arithmeticBalance) {
const n = BigInt(arithmeticBalance.numerator),
abs = fraction(
n < 0n ? -n : n,
BigInt(arithmeticBalance.denominator),
);
payable = zero();
recoverable = zero();
belowMinimum = zero();
if (!n) status = 'balanced';
else if (cmp(abs, fraction(100n)) < 0n) {
status = 'below_minimum';
belowMinimum = abs;
warn.push(
'Art27(6):saldo inferior a100MZN,sem cobrança/reembolso neste recorte;aritmética conservada,não é transporte de crédito.',
);
} else if (n > 0n) {
status = 'payable';
payable = abs;
} else {
status = 'recoverable';
recoverable = abs;
}
}
return {
version: annualSettlementVersion,
input: structuredClone(input),
subjectNuit: b.subjectNuit,
year: b.year,
assessment: assessment?.record
? {
key: b.assessmentKey,
kind: b.assessment,
version: assessment.record.model.version,
createdAt: assessment.record.createdAt,
colecta,
period: assessment.period,
origin: assessment.record.model.origin,
}
: null,
claims,
...(economic ? { economicCredit: economic } : {}),
internationalCredit: international,
...(investment ? { investmentCredit: investment } : {}),
pecDeduction: pec,
...(carry
? {
creditCarry: {
...carry,
afterInternational: ready ? creditCollecta : null,
afterPec: ready ? afterPec : null,
},
}
: {}),
knownWithholdings,
knownPpc,
arithmeticBalance,
payable,
recoverable,
belowMinimum,
status,
pending: [...new Set(pending)],
warnings: warn,
source: annualSource(b),
officialBalancesConsumed: false,
submitted: false,
refundReceived: false,
limitations: [
...(b.economicCredits
? [
'Crédito económico: IRPC correspondente, inclusão/acréscimo53 e quota proporcional reconciliados com a colecta corrente. Partilha64(3) separa rendimento elegível e excesso com natureza de mais-valia; associações64(4) têm modalidade/adaptação próprias. Preparação L6 é explícita; exclusão40, benefícios e restantes regras mantêm âmbito próprio. Sem reembolso ou transporte económico presumidos.',
]
: []),
...(b.creditCarry
? [
'Saldos anteriores documentados: escolha explícita das parcelas correntes e anteriores, sem prioridade presumida entre exercícios, reembolso ou criação/consumo de saldos oficiais.',
]
: []),
...(b.internationalCredits
? [
(positions.some((p) => p.nationalDerivation)
? 'Crédito internacional do próprio exercício:fracção nacional derivada no caso L6 ordinário com parcela líquida correspondida;CDT e montantes MZN documentados;sem repartição presumida de custos/perdas,transporte ou consumo oficial. '
: 'Crédito internacional do próprio exercício:fracção nacional,convenção e montantes MZN são documentados;sem cálculo presumido,transporte ou consumo oficial. ') +
(b.economicCredits
? b.investmentCredit
? 'CBF15 do primeiro exercício no detalhe próprio;restantes benefícios positivos separados;crédito económico no detalhe próprio.'
: 'Benefícios positivos ficam separados;crédito económico no detalhe próprio.'
: b.investmentCredit
? 'CBF15 do primeiro exercício no detalhe próprio;restantes créditos económicos/benefícios positivos separados.'
: 'Créditos económicos/benefícios positivos ficam separados.'),
]
: []),
b.economicCredits
? 'L8 parcial:benefícios próprios,derivação automática de quotas,inclusão/correcção económica na base,liquidação/associações,obrigações,autónomos,períodos especiais,moedas eM20/M22 separados. Saldos anteriores só quando detalhados e documentados.'
: b.creditCarry
? 'L8 parcial:créditos económicos/benefícios,obrigações,tributação autónoma,períodos especiais,outras moedas e correspondência M20/M22 separados. Prazos dos saldos anteriores demonstrados apenas para origens anuais ordinárias documentadas;sem criação automática de transporte.'
: b.internationalCredits
? 'L8 parcial:créditos económicos/benefícios positivos,transporte/caducidade,obrigações,tributação autónoma,períodos especiais e outras moedas separados. PEC sem reembolso ou transporte automático.'
: b.pecPayments
? 'L8 parcial:transporte/caducidade de PEC, cálculo da obrigação, créditos próprios positivos, tributação autónoma, períodos especiais e outras moedas separados. Excedente PEC não é reembolso nem saldo transportado oficialmente.'
: 'L8 parcial:PEC e créditos positivos,tributação autónoma,períodos especiais e pagamentos noutra moeda exigem regras próprias.',
'PPC aqui é pagamento comprovado;não cálculo automático da obrigação ou calendário.',
'Retenção sofrida não é retenção entregue pela empresa por conta de terceiros.',
'Valores racionais exactos;camposM20/M22 e representação não presumidos.',
'Saldo simulado por colecta projectada;sem pagamento,submissão ou reembolso automático.',
],
};
}
export type AnnualModel = ReturnType<typeof calculateAnnual>;
export function recordAnnual(
d: Record<string, unknown>,
b: AnnualBook,
now = new Date().toISOString(),
): AnnualBook {
return {
...b,
history: b.result
? [...b.history, structuredClone(b.result)]
: b.history,
result: {
version: annualSettlementVersion,
key: key(d, b),
createdAt: now,
model: calculateAnnual(d, b),
},
};
}
const show = (v: FiscalFraction | null) =>
v
? (fractionPresentation(v).approximate ? '≈' : '') +
fractionPresentation(v).decimal +
' MZN (exacto ' +
v.numerator +
'/' +
v.denominator +
')'
: 'Indeterminado';
const stateLabel = (s: State) =>
s === 'known'
? 'Valor conhecido'
: s === 'absent'
? 'Ausência declarada'
: 'Informação em falta';
export function annualReportRows(
d: Record<string, unknown>,
): Array<[string, string, string, string]> {
try {
const b = annualBook(d),
r = b?.result;
if (!b || !r) return [];
const m = r.model;
const status = {
payable: 'A pagar',
recoverable: 'A recuperar',
balanced: 'Saldo zero',
below_minimum: 'Inferior ao mínimo de cobrança/reembolso',
indeterminate: 'Indeterminado',
}[m.status];
return [
[
'IRPC — L8',
'Fecho anual — âmbito e origem',
'NUIT: ' +
m.subjectNuit +
'; exercício: ' +
m.year +
'; colecta: ' +
show(m.assessment?.colecta ?? null) +
'; origem: ' +
(m.assessment?.kind === 'special'
? 'L7 qualificado'
: 'L6 normal') +
'. ' +
(annualCurrent(d, b)
? 'Entradas correspondentes.'
: 'Histórico; origem/entradas alteradas: recalcule o saldo corrente.') +
' Saldo simulado, não liquidação submetida.',
m.input.reference +
'; ' +
m.input.assessmentReference +
'; ' +
m.source,
],
...m.claims.map(
(c) =>
[
'IRPC — L8',
(c.kind === 'withholding'
? 'Retenção sofrida'
: c.kind === 'pec'
? 'PEC'
: c.kind === 'economic_credit'
? 'IRPC correspondente aos lucros'
: c.kind === 'foreign_credit'
? 'Imposto estrangeiro'
: 'PPC') +
' — ' +
(c.document || c.id),
'Entidade: ' +
c.subjectNuit +
'; exercício: ' +
c.year +
'; emitente: ' +
c.issuer +
'; data: ' +
c.date +
'; comprovado: ' +
show(c.proofAmount) +
'; parcela: ' +
(c.reason ? 'Indeterminado' : show(c.value)) +
(c.parcel
? '; parcela documental: ' +
c.parcel +
'; ' +
(c.parcelReference ?? '')
: '') +
'. ' +
(c.reason ||
'Reconciliada sem utilização duplicada.'),
c.kind === 'economic_credit'
? (m.input.economicCredits?.reference ?? '') +
'; ' +
(m.input.economicCredits?.rows.find(
(r) => r.id === c.id,
)?.attributionReference ?? '')
: c.kind === 'withholding'
? m.input.withholdings.reference +
'; ' +
(m.input.withholdings.rows.find(
(r) => r.id === c.id,
)?.reference ?? '')
: c.kind === 'foreign_credit'
? (m.input.internationalCredits?.reference ??
'') +
'; ' +
(m.input.internationalCredits?.positions
.flatMap((p) => p.payments)
.find((r) => r.id === c.id)?.reference ??
'') +
'; ' +
(m.input.internationalCredits?.positions
.flatMap((p) => p.payments)
.find((r) => r.id === c.id)
?.exerciseReference ?? '')
: c.kind === 'pec'
? (m.input.pecPayments?.reference ?? '') +
'; ' +
(m.input.pecPayments?.rows.find(
(r) => r.id === c.id,
)?.reference ?? '') +
'; utilizações anteriores: ' +
(m.input.pecPayments?.rows.find(
(r) => r.id === c.id,
)?.previouslyUsed ?? '') +
' MZN; ' +
(m.input.pecPayments?.rows.find(
(r) => r.id === c.id,
)?.priorUseReference ?? '')
: m.input.ppc.reference +
'; ' +
(m.input.ppc.rows.find((r) => r.id === c.id)
?.reference ?? ''),
] as [string, string, string, string],
),
...(m.economicCredit
? [
...m.economicCredit.rows.map(
(r) =>
[
'IRPC — L8',
'Crédito económico — ' +
r.input.distributionDocument,
'Lucros incluídos: ' +
r.input.income +
' MZN;IRPC correspondente: ' +
r.input.taxAmount +
' MZN;crédito nominal60%: ' +
show(r.nominal) +
';acréscimo53 incluído: ' +
r.input.grossUpIncluded +
' MZN;quota proporcional: ' +
show(r.nationalLimit) +
(r.nationalDerivation
? '; derivação L6: colecta ' +
show(r.nationalDerivation.colecta) +
' × lucros mais acréscimo53 ' +
show(
r.nationalDerivation.included,
) +
' / matéria colectável ' +
show(
r.nationalDerivation
.taxableBase,
) +
'; exacta ' +
r.nationalLimit?.numerator +
'/' +
r.nationalLimit?.denominator
: '') +
(r.liquidation
? '; partilha64(3): valor atribuído ' +
show(r.liquidation.attributed) +
'; entradas efectivas ' +
show(r.liquidation.paidInCapital) +
'; limite ' +
show(r.liquidation.ceiling) +
'; rendimento elegível ' +
show(r.liquidation.eligibleIncome) +
'; excesso com natureza de mais-valia ' +
show(
r.liquidation.excessCapitalGain,
) +
'; ' +
r.liquidation.version +
'; ' +
r.liquidation.reference
: '') +
(r.association
? '; ' +
r.association.version +
'; modalidade ' +
r.association.kind +
'; tributação efectiva ' +
r.association.effectivelyTaxed +
'; ' +
r.association.reference
: '') +
';elegível: ' +
show(r.eligible) +
';uso efectivo anterior: ' +
show(r.previousUse) +
';disponível: ' +
show(r.available) +
';estado: ' +
r.status +
'. ' +
r.pending.join(' | '),
r.input.qualificationReference +
';40(1): ' +
r.input.article40Reference +
';inclusão: ' +
r.input.incomeReference +
';53: ' +
r.input.grossUpReference +
';quota: ' +
(r.nationalDerivation
? r.nationalDerivation.version +
'; correspondência: ' +
r.nationalDerivation.reference +
'; limite manual anterior conservado, não utilizado'
: r.input.nationalLimitReference) +
';uso: ' +
r.input.previousUse.reference,
] as [string, string, string, string],
),
[
'IRPC — L8',
'Crédito económico — antes do internacional',
'Disponível: ' +
show(m.economicCredit.available) +
';dedução: ' +
show(m.economicCredit.deducted) +
';colecta após crédito económico: ' +
show(m.economicCredit.afterCredit) +
'. Sem reembolso ou criação/consumo de saldo oficial.',
m.economicCredit.source +
'; ' +
(m.input.economicCredits?.reference ?? ''),
] as [string, string, string, string],
]
: []),
...(m.internationalCredit
? [
...m.internationalCredit.positions.map(
(p) =>
[
'IRPC — L8',
'Crédito internacional — ' + p.country,
'Imposto pago seleccionado: ' +
show(p.paid) +
'; fracção nacional: ' +
show(p.nationalLimit) +
(p.nationalDerivation
? '; derivação L6: colecta ' +
show(p.nationalDerivation.colecta) +
' × parcela líquida ' +
show(p.nationalDerivation.income) +
' / matéria colectável ' +
show(
p.nationalDerivation
.taxableBase,
) +
'; exacta ' +
p.nationalLimit?.numerator +
'/' +
p.nationalLimit?.denominator
: '; limite documentado') +
'; limite CDT: ' +
(p.input.treaty === 'no'
? 'Não aplicável documentado'
: show(p.treatyLimit)) +
(p.treatyDerivation
? '; ' +
p.treatyDerivation.version +
'; bruto ' +
show(p.treatyDerivation.gross) +
' × ' +
p.treatyDerivation.rate +
'%; período ' +
p.treatyDerivation.from +
' a ' +
p.treatyDerivation.to +
'; ' +
p.treatyDerivation.reference +
'; ' +
p.treatyDerivation.source
: '') +
'; elegível: ' +
show(p.eligible) +
'; utilização anterior: ' +
show(p.previousUse) +
'; disponível: ' +
show(p.available) +
'; imposto acima dos limites: ' +
show(p.aboveLimit) +
'. ' +
p.pending.join(' | '),
p.input.incomeReference +
'; ' +
(p.nationalDerivation
? 'Limite manual anterior conservado, não utilizado nesta derivação'
: p.input.nationalLimitReference) +
(p.nationalDerivation
? '; correspondência: ' +
p.nationalDerivation.reference +
'; ' +
p.nationalDerivation.version
: '') +
'; ' +
p.input.treatyReference +
'; ' +
p.input.previousUse.reference,
] as [string, string, string, string],
),
[
'IRPC — L8',
'Crédito internacional — antes do PEC',
'Disponível conhecido: ' +
show(m.internationalCredit.available) +
'; dedução: ' +
show(m.internationalCredit.deducted) +
'; colecta após crédito: ' +
show(m.internationalCredit.afterCredit) +
'; elegível não deduzido por insuficiência de colecta: ' +
show(m.internationalCredit.insufficientCollecta) +
(m.creditCarry
? '; crédito corrente não escolhido: ' +
show(
m.internationalCredit.notSelected ??
null,
)
: '') +
'. Sem reembolso,transporte ou afectação oficial por país. Estado: ' +
stateLabel(
m.input.internationalCredits?.state ?? '',
) +
'. Créditos económicos/benefícios: ' +
stateLabel(
m.input.internationalCredits?.economic
.state ?? '',
) +
' / ' +
stateLabel(
m.input.internationalCredits?.benefits
.state ?? '',
) +
(m.investmentCredit
? '. Resumo anterior dos benefícios conservado, não reaplicado; CBF15 no detalhe próprio.'
: '.'),
m.internationalCredit.source +
'; ' +
(m.input.internationalCredits?.reference ?? '') +
'; ' +
(m.input.internationalCredits?.economic
.reference ?? '') +
'; ' +
(m.input.internationalCredits?.benefits
.reference ?? ''),
] as [string, string, string, string],
]
: []),
...(m.creditCarry
? [
...m.creditCarry.lots.map(
(l) =>
[
'IRPC — L8',
'Saldo anterior — ' +
(l.kind === 'pec'
? 'PEC'
: 'Crédito internacional ' +
l.country) +
' — ' +
l.year,
'Remanescente da origem: ' +
show(l.opening) +
'; usos efectivos: ' +
show(l.used) +
'; disponível: ' +
show(l.available) +
'; parcela escolhida: ' +
show(l.selected) +
'; saldo aritmético posterior: ' +
show(l.remaining) +
'; último exercício ordinário: ' +
(l.lastYear ?? 'Indeterminado') +
'; prazo terminado: ' +
(l.expired ? 'Sim' : 'Não') +
'. ' +
l.pending.join(' | '),
l.input.originDocument +
'; ' +
l.input.originReference +
'; ' +
l.input.ruleReference +
'; ' +
l.input.usesReference +
'; ' +
l.input.uses
.map(
(u) =>
u.year +
': ' +
u.amount +
' MZN; ' +
u.document +
'; ' +
u.reference,
)
.join(' | '),
] as [string, string, string, string],
),
[
'IRPC — L8',
'Afectação escolhida entre exercícios',
'Corrente internacional: ' +
show(m.creditCarry.international) +
'; anterior internacional: ' +
show(m.creditCarry.priorInternational) +
'; PEC corrente: ' +
show(m.creditCarry.pec) +
'; PEC anterior: ' +
show(m.creditCarry.priorPec) +
'; colecta após créditos internacionais: ' +
show(m.creditCarry.afterInternational) +
'; colecta após todos os PEC: ' +
show(m.creditCarry.afterPec) +
'. Parcelas só deduzidas se o saldo estiver determinado. Sem criação ou consumo de saldo oficial. ' +
m.creditCarry.pending.join(' | '),
(m.input.creditCarry?.reference ?? '') +
'; ' +
(m.input.creditCarry?.allocationReference ?? '') +
'; ' +
m.creditCarry.source,
] as [string, string, string, string],
]
: []),
...(m.investmentCredit
? [
...m.investmentCredit.rows.map(
(r) =>
[
'IRPC — L8',
'CBF15 — ' +
r.input.issuer +
' / ' +
r.input.document +
' / ' +
r.input.parcel +
'; data ' +
r.input.date,
'Valor declarado: ' +
r.input.amount +
' ' +
r.input.currency +
'; investimento elegível: ' +
show(r.value),
r.input.reference +
'; ' +
r.reason +
'; parcela: ' +
r.input.parcelReference +
'; ' +
r.pending.join(' | '),
] as [string, string, string, string],
),
[
'IRPC — L8',
'CBF15 — crédito fiscal por investimento',
'Investimento: ' +
show(m.investmentCredit.investment) +
'; taxa ' +
(m.investmentCredit.rate === null
? 'não aplicável'
: m.investmentCredit.rate + '%') +
'; nominal ' +
show(m.investmentCredit.nominal) +
'; uso anterior ' +
show(m.investmentCredit.priorUse) +
(m.investmentCredit.input.carryForward
? '; TransporteCBF15/1: nominal corrente ' +
show(
m.investmentCredit.currentNominal ??
null,
) +
'; nominal de origem anterior ' +
show(
m.investmentCredit.carryNominal ?? null,
) +
'; parcela expirada ' +
show(m.investmentCredit.expired ?? null) +
'; ' +
m.investmentCredit.input.carryForward
.reference +
'; investimento corrente ' +
m.investmentCredit.input.carryForward
.currentState +
': ' +
m.investmentCredit.input.carryForward
.currentReference
: '') +
'; disponível ' +
show(m.investmentCredit.available) +
'; dedução ' +
show(m.investmentCredit.deducted) +
'; sem colecta neste exercício ' +
show(m.investmentCredit.unused) +
(m.investmentCredit.projectCeiling
? '; quota documental do projecto ' +
show(m.investmentCredit.projectCeiling)
: '') +
'; colecta após benefício ' +
show(m.investmentCredit.afterCredit),
m.investmentCredit.version +
'; ' +
m.investmentCredit.source +
(m.investmentCredit.window
? '; JanelaCBF15/1: exercícios ' +
m.investmentCredit.window.firstYear +
'–' +
m.investmentCredit.window.lastYear +
'; exercício ' +
m.investmentCredit.window.exercise +
'/5; ' +
(m.investmentCredit.window.status ===
'expired'
? 'prazo expirado'
: 'dentro do prazo')
: '') +
'; projecto ' +
m.investmentCredit.input.projectReference +
'; ' +
m.investmentCredit.input.qualification.reference +
'; período ' +
m.investmentCredit.input.qualification.from +
' a ' +
m.investmentCredit.input.qualification.to +
'; ' +
m.investmentCredit.input.qualification
.periodReference +
'; ' +
m.investmentCredit.input.locationReference +
'; ' +
'início ' +
m.investmentCredit.input.startDate +
'; ' +
m.investmentCredit.input.startReference +
'; reconhecimento ' +
m.investmentCredit.input.recognitionReference +
'; exclusividade ' +
(m.investmentCredit.input.soleProject === 'no'
? m.investmentCredit.input.projectQuota
?.reference
: m.investmentCredit.input
.soleProjectReference) +
'; outros benefícios: ' +
m.investmentCredit.input
.noOtherBenefitsReference +
(m.investmentCredit.input.carryForward
? '; origens: ' +
m.investmentCredit.input.assets
.filter(
(r) => r.originYear !== undefined,
)
.map(
(r) =>
r.issuer +
'/' +
r.document +
'/' +
r.parcel +
': exercício ' +
r.originYear +
'; NUIT ' +
r.originNuit +
'; projecto ' +
r.originProject +
', taxa ' +
r.originRate +
'%; ' +
r.originReference,
)
.join(' | ')
: '') +
'; uso anterior: ' +
m.investmentCredit.input.previousUse.reference +
'; ' +
m.investmentCredit.pending.join(' | '),
] as [string, string, string, string],
]
: []),
...(m.pecDeduction
? [
[
'IRPC — L8',
'PEC — dedução limitada à colecta',
'Estado: ' +
stateLabel(m.input.pecPayments?.state ?? '') +
'; parcelas disponíveis: ' +
show(m.pecDeduction.available) +
'; dedução: ' +
show(m.pecDeduction.deducted) +
'; colecta após PEC, antes de retenções/PPC: ' +
show(m.pecDeduction.afterPec) +
'; parcela seleccionada não deduzida: ' +
show(m.pecDeduction.remaining) +
'. Excedente sem reembolso PEC; transporte/caducidade não automáticos. Subtotais não resolvem informação em falta.',
(m.input.pecPayments?.reference ?? '') +
'; ' +
m.pecDeduction.source,
] as [string, string, string, string],
]
: []),
[
'IRPC — L8',
'Saldo anual simulado',
'Estado: ' +
status +
'. Retenções conhecidas: ' +
show(m.knownWithholdings) +
'; PPC conhecidos: ' +
show(m.knownPpc) +
'; saldo aritmético: ' +
show(m.arithmeticBalance) +
'; a pagar: ' +
show(m.payable) +
'; a recuperar: ' +
show(m.recoverable) +
'. ' +
m.pending.join(' | ') +
' ' +
m.warnings.join(' '),
m.source,
],
[
'IRPC — L8',
'Estados dos comprovativos',
'Retenções sofridas: ' +
stateLabel(m.input.withholdings.state) +
'; PPC: ' +
stateLabel(m.input.ppc.state) +
'. Subtotais conhecidos não substituem informação em falta.',
m.input.withholdings.reference + '; ' + m.input.ppc.reference,
],
[
'IRPC — L8',
'PEC e créditos separados',
(m.input.pecPayments
? 'Declaração PEC anterior conservada (substituída nesta execução pelo detalhe comprovado): '
: 'PEC: ') +
stateLabel(m.input.pec.state) +
'; montante declarado: ' +
(m.input.pec.amount || 'Não preenchido') +
(m.input.internationalCredits
? '; resumo anterior de créditos conservado,substituído nesta execução pelo detalhe: '
: '; créditos: ') +
stateLabel(m.input.credits.state) +
'; montante declarado: ' +
(m.input.credits.amount || 'Não preenchido') +
'. Sem dedução, transporte ou reembolso presumidos. ' +
m.limitations.join(' '),
m.input.pec.reference + '; ' + m.input.credits.reference,
],
];
} catch (e) {
return [
[
'IRPC — L8',
'Não recuperável',
String(e),
'Originais e históricos conservados.',
],
];
}
}
export function annualHtml(d: Record<string, unknown>) {
const b = annualBook(d);
if (!b?.result || !annualCurrent(d, b))
throw Error(
'Calcule e guardeL8 com as fontes actuais antes de exportar.',
);
const esc = (s: string) =>
s.replace(
/[&<>"']/g,
(c) =>
({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
})[c]!,
);
return (
'<!doctype html><html lang="pt"><meta charset="utf-8"><title>IRPC — L8 saldo anual simulado</title><h1>IRPC — colecta e saldo anual simulado</h1><p>Não é declaração submetida,pagamento efectuado ou reembolso recebido.</p>' +
annualReportRows(d)
.map(
(r) =>
'<section><h2>' +
esc(r[1]) +
'</h2><p>' +
esc(r[2]) +
'</p><small>' +
esc(r[3]) +
'</small></section>',
)
.join('') +
'</html>'
);
}