import { selectFx, fxSelectionKey, type FxSelection, } from './iva-fx-selection.ts'; import { currencyEventBook, newCurrencyEvent } from './iva-currency-events.ts'; import { convertCents } from './business-financial-plan.ts'; import { decimalFraction, fraction, type FiscalFraction, } from './fiscal-rational.ts'; import { closureFingerprint } from './iva-closure.ts'; export type IrpcFxLink = { id: string; fingerprint: string }; export type IrpcFxAmount = { currency: string; fxSelection?: FxSelection; fxLink?: IrpcFxLink; }; export type IrpcFxEvidence = { originalAmount: string; originalCurrency: string; convertedCurrency: 'MZN'; column: string; rate: string; date: string; direction: string; source: string; selectionKey: string; rounding: string; }; export function irpcFxSources(d: Record) { return currencyEventBook(d) .rows.filter((r) => r.fxSelection) .map((r) => ({ id: r.id, label: `${r.description || r.id} — ${r.sourceCurrency} — ${r.fxSelection?.applied?.rate ?? 'por aplicar'}`, fingerprint: closureFingerprint(r), selection: structuredClone(r.fxSelection!), })); } export function irpcFxDependency( d: Record, input: IrpcFxAmount, ): string { if (input.currency === 'MZN' || !input.fxLink) return ''; const source = irpcFxSources(d).find((s) => s.id === input.fxLink!.id); return !source ? 'Selecção cambial de origem indisponível;vínculo e histórico conservados.' : source.fingerprint !== input.fxLink.fingerprint ? 'Selecção cambial de origem alterada;compare a nova selecção antes de a aplicar às retenções.' : ''; } export function irpcFxRow(id: string, input: IrpcFxAmount) { const s = input.fxSelection, a = s?.applied; return { ...newCurrencyEvent(id, 'MZN'), sourceCurrency: input.currency, fxSelection: s, fxDirection: s?.direction ?? '', fxRate: a?.rate ?? '', fxDate: a?.date ?? '', fxSource: a?.source ?? '', }; } export function convertIrpcAmount( d: Record, amount: string, input: IrpcFxAmount, ): { value: FiscalFraction; evidence: IrpcFxEvidence | null } { const original = decimalFraction(amount); if (input.currency === 'MZN') return { value: original, evidence: null }; const issue = irpcFxDependency(d, input); if (issue) throw Error(issue); const s = input.fxSelection; if (!s) throw Error( 'Reutilize ou aplique a selecção cambial já entregue:moeda original,boletim,coluna,data e sentido;sem taxa actual por defeito.', ); const selected = selectFx(s), a = s.applied; if ( !a || a.selectionKey !== fxSelectionKey(s) || a.rate !== selected.rate || a.date !== selected.date || a.source !== selected.source || a.column !== selected.column || s.sourceCurrency !== input.currency ) throw Error( 'Selecção cambial alterada,não confirmada ou de outra moeda;use a conferência e confirmação existentes,sem recalcular históricos.', ); const [whole, places = ''] = selected.rate.split('.'), rate = BigInt(whole + places.padEnd(8, '0')), scale = 100000000n; const micro = (BigInt(original.numerator) * 1000000n) / BigInt(original.denominator); const converted = s.direction === 'mzn_per_unit' ? convertCents(micro, rate, scale) : convertCents(micro, scale, rate); return { value: fraction(converted, 1000000n), evidence: { originalAmount: amount, originalCurrency: input.currency, convertedCurrency: 'MZN', column: selected.column, rate: selected.rate, date: selected.date, direction: s.direction, source: selected.source, selectionKey: a.selectionKey, rounding: 'Mesmo convertCents entregue;conversão à unidade de0,000001MZN;apresentação separada,não regra nova de arredondamento do imposto.', }, }; } export function irpcFxEvidenceLabel(e: IrpcFxEvidence): string { const column = ( { buy: 'Compra', sell: 'Venda', mean: 'Média de referência BM', exception: 'Excepção fiscal identificada', } as Record )[e.column] ?? e.column; const direction = e.direction === 'mzn_per_unit' ? 'MZN por ' + e.originalCurrency : e.originalCurrency + ' por MZN'; return `Original: ${e.originalAmount} ${e.originalCurrency}; coluna: ${column}; taxa: ${e.rate}; cotação: ${direction}; data cambial: ${e.date}; fonte: ${e.source.split(';perspectiva:')[0]}. Montante e selecção originais conservados.`; } export function updateIrpcFx( input: IrpcFxAmount, selection: FxSelection, ): Partial { return { fxSelection: selection, fxLink: input.fxSelection && fxSelectionKey(input.fxSelection) === fxSelectionKey(selection) ? input.fxLink : undefined, }; }