654 lines
24 KiB
TypeScript
654 lines
24 KiB
TypeScript
import { inputText } from './input-text.ts';
|
||
import {
|
||
closureBook,
|
||
closureSources,
|
||
closureFingerprint,
|
||
type ClosureSource,
|
||
type ClosurePayment,
|
||
} from './iva-closure.ts';
|
||
import {
|
||
addFractions,
|
||
subtractFractions,
|
||
positiveFraction,
|
||
decimalFraction,
|
||
fraction,
|
||
fractionPresentation,
|
||
type FiscalFraction,
|
||
} from './fiscal-rational.ts';
|
||
|
||
export const substitutionSource =
|
||
'L3-C: VAT Return.pdf SHA2569c0b99a8,p2/quadro9;RCIVA15,Decreto7/2008 PDF3/p101.21=(17actual−18actual)−(17anterior−18anterior);devedor→22;credor só utilizável em declaração posterior em prazo após comunicação20. Sem repetir19/20 ou pagamentos anteriores. Exemplar recebido assinala substituiçãoModeloA;RCIVA15 refereModeloB:correspondência de formulário/submissão não automatizada.';
|
||
export type PriorDeclaration = {
|
||
kind: 'AB' | 'C';
|
||
id: string;
|
||
period: string;
|
||
entityRef: string;
|
||
origin: 'projected' | 'effective';
|
||
calculatedAt: string;
|
||
declarationDate: string;
|
||
declarationReference: string;
|
||
fingerprint: string;
|
||
debit: FiscalFraction;
|
||
credit: FiscalFraction;
|
||
paid: FiscalFraction;
|
||
outstanding: FiscalFraction;
|
||
carry: FiscalFraction;
|
||
paymentRefs: string[];
|
||
};
|
||
export type SubstitutionInput = {
|
||
id: string;
|
||
prior: PriorDeclaration | null;
|
||
source: ClosureSource | null;
|
||
priorReference: string;
|
||
priorDate: string;
|
||
date: string;
|
||
reference: string;
|
||
reason: string;
|
||
stage: 'preparation' | 'presented';
|
||
interest: '' | 'none' | 'documented';
|
||
interestAmount: string;
|
||
interestRef: string;
|
||
payments: ClosurePayment[];
|
||
};
|
||
export type SubstitutionResult = {
|
||
id: string;
|
||
status: 'determined' | 'indeterminate';
|
||
reason: string;
|
||
inputKey: string;
|
||
fields: Record<string, FiscalFraction | null>;
|
||
pendingCredit: FiscalFraction | null;
|
||
paid: FiscalFraction | null;
|
||
outstanding: FiscalFraction | null;
|
||
overpayment: FiscalFraction | null;
|
||
totalPaid: FiscalFraction | null;
|
||
totalOutstanding: FiscalFraction | null;
|
||
source: string;
|
||
};
|
||
export type SubstitutionCalculation = {
|
||
calculatedAt: string;
|
||
key: string;
|
||
inputs: SubstitutionInput[];
|
||
rows: SubstitutionResult[];
|
||
};
|
||
export type SubstitutionBook = {
|
||
version: 1;
|
||
rows: SubstitutionInput[];
|
||
result?: SubstitutionCalculation;
|
||
history?: SubstitutionCalculation[];
|
||
};
|
||
const zero = () => fraction(0n),
|
||
scope = (d: Record<string, unknown>) =>
|
||
inputText(d.context_opportunity || 'general'),
|
||
norm = (s: string) => s.trim().toLocaleLowerCase();
|
||
const date = (v: string) =>
|
||
/^20\d{2}-(0[1-9]|1[0-2])-\d{2}$/.test(v) &&
|
||
!Number.isNaN(Date.parse(v)) &&
|
||
new Date(v).toISOString().slice(0, 10) === v;
|
||
function store(d: Record<string, unknown>): Record<string, unknown> {
|
||
const s = JSON.parse(
|
||
inputText(d.modelo_a_snapshot || '{"version":1,"books":{}}'),
|
||
);
|
||
if (s.version !== 1 || !s.books)
|
||
throw Error('Livros fiscais não recuperáveis;originais preservados.');
|
||
return s;
|
||
}
|
||
function maps(d: Record<string, unknown>): Record<string, SubstitutionBook> {
|
||
const s = store(d).ivaSubstitutions;
|
||
if (s === undefined) return {};
|
||
if (!s || typeof s !== 'object' || Array.isArray(s))
|
||
throw Error('Substituições não recuperáveis;originais preservados.');
|
||
return s as Record<string, SubstitutionBook>;
|
||
}
|
||
function validate(b: SubstitutionBook): void {
|
||
if (
|
||
!b ||
|
||
b.version !== 1 ||
|
||
!Array.isArray(b.rows) ||
|
||
(b.history !== undefined && !Array.isArray(b.history)) ||
|
||
(b.result !== undefined &&
|
||
(!Array.isArray(b.result.rows) || !Array.isArray(b.result.inputs)))
|
||
)
|
||
throw Error('Livro de substituições não recuperável;sem o substituir.');
|
||
for (const r of b.rows)
|
||
if (
|
||
!r ||
|
||
![
|
||
'id',
|
||
'priorReference',
|
||
'priorDate',
|
||
'date',
|
||
'reference',
|
||
'reason',
|
||
'interestAmount',
|
||
'interestRef',
|
||
].every(
|
||
(k) => typeof r[k as keyof SubstitutionInput] === 'string',
|
||
) ||
|
||
!['preparation', 'presented'].includes(r.stage) ||
|
||
!['', 'none', 'documented'].includes(r.interest) ||
|
||
!Array.isArray(r.payments) ||
|
||
r.payments.some(
|
||
(p) =>
|
||
!p ||
|
||
!['id', 'reference', 'date', 'amount'].every(
|
||
(k) => typeof p[k as keyof ClosurePayment] === 'string',
|
||
),
|
||
)
|
||
)
|
||
throw Error(
|
||
'Entradas da substituição não recuperáveis;originais preservados.',
|
||
);
|
||
}
|
||
export function substitutionBook(d: Record<string, unknown>): SubstitutionBook {
|
||
const b = maps(d)[scope(d)] ?? { version: 1, rows: [] };
|
||
validate(b);
|
||
return b;
|
||
}
|
||
export function saveSubstitutionBook(
|
||
d: Record<string, unknown>,
|
||
b: SubstitutionBook,
|
||
): string {
|
||
validate(b);
|
||
const s = store(d);
|
||
return JSON.stringify({
|
||
...s,
|
||
ivaSubstitutions: { ...maps(d), [scope(d)]: b },
|
||
});
|
||
}
|
||
export function newSubstitution(id: string): SubstitutionInput {
|
||
return {
|
||
id,
|
||
prior: null,
|
||
source: null,
|
||
priorReference: '',
|
||
priorDate: '',
|
||
date: '',
|
||
reference: '',
|
||
reason: '',
|
||
stage: 'preparation',
|
||
interest: '',
|
||
interestAmount: '',
|
||
interestRef: '',
|
||
payments: [],
|
||
};
|
||
}
|
||
export function previousDeclarations(
|
||
d: Record<string, unknown>,
|
||
b = substitutionBook(d),
|
||
): PriorDeclaration[] {
|
||
const ab = closureBook(d),
|
||
out: PriorDeclaration[] = [];
|
||
for (const calc of [
|
||
...(ab.history ?? []),
|
||
...(ab.result ? [ab.result] : []),
|
||
])
|
||
for (const r of calc.rows) {
|
||
const input = calc.inputs.find((i) => i.id === r.id);
|
||
if (
|
||
r.status !== 'determined' ||
|
||
!input ||
|
||
!r.fields['17'] ||
|
||
!r.fields['18'] ||
|
||
!r.fields['26'] ||
|
||
!r.paid ||
|
||
!r.outstanding
|
||
)
|
||
continue;
|
||
out.push({
|
||
kind: 'AB',
|
||
id: r.id,
|
||
period: r.period,
|
||
entityRef: r.entityRef,
|
||
origin: r.origin,
|
||
calculatedAt: calc.calculatedAt,
|
||
declarationDate: input.submissionDate,
|
||
declarationReference: '',
|
||
fingerprint: closureFingerprint([input, r]),
|
||
debit: r.fields['17'],
|
||
credit: r.fields['18'],
|
||
paid: r.paid,
|
||
outstanding: r.outstanding,
|
||
carry: r.fields['26'],
|
||
paymentRefs: input.payments.map((p) => p.reference),
|
||
});
|
||
}
|
||
for (const calc of [...(b.history ?? []), ...(b.result ? [b.result] : [])])
|
||
for (const r of calc.rows) {
|
||
const input = calc.inputs.find((i) => i.id === r.id);
|
||
if (
|
||
r.status !== 'determined' ||
|
||
!input?.prior ||
|
||
!input.source ||
|
||
!r.totalPaid ||
|
||
!r.totalOutstanding
|
||
)
|
||
continue;
|
||
out.push({
|
||
kind: 'C',
|
||
id: r.id,
|
||
period: input.prior.period,
|
||
entityRef: input.prior.entityRef,
|
||
origin: input.prior.origin,
|
||
calculatedAt: calc.calculatedAt,
|
||
declarationDate: input.date,
|
||
declarationReference: input.reference,
|
||
fingerprint: closureFingerprint([input, r]),
|
||
debit: input.source.debit!,
|
||
credit: input.source.credit!,
|
||
paid: r.totalPaid,
|
||
outstanding: r.totalOutstanding,
|
||
carry: input.prior.carry,
|
||
paymentRefs: [
|
||
...input.prior.paymentRefs,
|
||
...input.payments.map((p) => p.reference),
|
||
],
|
||
});
|
||
}
|
||
return out.filter(
|
||
(r, i, a) =>
|
||
a.findIndex(
|
||
(s) =>
|
||
s.kind === r.kind &&
|
||
s.id === r.id &&
|
||
s.fingerprint === r.fingerprint,
|
||
) === i,
|
||
);
|
||
}
|
||
function available(
|
||
d: Record<string, unknown>,
|
||
b: SubstitutionBook,
|
||
r: SubstitutionInput,
|
||
) {
|
||
return [
|
||
previousDeclarations(d, b).some(
|
||
(p) => JSON.stringify(p) === JSON.stringify(r.prior),
|
||
),
|
||
closureSources(d).some(
|
||
(s) => JSON.stringify(s) === JSON.stringify(r.source),
|
||
),
|
||
];
|
||
}
|
||
function pastPaymentUses(
|
||
d: Record<string, unknown>,
|
||
b: SubstitutionBook,
|
||
): Array<{ id: string; entity: string; origin: string; reference: string }> {
|
||
return Object.entries({ ...maps(d), [scope(d)]: b }).flatMap(
|
||
([context, book]) =>
|
||
[
|
||
...(book.history ?? []),
|
||
...(book.result ? [book.result] : []),
|
||
].flatMap((calc) =>
|
||
calc.inputs
|
||
.filter(
|
||
(r) =>
|
||
r.prior &&
|
||
(context === scope(d) ||
|
||
r.prior.origin === 'effective') &&
|
||
calc.rows.some(
|
||
(v) =>
|
||
v.id === r.id &&
|
||
v.status === 'determined' &&
|
||
v.paid,
|
||
),
|
||
)
|
||
.flatMap((r) =>
|
||
r.payments.map((p) => ({
|
||
id: r.id,
|
||
entity: norm(r.prior!.entityRef),
|
||
origin: r.prior!.origin,
|
||
reference: norm(p.reference),
|
||
})),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
function abPaymentKeys(d: Record<string, unknown>): string[] {
|
||
const ab = store(d).ivaClosures as
|
||
| Record<string, ReturnType<typeof closureBook>>
|
||
| undefined;
|
||
return Object.entries(ab ?? {}).flatMap(([id, v]) =>
|
||
[
|
||
...v.rows,
|
||
...[...(v.history ?? []), ...(v.result ? [v.result] : [])].flatMap(
|
||
(c) => c.inputs,
|
||
),
|
||
]
|
||
.filter((r) => id === scope(d) || r.origin === 'effective')
|
||
.flatMap((r) =>
|
||
r.payments.map((p) =>
|
||
JSON.stringify([
|
||
norm(r.entityRef),
|
||
r.origin,
|
||
norm(p.reference),
|
||
]),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
function key(d: Record<string, unknown>, b: SubstitutionBook) {
|
||
const ab = abPaymentKeys(d),
|
||
past = pastPaymentUses(d, b);
|
||
return closureFingerprint([
|
||
b.rows,
|
||
b.rows.map((r) => available(d, b, r)),
|
||
uses(d, b).map((r) => [
|
||
r.prior?.entityRef,
|
||
r.prior?.origin,
|
||
r.prior?.kind,
|
||
r.prior?.id,
|
||
r.payments.map((p) => norm(p.reference)),
|
||
]),
|
||
b.rows.map((r) =>
|
||
r.payments.map(
|
||
(p) =>
|
||
!!r.prior &&
|
||
(ab.includes(
|
||
JSON.stringify([
|
||
norm(r.prior.entityRef),
|
||
r.prior.origin,
|
||
norm(p.reference),
|
||
]),
|
||
) ||
|
||
past.some(
|
||
(v) =>
|
||
v.id !== r.id &&
|
||
v.entity === norm(r.prior!.entityRef) &&
|
||
v.origin === r.prior!.origin &&
|
||
v.reference === norm(p.reference),
|
||
)),
|
||
),
|
||
),
|
||
]);
|
||
}
|
||
|
||
export function substitutionCurrent(
|
||
d: Record<string, unknown>,
|
||
b: SubstitutionBook,
|
||
): boolean {
|
||
return (
|
||
!!b.result &&
|
||
b.result.key === key(d, b) &&
|
||
b.result.rows.length === b.rows.length &&
|
||
b.rows.every((r) =>
|
||
b.result!.rows.some(
|
||
(s) => s.id === r.id && s.inputKey === closureFingerprint(r),
|
||
),
|
||
)
|
||
);
|
||
}
|
||
function uses(
|
||
d: Record<string, unknown>,
|
||
b: SubstitutionBook,
|
||
): SubstitutionInput[] {
|
||
return [
|
||
...b.rows,
|
||
...Object.entries(maps(d))
|
||
.filter(([id]) => id !== scope(d))
|
||
.flatMap(([, v]) => {
|
||
validate(v);
|
||
return v.rows.filter((r) => r.prior?.origin === 'effective');
|
||
}),
|
||
];
|
||
}
|
||
export function recordSubstitution(
|
||
d: Record<string, unknown>,
|
||
b: SubstitutionBook,
|
||
now = new Date().toISOString(),
|
||
): SubstitutionBook {
|
||
validate(b);
|
||
if (new Set(b.rows.map((r) => r.id)).size !== b.rows.length)
|
||
throw Error('Identidade de substituição repetida.');
|
||
const all = uses(d, b),
|
||
paymentsAB = abPaymentKeys(d);
|
||
const rows: SubstitutionResult[] = b.rows.map((row) => {
|
||
const fields: Record<string, FiscalFraction | null> =
|
||
Object.fromEntries(
|
||
[
|
||
'17',
|
||
'18',
|
||
'19',
|
||
'20',
|
||
'21',
|
||
'22',
|
||
'23',
|
||
'24',
|
||
'25',
|
||
'26',
|
||
'27',
|
||
].map((k) => [k, null]),
|
||
);
|
||
const result: SubstitutionResult = {
|
||
id: row.id,
|
||
status: 'indeterminate',
|
||
reason: '',
|
||
inputKey: closureFingerprint(row),
|
||
fields,
|
||
pendingCredit: null,
|
||
paid: null,
|
||
outstanding: null,
|
||
overpayment: null,
|
||
totalPaid: null,
|
||
totalOutstanding: null,
|
||
source: substitutionSource,
|
||
};
|
||
const stop = (reason: string) => ({ ...result, reason });
|
||
const prior = row.prior,
|
||
source = row.source;
|
||
if (!prior || !source || !available(d, b, row).every(Boolean))
|
||
return stop(
|
||
'Escolha declaração anterior e resultadoL2 congelados recuperáveis;sem fabricar ou recalcular origens.',
|
||
);
|
||
if (
|
||
d.currency !== 'MZN' ||
|
||
source.currency !== 'MZN' ||
|
||
source.opportunity !== scope(d) ||
|
||
source.period !== prior.period ||
|
||
!/^2026-(0[1-9]|1[0-2])$/.test(prior.period) ||
|
||
!source.debit ||
|
||
!source.credit
|
||
)
|
||
return stop(
|
||
'A substituição exige mesmo âmbito e período2026/MZN,com17/18 determinados.',
|
||
);
|
||
if (
|
||
!prior.entityRef.trim() ||
|
||
(prior.origin === 'effective' && source.projected)
|
||
)
|
||
return stop(
|
||
'Origem projectada não serve como declaração efectiva;conservar pressupostos identificados.',
|
||
);
|
||
const seen = new Set([row.id]);
|
||
let ancestor: PriorDeclaration | null = prior;
|
||
while (ancestor?.kind === 'C') {
|
||
if (seen.has(ancestor.id)) break;
|
||
seen.add(ancestor.id);
|
||
ancestor = b.rows.find((r) => r.id === ancestor!.id)?.prior ?? null;
|
||
}
|
||
if (ancestor?.kind === 'C' && seen.has(ancestor.id))
|
||
return stop(
|
||
'Sequência circular de substituições;ligue cada versão à anterior sem ciclos.',
|
||
);
|
||
if (
|
||
all.filter(
|
||
(r) =>
|
||
r.prior &&
|
||
norm(r.prior.entityRef) === norm(prior.entityRef) &&
|
||
r.prior.origin === prior.origin &&
|
||
r.prior.kind === prior.kind &&
|
||
r.prior.id === prior.id,
|
||
).length > 1
|
||
)
|
||
return stop(
|
||
'Declaração anterior já ligada a outra substituição;reconcilie a sequência sem duplicar o diferencial.',
|
||
);
|
||
if (
|
||
!row.priorReference.trim() ||
|
||
row.priorDate !== prior.declarationDate ||
|
||
(prior.declarationReference &&
|
||
row.priorReference !== prior.declarationReference) ||
|
||
!date(row.priorDate) ||
|
||
row.priorDate.slice(0, 7) < prior.period ||
|
||
!date(row.date) ||
|
||
row.date < row.priorDate ||
|
||
!row.reference.trim() ||
|
||
!row.reason.trim()
|
||
)
|
||
return stop(
|
||
'Reutilize a data/referência disponível da versão anterior;identifique referência/data da declaração anterior e da substituição,com motivo;preparação não equivale a submissão.',
|
||
);
|
||
if (
|
||
BigInt(source.debit.numerator) < 0n ||
|
||
BigInt(source.credit.numerator) < 0n ||
|
||
(BigInt(source.debit.numerator) > 0n &&
|
||
BigInt(source.credit.numerator) > 0n)
|
||
)
|
||
return stop('Sinais17/18 incompatíveis na origemL2.');
|
||
fields['17'] = source.debit;
|
||
fields['18'] = source.credit;
|
||
fields['21'] = subtractFractions(
|
||
subtractFractions(source.debit, source.credit),
|
||
subtractFractions(prior.debit, prior.credit),
|
||
);
|
||
fields['22'] = positiveFraction(fields['21']);
|
||
result.pendingCredit = positiveFraction(
|
||
subtractFractions(zero(), fields['21']),
|
||
);
|
||
if (row.interest === 'none' && row.interestRef.trim())
|
||
fields['23'] = zero();
|
||
else if (row.interest === 'documented' && row.interestRef.trim())
|
||
try {
|
||
fields['23'] = decimalFraction(row.interestAmount);
|
||
} catch {
|
||
/* unknown preserved */
|
||
}
|
||
if (!fields['23'])
|
||
return stop(
|
||
'Juros adicionais por identificar;sem copiar juros anteriores ou presumir taxa/prazo.',
|
||
);
|
||
fields['24'] = addFractions([fields['22'], fields['23']]);
|
||
const paid: FiscalFraction[] = [];
|
||
for (const p of row.payments) {
|
||
const ident = JSON.stringify([
|
||
norm(prior.entityRef),
|
||
prior.origin,
|
||
norm(p.reference),
|
||
]);
|
||
if (
|
||
!p.reference.trim() ||
|
||
!date(p.date) ||
|
||
p.date < row.date ||
|
||
prior.paymentRefs.some(
|
||
(ref) => norm(ref) === norm(p.reference),
|
||
) ||
|
||
pastPaymentUses(d, b).some(
|
||
(v) =>
|
||
v.id !== row.id &&
|
||
v.entity === norm(prior.entityRef) &&
|
||
v.origin === prior.origin &&
|
||
v.reference === norm(p.reference),
|
||
) ||
|
||
paymentsAB.includes(ident) ||
|
||
all.flatMap((r) =>
|
||
r.payments.filter(
|
||
(p2) =>
|
||
r.prior &&
|
||
JSON.stringify([
|
||
norm(r.prior.entityRef),
|
||
r.prior.origin,
|
||
norm(p2.reference),
|
||
]) === ident,
|
||
),
|
||
).length > 1
|
||
)
|
||
return stop(
|
||
'Pagamento já registado na anterior/outra declaração,sem referência/data aplicável;registe só pagamentos adicionais.',
|
||
);
|
||
try {
|
||
paid.push(decimalFraction(p.amount));
|
||
} catch {
|
||
return stop(
|
||
'Montante adicional não identificado;sem baixa presumida.',
|
||
);
|
||
}
|
||
}
|
||
result.paid = addFractions(paid);
|
||
result.outstanding = positiveFraction(
|
||
subtractFractions(fields['24'], result.paid),
|
||
);
|
||
result.overpayment = positiveFraction(
|
||
subtractFractions(result.paid, fields['24']),
|
||
);
|
||
result.totalPaid = addFractions([prior.paid, result.paid]);
|
||
result.totalOutstanding = addFractions([
|
||
prior.outstanding,
|
||
result.outstanding,
|
||
]);
|
||
result.status = 'determined';
|
||
result.reason =
|
||
'Diferencial21 separado da declaração anterior;19/20 não reutilizados,25/26/27 não atribuídos por esta substituição. Crédito credor aguarda comunicação20;sem compensar dívida anterior automaticamente. Pagamentos adicionais e saldo anterior separados;sem submissãoAT ou certificação de cobrança. ' +
|
||
(row.stage === 'preparation'
|
||
? 'Preparação/simulação;referências e datas declaradas não comprovam entrega.'
|
||
: 'Apresentação registada pelo utilizador fora do InvesteMoz.');
|
||
return result;
|
||
});
|
||
const result = {
|
||
calculatedAt: now,
|
||
key: key(d, b),
|
||
inputs: structuredClone(b.rows),
|
||
rows,
|
||
};
|
||
return {
|
||
...b,
|
||
...(b.result
|
||
? { history: [...(b.history ?? []), structuredClone(b.result)] }
|
||
: {}),
|
||
result,
|
||
};
|
||
}
|
||
export function substitutionReportRows(
|
||
d: Record<string, unknown>,
|
||
): Array<[string, string, string, string]> {
|
||
try {
|
||
const b = substitutionBook(d);
|
||
if (!b.rows.length) return [];
|
||
const out: Array<[string, string, string, string]> = [
|
||
[
|
||
'IVA mensal',
|
||
'L3-C — substituições',
|
||
substitutionCurrent(d, b)
|
||
? 'Resultado correspondente às entradas'
|
||
: 'Histórico preservado;rever entradas/origens',
|
||
substitutionSource,
|
||
],
|
||
];
|
||
const fmt = (v: FiscalFraction | null | undefined) =>
|
||
v ? fractionPresentation(v).decimal : 'Indeterminado';
|
||
for (const r of b.result?.rows ?? []) {
|
||
const input = b.result!.inputs.find((i) => i.id === r.id),
|
||
p = input?.prior;
|
||
out.push([
|
||
'IVA mensal',
|
||
`Substituição — ${p?.period ?? 'por identificar'}`,
|
||
`21:${fmt(r.fields['21'])};22 adicional:${fmt(r.fields['22'])};23 adicional:${fmt(r.fields['23'])};24 adicional:${fmt(r.fields['24'])};crédito aguardando comunicação:${fmt(r.pendingCredit)}`,
|
||
`${p?.entityRef ?? ''};${scope(d)};MZN;${p?.origin === 'effective' ? 'Dados efectivos declarados' : 'Pressuposto de simulação'};anterior ${input?.priorReference ?? ''},${input?.priorDate ?? ''};substituição ${input?.reference ?? ''},${input?.date ?? ''};${input?.reason ?? ''};${r.reason}`,
|
||
]);
|
||
out.push([
|
||
'IVA mensal',
|
||
`Reconciliação da substituição — ${p?.period ?? ''}`,
|
||
`Anterior:pago${fmt(p?.paid)},por liquidar${fmt(p?.outstanding)},reporte histórico26:${fmt(p?.carry)};adicional:pago${fmt(r.paid)},por liquidar${fmt(r.outstanding)},excedente${fmt(r.overpayment)};total registado${fmt(r.totalPaid)},total por liquidar sem compensação${fmt(r.totalOutstanding)}`,
|
||
`Origem anterior${p?.kind}/${p?.id},versão${p?.calculatedAt};origemL2 revisão${input?.source?.revision},${input?.source?.calculatedAt};pagamentos adicionais:${input?.payments.map((p) => `${p.reference};${p.date};${p.amount}MZN`).join(' | ') || 'nenhum'};juros adicionais:${input?.interestRef ?? ''};histórico${b.result!.calculatedAt};${substitutionSource}`,
|
||
]);
|
||
}
|
||
return out;
|
||
} catch {
|
||
return [
|
||
[
|
||
'IVA mensal',
|
||
'L3-C',
|
||
'Substituição não recuperável',
|
||
'Originais preservados;sem diferencial/crédito presumidos.',
|
||
],
|
||
];
|
||
}
|
||
}
|