use super::funejson::Value; use super::money_add::{add_money, subtract_money}; use super::money_amount::{money, money_from_value, money_to_value}; use super::money_convert::{convert_money, exchange_rate_from_value, ExchangeRate}; /// Revalue foreign-currency balances at the closing rates and report the /// unrealised gain (positive) or loss (negative) on each and in total. /// /// # Panics /// Panics on a balance already in the functional currency, a booked value in /// another currency, or a missing or duplicated rate. pub fn fx_revaluation(balances: &[ForeignBalance], rates: &[ExchangeRate], functional_currency: &str) -> FxRevaluation { let mut net = money(0, functional_currency); let mut lines: Vec = Vec::with_capacity(balances.len()); for item in balances { let foreign = item.balance.currency.as_str(); if foreign == functional_currency { panic!("account {} is already in {}", item.account, functional_currency); } if item.booked_value.currency != functional_currency { panic!("booked value of account {} must be in {}", item.account, functional_currency); } let matching: Vec<&ExchangeRate> = rates .iter() .filter(|r| r.base == foreign && r.quote == functional_currency) .collect(); if matching.is_empty() { panic!("no period-end rate from {} to {}", foreign, functional_currency); } if matching.len() > 1 { panic!("more than one period-end rate from {} to {}", foreign, functional_currency); } let revalued_value = convert_money(&item.balance, matching[0], "half-up"); let gain_loss = subtract_money(&revalued_value, &item.booked_value); net = add_money(&net, &gain_loss); lines.push(RevaluedBalance { account: item.account.clone(), balance: item.balance.clone(), booked_value: item.booked_value.clone(), revalued_value, gain_loss, }); } FxRevaluation { lines, net_gain_loss: net, } } pub fn foreign_balance_from_value(v: &Value) -> ForeignBalance { ForeignBalance { account: v.get("account").as_str().to_string(), balance: money_from_value(v.get("balance")), booked_value: money_from_value(v.get("bookedValue")), } } pub fn fx_revaluation_to_value(result: &FxRevaluation) -> Value { Value::obj(vec![ ( "lines", Value::Arr( result .lines .iter() .map(|l| { Value::obj(vec![ ("account", Value::str(&l.account)), ("balance", money_to_value(&l.balance)), ("bookedValue", money_to_value(&l.booked_value)), ("revaluedValue", money_to_value(&l.revalued_value)), ("gainLoss", money_to_value(&l.gain_loss)), ]) }) .collect(), ), ), ("netGainLoss", money_to_value(&result.net_gain_loss)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let balances: Vec = args[0].as_arr().iter().map(foreign_balance_from_value).collect(); let rates: Vec = args[1].as_arr().iter().map(exchange_rate_from_value).collect(); fx_revaluation_to_value(&fx_revaluation(&balances, &rates, args[2].as_str())) }