Functional Weave
Code in Rust

finance.fx-revaluation@1.0.0

impl/rust.rs

3,569 bytes · the Rust 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.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_add::{add_money, subtract_money};  ← from money.add ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value};  ← from money.amount ^1.0.0 · built alongside by fune
use super::money_convert::{convert_money, exchange_rate_from_value, ExchangeRate};  ← from money.convert ^1.0.0 · built alongside by fune

/// 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<RevaluedBalance> = 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<ForeignBalance> = args[0].as_arr().iter().map(foreign_balance_from_value).collect();
    let rates: Vec<ExchangeRate> = args[1].as_arr().iter().map(exchange_rate_from_value).collect();
    fx_revaluation_to_value(&fx_revaluation(&balances, &rates, args[2].as_str()))
}