use super::funejson::Value; use super::dates_days_between::days_between; use super::lending_daily_interest::{daily_interest, dated_balance_from_value, DatedBalance}; use super::money_amount::{money, money_from_value, money_to_value, Money}; /// 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 = 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 = 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(), )) }