Files
Investidor-V2/resources/js/lib/irpc-fx.ts
T

153 lines
5.3 KiB
TypeScript

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<string, unknown>) {
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<string, unknown>,
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<string, unknown>,
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<string, string>
)[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<IrpcFxAmount> {
return {
fxSelection: selection,
fxLink:
input.fxSelection &&
fxSelectionKey(input.fxSelection) === fxSelectionKey(selection)
? input.fxLink
: undefined,
};
}