Functional Weave
Code in TypeScript

banking.overdraft-interest@1.0.0

impl/rust.rs

3,378 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::dates_days_between::days_between;  ← from dates.days-between ^1.0.0 · built alongside by fune
use super::lending_daily_interest::{daily_interest, dated_balance_from_value, DatedBalance};  ← from lending.daily-interest ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

/// Overdraft interest over a statement period. Each day the account is
/// overdrawn, the part of the overdrawn amount above the interest-free buffer
/// accrues interest; credit balances earn nothing here. The accrual itself is
/// lending.daily-interest on those chargeable amounts, so it is summed exactly
/// and rounded once for the period.
///
/// # Panics
/// Panics on a negative rate or buffer, mixed currencies, or anything
/// lending.daily-interest refuses.
pub fn overdraft_interest(
    balances: &[DatedBalance],
    from_iso: &str,
    to_iso: &str,
    annual_rate_basis_points: i64,
    interest_free_buffer: &Money,
    convention: &str,
    mode: &str,
) -> OverdraftInterest {
    if annual_rate_basis_points < 0 {
        panic!("annualRateBasisPoints must not be negative, received {}", annual_rate_basis_points);
    }
    if interest_free_buffer.minor < 0 {
        panic!("interestFreeBuffer must not be negative, received {}", interest_free_buffer.minor);
    }
    for entry in balances {
        if entry.balance.currency != interest_free_buffer.currency {
            panic!("currency mismatch: {} and {}", entry.balance.currency, interest_free_buffer.currency);
        }
    }
    let chargeable: Vec<DatedBalance> = balances
        .iter()
        .map(|entry| DatedBalance {
            date: entry.date.clone(),
            balance: money((-entry.balance.minor - interest_free_buffer.minor).max(0), &entry.balance.currency),
        })
        .collect();
    // Validates the dates, their order and the period before anything is counted.
    let accrued = daily_interest(&chargeable, from_iso, to_iso, annual_rate_basis_points, convention, mode);
    let mut days_overdrawn = 0i64;
    let mut days_charged = 0i64;
    for i in 0..balances.len() {
        let date = balances[i].date.as_str();
        let start = if date > from_iso { date } else { from_iso };
        let next = if i + 1 < balances.len() { balances[i + 1].date.as_str() } else { to_iso };
        let end = if next < to_iso { next } else { to_iso };
        if start >= end {
            continue;
        }
        let days = days_between(start, end);
        if balances[i].balance.minor < 0 {
            days_overdrawn += days;
        }
        if chargeable[i].balance.minor > 0 {
            days_charged += days;
        }
    }
    OverdraftInterest {
        interest: accrued.interest,
        days_overdrawn,
        days_charged,
    }
}

pub fn overdraft_interest_to_value(result: &OverdraftInterest) -> Value {
    Value::obj(vec![
        ("interest", money_to_value(&result.interest)),
        ("daysOverdrawn", Value::Int(result.days_overdrawn)),
        ("daysCharged", Value::Int(result.days_charged)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let balances: Vec<DatedBalance> = args[0].as_arr().iter().map(dated_balance_from_value).collect();
    overdraft_interest_to_value(&overdraft_interest(
        &balances,
        args[1].as_str(),
        args[2].as_str(),
        args[3].as_i64(),
        &money_from_value(&args[4]),
        args[5].as_str(),
        args[6].as_str(),
    ))
}