2388 lines
99 KiB
TypeScript
2388 lines
99 KiB
TypeScript
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) =>
|
||
({
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
'"': '"',
|
||
"'": ''',
|
||
})[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>'
|
||
);
|
||
}
|