641 lines
25 KiB
TypeScript
641 lines
25 KiB
TypeScript
import {
|
||
selectFx,
|
||
fxSelectionKey,
|
||
type FxSelection,
|
||
} from './iva-fx-selection.ts';
|
||
import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts';
|
||
import { convertCents as convertScaledAmount } from './business-financial-plan.ts';
|
||
export const currencyEventVersion = 'currency-events/1';
|
||
export const currencyEventSource =
|
||
'CIVA Lei13/2016,7(1)–(7),PDF8/p1426(266);8,PDF9/p1426(267);15(8),PDF14/p1426(272).Lei22/2022 eLei10/2025 não alteram7/8,15(8)mantido.RIVA21/2008PDF4:regra histórica5dias úteis,não prazo automático vigente;usar prazo e disposição aplicáveis identificados. P104:orientação operacional da Tempo25/10/2026 compra/venda/médiaBM conforme natureza e perspectiva;data de submissão indicada;excepções fiscais identificadas. Cenários históricos permanecem condicionados;sem base/IVA/dedução automaticamente activados.';
|
||
export const currencyEventTreatments = [
|
||
{
|
||
id: 'foreign_scenario',
|
||
branch: 'P104',
|
||
label: 'Moeda estrangeira — selecção operacional / cenário histórico',
|
||
},
|
||
{
|
||
id: 'goods_available',
|
||
branch: 'P131',
|
||
label: 'Bens postos à disposição7(1)a',
|
||
},
|
||
{
|
||
id: 'goods_transport',
|
||
branch: 'P131',
|
||
label: 'Início do transporte7(2)',
|
||
},
|
||
{
|
||
id: 'goods_installation',
|
||
branch: 'P131',
|
||
label: 'Instalação/montagem concluída7(2)',
|
||
},
|
||
{
|
||
id: 'service_completed',
|
||
branch: 'P131',
|
||
label: 'Serviço realizado7(1)b',
|
||
},
|
||
{
|
||
id: 'service_early_charge',
|
||
branch: 'P131',
|
||
label: 'Serviço cobrado/debitado antes da realização7(1)b',
|
||
},
|
||
{
|
||
id: 'import_clearance',
|
||
branch: 'P131',
|
||
label: 'Desembaraço aduaneiro7(1)c',
|
||
},
|
||
{
|
||
id: 'import_auction',
|
||
branch: 'P131',
|
||
label: 'Arrematação/venda na importação7(1)c',
|
||
},
|
||
{
|
||
id: 'continuous_period',
|
||
branch: 'P131',
|
||
label: 'Termo do período de pagamento sucessivo7(3)',
|
||
},
|
||
{
|
||
id: 'contract_effects',
|
||
branch: 'P131',
|
||
label: 'Efeitos translativos posteriores7(7)',
|
||
},
|
||
{
|
||
id: 'own_goods',
|
||
branch: 'P132',
|
||
label: 'Afectação/transmissão assimilada3(3)e/f e7(4)',
|
||
},
|
||
{
|
||
id: 'own_services',
|
||
branch: 'P132',
|
||
label: 'Serviço assimilado4(2) e7(4)',
|
||
},
|
||
{
|
||
id: 'commission_delivery',
|
||
branch: 'P132',
|
||
label: 'Comissário põe bens à disposição7(5)',
|
||
},
|
||
{
|
||
id: 'consignment_deadline',
|
||
branch: 'P132',
|
||
label: 'Termo de180dias da consignação7(6)',
|
||
},
|
||
{
|
||
id: 'advance_payment',
|
||
branch: 'P132',
|
||
label: 'Pagamento anterior à factura8c',
|
||
},
|
||
] as const;
|
||
type Choice = '' | 'yes' | 'no';
|
||
export type CurrencyEventRow = {
|
||
fxSelection?: FxSelection;
|
||
id: string;
|
||
treatment: string;
|
||
description: string;
|
||
period: string;
|
||
origin: 'projected' | 'effective';
|
||
operationRef: string;
|
||
documentRef: string;
|
||
documentDate: string;
|
||
scope: string;
|
||
evidence: string;
|
||
eventDate: string;
|
||
eventRef: string;
|
||
otherDate: string;
|
||
reviewDate: string;
|
||
factsConfirmed: Choice;
|
||
invoiceRequired: Choice;
|
||
invoiceState: '' | 'issued' | 'not_issued';
|
||
invoiceDate: string;
|
||
invoiceRef: string;
|
||
legalDeadline: string;
|
||
deadlineRef: string;
|
||
paymentState: '' | 'none' | 'received';
|
||
paymentDate: string;
|
||
paymentRef: string;
|
||
amount: string;
|
||
paymentAmount: string;
|
||
currency: string;
|
||
sourceCurrency: string;
|
||
fxRate: string;
|
||
fxDate: string;
|
||
fxSource: string;
|
||
fxRuleRef: string;
|
||
fxDirection: '' | 'mzn_per_unit' | 'units_per_mzn';
|
||
};
|
||
type DueEvent = { date: string; amount: string | null; reason: string };
|
||
type LineResult = {
|
||
id: string;
|
||
status: 'determined' | 'not_applicable' | 'indeterminate' | 'conditional';
|
||
eventDate: string | null;
|
||
dueEvents: DueEvent[];
|
||
remainingPending: boolean;
|
||
scenarioAmount: string | null;
|
||
baseAmount: null;
|
||
vat: null;
|
||
rate: null;
|
||
reason: string;
|
||
};
|
||
export type CurrencyEventCalculation = {
|
||
version: string;
|
||
revision: number;
|
||
calculatedAt: string;
|
||
opportunity: string;
|
||
location: string;
|
||
sourceKey: string;
|
||
source: string;
|
||
inputs: CurrencyEventRow[];
|
||
rows: LineResult[];
|
||
};
|
||
export type CurrencyEventBook = {
|
||
rows: CurrencyEventRow[];
|
||
result?: CurrencyEventCalculation;
|
||
history?: CurrencyEventCalculation[];
|
||
};
|
||
type Store = ReturnType<typeof readAcquisitionStore> & {
|
||
currencyEvents?: Record<string, CurrencyEventBook>;
|
||
};
|
||
export function newCurrencyEvent(
|
||
id: string,
|
||
currency: string,
|
||
): CurrencyEventRow {
|
||
return {
|
||
id,
|
||
treatment: '',
|
||
description: '',
|
||
period: '',
|
||
origin: 'projected',
|
||
operationRef: '',
|
||
documentRef: '',
|
||
documentDate: '',
|
||
scope: '',
|
||
evidence: '',
|
||
eventDate: '',
|
||
eventRef: '',
|
||
otherDate: '',
|
||
reviewDate: '',
|
||
factsConfirmed: '',
|
||
invoiceRequired: '',
|
||
invoiceState: '',
|
||
invoiceDate: '',
|
||
invoiceRef: '',
|
||
legalDeadline: '',
|
||
deadlineRef: '',
|
||
paymentState: '',
|
||
paymentDate: '',
|
||
paymentRef: '',
|
||
amount: '',
|
||
paymentAmount: '',
|
||
currency,
|
||
sourceCurrency: '',
|
||
fxRate: '',
|
||
fxDate: '',
|
||
fxSource: '',
|
||
fxRuleRef: '',
|
||
fxDirection: '',
|
||
};
|
||
}
|
||
export function switchCurrencyEvent(
|
||
row: CurrencyEventRow,
|
||
treatment: string,
|
||
): CurrencyEventRow {
|
||
return { ...row, treatment, factsConfirmed: '', invoiceRequired: '' };
|
||
}
|
||
function readStore(draft: Record<string, unknown>): Store {
|
||
const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||
if (
|
||
store.currencyEvents !== undefined &&
|
||
(!store.currencyEvents ||
|
||
typeof store.currencyEvents !== 'object' ||
|
||
Array.isArray(store.currencyEvents))
|
||
)
|
||
throw new Error(
|
||
'Moeda/eventos não recuperáveis;originais preservados.',
|
||
);
|
||
const fields = Object.keys(newCurrencyEvent('', ''));
|
||
for (const b of Object.values(store.currencyEvents ?? {}))
|
||
if (
|
||
!b ||
|
||
!Array.isArray(b.rows) ||
|
||
(b.history !== undefined && !Array.isArray(b.history)) ||
|
||
b.rows.some(
|
||
(r) =>
|
||
!r ||
|
||
fields.some(
|
||
(k) =>
|
||
typeof r[k as keyof CurrencyEventRow] !== 'string',
|
||
),
|
||
)
|
||
)
|
||
throw new Error(
|
||
'Linhas de moeda/eventos não recuperáveis;sem lista vazia por defeito.',
|
||
);
|
||
return store;
|
||
}
|
||
export function currencyEventBook(
|
||
draft: Record<string, unknown>,
|
||
): CurrencyEventBook {
|
||
return (
|
||
readStore(draft).currencyEvents?.[acquisitionScope(draft)] ?? {
|
||
rows: [],
|
||
}
|
||
);
|
||
}
|
||
export function saveCurrencyEventBook(
|
||
draft: Record<string, unknown>,
|
||
book: CurrencyEventBook,
|
||
): string {
|
||
const store = readStore(draft),
|
||
encoded = JSON.stringify({
|
||
...store,
|
||
currencyEvents: {
|
||
...store.currencyEvents,
|
||
[acquisitionScope(draft)]: book,
|
||
},
|
||
});
|
||
readStore({ ...draft, modelo_a_snapshot: encoded });
|
||
return encoded;
|
||
}
|
||
function date(s: string): number | null {
|
||
const n = Date.parse(s + 'T00:00:00Z');
|
||
return /^\d{4}-\d{2}-\d{2}$/.test(s) &&
|
||
Number.isFinite(n) &&
|
||
new Date(n).toISOString().slice(0, 10) === s
|
||
? n
|
||
: null;
|
||
}
|
||
function decimal(s: string, places = 6): bigint | null {
|
||
if (!new RegExp('^\\d{1,15}(\\.\\d{1,' + places + '})?$').test(s.trim()))
|
||
return null;
|
||
const [w, f = ''] = s.trim().split('.');
|
||
return BigInt(w) * 10n ** BigInt(places) + BigInt(f.padEnd(places, '0'));
|
||
}
|
||
function money(n: bigint): string {
|
||
return `${n / 1000000n}.${(n % 1000000n).toString().padStart(6, '0').replace(/0+$/, '').padEnd(2, '0')}`;
|
||
}
|
||
export function evaluateCurrencyEvent(row: CurrencyEventRow): LineResult {
|
||
const result: LineResult = {
|
||
id: row.id,
|
||
status: 'indeterminate',
|
||
eventDate: null,
|
||
dueEvents: [],
|
||
remainingPending: false,
|
||
scenarioAmount: null,
|
||
baseAmount: null,
|
||
vat: null,
|
||
rate: null,
|
||
reason: '',
|
||
};
|
||
const stop = (
|
||
reason: string,
|
||
status: LineResult['status'] = 'indeterminate',
|
||
) => ({ ...result, status, reason });
|
||
if (!currencyEventTreatments.some((t) => t.id === row.treatment))
|
||
return stop('Identifique percurso de moeda/evento.');
|
||
if (
|
||
!/^2026-(0[1-9]|1[0-2])$/.test(row.period) ||
|
||
!['projected', 'effective'].includes(row.origin) ||
|
||
date(row.documentDate) === null ||
|
||
![
|
||
row.description,
|
||
row.operationRef,
|
||
row.documentRef,
|
||
row.scope,
|
||
row.evidence,
|
||
].every((v) => !!v.trim())
|
||
)
|
||
return stop(
|
||
'Identifique mês de contexto2026,operação,documento/data,âmbito e factos ou pressupostos expressos.',
|
||
);
|
||
if (row.factsConfirmed === 'no')
|
||
return stop(
|
||
'Os factos declarados afastam este percurso;sem isenção ou IVA zero por defeito.',
|
||
'not_applicable',
|
||
);
|
||
if (row.factsConfirmed !== 'yes')
|
||
return stop(
|
||
'Factos do percurso por esclarecer;não usar datas ou câmbio por defeito.',
|
||
);
|
||
if (row.treatment === 'foreign_scenario') {
|
||
const amount = decimal(row.amount),
|
||
fx = decimal(row.fxRate, 8);
|
||
if (
|
||
row.currency !== 'MZN' ||
|
||
![
|
||
'USD',
|
||
'EUR',
|
||
'ZAR',
|
||
'GBP',
|
||
'CNY',
|
||
'BRL',
|
||
'JPY',
|
||
'CHF',
|
||
'AUD',
|
||
'CAD',
|
||
].includes(row.sourceCurrency) ||
|
||
amount === null ||
|
||
fx === null ||
|
||
fx === 0n ||
|
||
date(row.fxDate) === null ||
|
||
![row.fxSource, row.fxRuleRef].every((v) => !!v.trim()) ||
|
||
!['mzn_per_unit', 'units_per_mzn'].includes(row.fxDirection)
|
||
)
|
||
return stop(
|
||
'Indique moeda original,valor,taxa positiva,direcção,data,fonte e regra/pressuposto expresso.Sem substituir moeda original porMZN ou reutilizar taxa de apresentação como regra fiscal.',
|
||
);
|
||
result.scenarioAmount = money(
|
||
row.fxDirection === 'mzn_per_unit'
|
||
? convertScaledAmount(amount, fx, 100000000n)
|
||
: (amount * 100000000n + fx / 2n) / fx,
|
||
);
|
||
if (row.fxSelection) {
|
||
const converted = result.scenarioAmount;
|
||
result.scenarioAmount = null;
|
||
try {
|
||
const selected = selectFx(row.fxSelection),
|
||
applied = row.fxSelection.applied;
|
||
if (
|
||
!applied ||
|
||
applied.selectionKey !== fxSelectionKey(row.fxSelection) ||
|
||
applied.rate !== selected.rate ||
|
||
applied.date !== selected.date ||
|
||
row.sourceCurrency !== row.fxSelection.sourceCurrency ||
|
||
row.fxDirection !== row.fxSelection.direction ||
|
||
row.fxRate !== selected.rate ||
|
||
row.fxDate !== selected.date ||
|
||
row.fxSource !== applied.source
|
||
)
|
||
return stop(
|
||
'Selecção cambial alterada ou não aplicada explicitamente;histórico preservado,sem recálculo automático.',
|
||
);
|
||
result.scenarioAmount = converted;
|
||
const columnLabel = (
|
||
{
|
||
buy: 'Compra',
|
||
sell: 'Venda',
|
||
mean: 'Média de referência BM',
|
||
exception: 'Excepção fiscal identificada',
|
||
} as Record<string, string>
|
||
)[selected.column];
|
||
return stop(
|
||
`Conversão operacional segundo a orientação da Tempo de25/10/2026:${row.amount} ${row.sourceCurrency} → ${result.scenarioAmount} MZN. Coluna ${columnLabel};taxa ${row.fxRate};${row.fxDirection === 'mzn_per_unit' ? 'MZN por unidade da moeda original' : 'unidades da moeda original por MZN'};data ${row.fxDate};boletim ${row.fxSelection.bulletinRef}. Sem cálculo de IVA ou dedução e sem alteração dos livros fiscais anteriores.`,
|
||
'determined',
|
||
);
|
||
} catch (error) {
|
||
return stop(String(error));
|
||
}
|
||
}
|
||
return stop(
|
||
`P104:cenário condicionado=${result.scenarioAmount}MZN,origem${row.sourceCurrency},taxa${row.fxRate},${row.fxDirection},data${row.fxDate}. CotejoLei2/2006 concluido:106(5)nao define taxa/data;regra complementar15(8) ainda por demonstrar;referência indicada é pressuposto,não validação da taxa legal.Base fiscal permanece indeterminada.Sem IVA ou activação nos livros anteriores.`,
|
||
`conditional`,
|
||
);
|
||
}
|
||
const review = date(row.reviewDate),
|
||
event = date(row.eventDate);
|
||
if (review === null || !row.eventRef.trim())
|
||
return stop(
|
||
'Identifique data de conferência e documento/âmbito do evento específico.',
|
||
);
|
||
if (row.treatment === 'consignment_deadline') {
|
||
const delivery = date(row.otherDate);
|
||
if (delivery === null)
|
||
return stop(
|
||
'Identifique entrega das mercadorias à consignação3(3)d;7(6) exige termo180dias,não embalagensP102.',
|
||
);
|
||
result.eventDate = new Date(delivery + 180 * 86400000)
|
||
.toISOString()
|
||
.slice(0, 10);
|
||
} else if (row.treatment === 'advance_payment') {
|
||
if (date(row.paymentDate) === null)
|
||
return stop('Identifique recebimento anterior à factura para8c.');
|
||
result.eventDate = null;
|
||
} else {
|
||
if (event === null)
|
||
return stop(
|
||
'Identifique a data própria do evento7,sem inferir da emissão do documento.',
|
||
);
|
||
result.eventDate = row.eventDate;
|
||
if (
|
||
row.treatment === 'service_early_charge' &&
|
||
(date(row.otherDate) === null || event >= date(row.otherDate)!)
|
||
)
|
||
return stop(
|
||
'Indique realização do serviço posterior à cobrança/débito;aplique o evento apenas ao montante respectivo.',
|
||
);
|
||
if (
|
||
row.treatment === 'contract_effects' &&
|
||
(date(row.otherDate) === null || event < date(row.otherDate)!)
|
||
)
|
||
return stop(
|
||
'Indique disponibilização anterior ou simultânea aos efeitos translativos;este7(7) exclui3(3)a/b,que têm percursos próprios.',
|
||
);
|
||
}
|
||
if (['import_clearance', 'import_auction'].includes(row.treatment)) {
|
||
if (date(result.eventDate!)! > review)
|
||
return stop(
|
||
'Evento aduaneiro futuro na conferência;sem exigibilidade declarada.',
|
||
);
|
||
result.dueEvents = [
|
||
{
|
||
date: result.eventDate!,
|
||
amount:
|
||
decimal(row.amount) === null
|
||
? null
|
||
: money(decimal(row.amount)!),
|
||
reason: '7(1)c:desembaraço/arrematação/venda na importação;factura comercial não desloca o evento aduaneiro',
|
||
},
|
||
];
|
||
return stop(
|
||
'Importação7(1)c:facto gerador/exigibilidade pelo evento aduaneiro identificado;sem aplicar8 por factura comercial,nem calcular base/IVA.',
|
||
'determined',
|
||
);
|
||
}
|
||
if (row.invoiceRequired === '')
|
||
return stop(
|
||
'Identifique se esta operação dá lugar à obrigação de factura25;7 e8 são estados separados.',
|
||
);
|
||
if (row.invoiceRequired === 'no') {
|
||
if (result.eventDate && date(result.eventDate)! > review)
|
||
return stop(
|
||
'Evento futuro na conferencia;sem exigibilidade declarada.',
|
||
);
|
||
if (row.treatment === 'advance_payment')
|
||
return stop(
|
||
'Percurso8c exige operação com obrigação de factura;conferir enquadramento.',
|
||
'not_applicable',
|
||
);
|
||
result.dueEvents = [
|
||
{
|
||
date: result.eventDate!,
|
||
amount:
|
||
decimal(row.amount) === null
|
||
? null
|
||
: money(decimal(row.amount)!),
|
||
reason: 'Exigibilidade segundo evento7,sem obrigação de factura declarada',
|
||
},
|
||
];
|
||
return stop(
|
||
'Facto gerador e exigibilidade7 identificados no âmbito declarado;sem calcular IVA,base ou dedução.',
|
||
'determined',
|
||
);
|
||
}
|
||
const deadline = date(row.legalDeadline);
|
||
if (
|
||
deadline === null ||
|
||
!row.deadlineRef.trim() ||
|
||
(result.eventDate && deadline < date(result.eventDate)!)
|
||
)
|
||
return stop(
|
||
'Facto gerador identificado;indique prazo legal de emissão e disposição/calendário aplicáveis.Sem presumir automaticamente cinco dias úteis2008 ou feriados.',
|
||
);
|
||
if (
|
||
!['issued', 'not_issued'].includes(row.invoiceState) ||
|
||
!['none', 'received'].includes(row.paymentState)
|
||
)
|
||
return stop(
|
||
'Identifique emissão e recebimentos;desconhecido não é ausência de pagamento.',
|
||
);
|
||
const invoice =
|
||
row.invoiceState === 'issued' ? date(row.invoiceDate) : null;
|
||
if (
|
||
row.invoiceState === 'issued' &&
|
||
(invoice === null || invoice > review || !row.invoiceRef.trim())
|
||
)
|
||
return stop(
|
||
'Identifique factura efectivamente emitida/data/referência até à conferência.',
|
||
);
|
||
const normalDate =
|
||
invoice !== null && invoice <= deadline
|
||
? row.invoiceDate
|
||
: row.legalDeadline;
|
||
const total = decimal(row.amount);
|
||
let paid = 0n;
|
||
if (row.paymentState === 'received') {
|
||
const payment = date(row.paymentDate),
|
||
amount = decimal(row.paymentAmount);
|
||
if (
|
||
payment === null ||
|
||
payment > review ||
|
||
amount === null ||
|
||
total === null ||
|
||
amount > total ||
|
||
!row.paymentRef.trim()
|
||
)
|
||
return stop(
|
||
'Identifique recebimento,parcela e total do âmbito na mesma moeda;pagamento não pode exceder o total.Sem duplicar parcelas nem substituir montante desconhecido porzero.',
|
||
);
|
||
if (
|
||
(invoice === null || payment < invoice) &&
|
||
payment < date(normalDate)!
|
||
) {
|
||
paid = amount;
|
||
result.dueEvents.push({
|
||
date: row.paymentDate,
|
||
amount: money(amount),
|
||
reason: '8c:montante recebido antes da factura,sem prejuízo do termo do prazo8b',
|
||
});
|
||
} else if (row.treatment === 'advance_payment')
|
||
return stop(
|
||
'Pagamento não é anterior à factura/termo aplicável;não antecipar por8c.',
|
||
'not_applicable',
|
||
);
|
||
}
|
||
if (row.treatment === 'advance_payment' && row.paymentState !== 'received')
|
||
return stop('Identifique o recebimento anterior à factura.');
|
||
const remainder = total === null ? null : total - paid;
|
||
if (date(normalDate)! <= review) {
|
||
if (remainder === null || remainder > 0n)
|
||
result.dueEvents.push({
|
||
date: normalDate,
|
||
amount: remainder === null ? null : money(remainder),
|
||
reason:
|
||
invoice !== null && invoice <= deadline
|
||
? '8a:factura emitida dentro do prazo'
|
||
: '8b:termo do prazo de emissão',
|
||
});
|
||
} else result.remainingPending = remainder === null || remainder > 0n;
|
||
return stop(
|
||
'Exigibilidade separada em parcelas8a/b/c,sem dupla contagem do recebimento e restante. Facto gerador7 conservado;prazo legal parametrizado com referência aplicável.Sem IVA,base fiscal,nova dedução ou totaisModeloA.',
|
||
'determined',
|
||
);
|
||
}
|
||
function key(draft: Record<string, unknown>, book: CurrencyEventBook): string {
|
||
return JSON.stringify({
|
||
version: currencyEventVersion,
|
||
scope: acquisitionScope(draft),
|
||
location: draft.selection_labels ?? {},
|
||
rows: book.rows,
|
||
});
|
||
}
|
||
export function currencyEventCurrent(
|
||
draft: Record<string, unknown>,
|
||
book: CurrencyEventBook,
|
||
): boolean {
|
||
return !!book.result && book.result.sourceKey === key(draft, book);
|
||
}
|
||
export function recordCurrencyEvents(
|
||
draft: Record<string, unknown>,
|
||
book: CurrencyEventBook,
|
||
): CurrencyEventBook {
|
||
if (
|
||
!book.rows.length ||
|
||
new Set(book.rows.map((r) => r.id)).size !== book.rows.length
|
||
)
|
||
throw new Error(
|
||
'Indique operações com identidades distintas;originais conservados.',
|
||
);
|
||
const result: CurrencyEventCalculation = {
|
||
version: currencyEventVersion,
|
||
revision: (book.result?.revision ?? 0) + 1,
|
||
calculatedAt: new Date().toISOString(),
|
||
opportunity: acquisitionScope(draft),
|
||
location: JSON.stringify(draft.selection_labels ?? {}),
|
||
sourceKey: key(draft, book),
|
||
source: currencyEventSource,
|
||
inputs: structuredClone(book.rows),
|
||
rows: book.rows.map(evaluateCurrencyEvent),
|
||
};
|
||
const next = {
|
||
...book,
|
||
result,
|
||
history: [
|
||
...(book.history ?? []),
|
||
...(book.result ? [book.result] : []),
|
||
],
|
||
};
|
||
saveCurrencyEventBook(draft, next);
|
||
return next;
|
||
}
|
||
export function currencyEventReportRows(
|
||
draft: Record<string, unknown>,
|
||
): Array<[string, string, string, string]> {
|
||
try {
|
||
const book = currencyEventBook(draft),
|
||
r = book.result;
|
||
if (!r) return [];
|
||
return r.inputs.map((row, i): [string, string, string, string] => {
|
||
if (row.treatment === 'foreign_scenario' && row.fxSelection)
|
||
return [
|
||
'IVA mensal',
|
||
`P104 — ${row.period} — ${row.id}`,
|
||
`${row.description};${r.rows[i].reason};Original:${row.amount} ${row.sourceCurrency};Convertido:${r.rows[i].scenarioAmount ?? 'Indeterminado'} MZN`,
|
||
`${r.opportunity};${currencyEventCurrent(draft, book) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};documento:${row.documentRef},${row.documentDate};âmbito:${row.scope};boletim:${row.fxSelection.bulletinRef};fonte:${row.fxSelection.source};natureza:${row.fxSelection.nature};perspectiva:${row.fxSelection.bulletinPerspective};relação:${row.fxSelection.relation};taxa:${row.fxRate};data:${row.fxDate};sentido:${row.fxDirection};regra/excepção:${row.fxRuleRef};sem base/IVA/dedução activados.`,
|
||
];
|
||
return [
|
||
'IVA mensal',
|
||
`${currencyEventTreatments.find((t) => t.id === row.treatment)?.branch ?? 'Evento'} — ${row.period} — ${row.id}`,
|
||
`${row.description};${r.rows[i].reason};facto gerador:${r.rows[i].eventDate ?? 'Indeterminado/não apurado'};parcelas:${r.rows[i].dueEvents.map((p) => p.date + ': ' + (p.amount ?? 'Montante indeterminado') + ' ' + row.currency + ' (' + p.reason + ')').join('; ') || 'Nenhuma apurada'};restante pendente:${r.rows[i].status === 'indeterminate' ? 'Indeterminado' : r.rows[i].remainingPending ? 'Sim' : 'Não'};${row.treatment === 'foreign_scenario' && r.rows[i].status === 'determined' ? 'conversão operacional' : 'cenário condicionado'}:${r.rows[i].scenarioAmount ?? 'Indeterminado'};base/IVA:Indeterminados`,
|
||
`${r.opportunity};${r.location};${currencyEventCurrent(draft, book) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};${row.origin};documento ${row.documentRef},${row.documentDate};ambito ${row.scope};${row.treatment === 'foreign_scenario' ? `origem ${row.amount} ${row.sourceCurrency};taxa ${row.fxRate},${row.fxDirection},${row.fxDate};fonte ${row.fxSource};pressuposto ${row.fxRuleRef}` : `evento ${row.eventDate},${row.eventRef};outra data ${row.otherDate};conferencia ${row.reviewDate};factura ${row.invoiceRequired},${row.invoiceState},${row.invoiceDate},${row.invoiceRef};prazo ${row.legalDeadline},${row.deadlineRef};recebimento ${row.paymentState},${row.paymentDate},${row.paymentAmount},${row.paymentRef};total do ambito ${row.amount} ${row.currency}`};fundamento ${row.evidence};sem nova dedução/campo11/IVA ou activação cambial nos livros anteriores.`,
|
||
];
|
||
});
|
||
} catch {
|
||
return [
|
||
[
|
||
'IVA mensal',
|
||
'Moeda/eventos',
|
||
'Dados não recuperáveis',
|
||
'Originais preservados;sem substituição porzero.',
|
||
],
|
||
];
|
||
}
|
||
}
|