impl/typescript.ts
1,833 bytes · the TypeScript implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { addMoney, subtractMoney } from "./money_add.ts"; ← from money.add ^1.0.0 · built alongside by fune
import { money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { convertMoney } from "./money_convert.ts"; ← from money.convert ^1.0.0 · built alongside by fune
import { type ExchangeRate } from "./money_convert_types.ts";
import { type ForeignBalance, type FxRevaluation, type RevaluedBalance } from "./finance_fx_revaluation_types.ts";
/**
* Revalue foreign-currency balances at the closing rates and report the
* unrealised gain (positive) or loss (negative) on each and in total.
*/
export function fxRevaluation(
balances: readonly ForeignBalance[],
rates: readonly ExchangeRate[],
functionalCurrency: string,
): FxRevaluation {
let netGainLoss = money(0, functionalCurrency);
const lines: RevaluedBalance[] = [];
for (const item of balances) {
const foreign = item.balance.currency;
if (foreign === functionalCurrency) {
throw new RangeError(`account ${item.account} is already in ${functionalCurrency}`);
}
if (item.bookedValue.currency !== functionalCurrency) {
throw new RangeError(`booked value of account ${item.account} must be in ${functionalCurrency}`);
}
const matching = rates.filter((r) => r.base === foreign && r.quote === functionalCurrency);
if (matching.length === 0) {
throw new RangeError(`no period-end rate from ${foreign} to ${functionalCurrency}`);
}
if (matching.length > 1) {
throw new RangeError(`more than one period-end rate from ${foreign} to ${functionalCurrency}`);
}
const revaluedValue = convertMoney(item.balance, matching[0], "half-up");
const gainLoss = subtractMoney(revaluedValue, item.bookedValue);
lines.push({ account: item.account, balance: item.balance, bookedValue: item.bookedValue, revaluedValue, gainLoss });
netGainLoss = addMoney(netGainLoss, gainLoss);
}
return { lines, netGainLoss };
}