153 lines
5.3 KiB
TypeScript
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,
|
|
};
|
|
}
|