Skip to main content

dinero/models/
balance.rs

1use crate::error::BalanceError;
2use crate::models::{Currency, Money};
3use std::collections::hash_map::Iter;
4use std::collections::HashMap;
5use std::error::Error;
6use std::fmt;
7use std::fmt::{Display, Formatter};
8use std::ops::{Add, Neg, Sub};
9use std::rc::Rc;
10
11/// Balance is money with several currencies, for example 100 USD and 50 EUR
12#[derive(Debug, Clone)]
13pub struct Balance {
14    pub balance: HashMap<Option<Rc<Currency>>, Money>,
15}
16impl Default for Balance {
17    fn default() -> Self {
18        Self::new()
19    }
20}
21
22#[derive(Debug)]
23pub struct TooManyCurrenciesError {
24    pub num_currencies: usize,
25}
26impl Error for TooManyCurrenciesError {}
27impl Display for TooManyCurrenciesError {
28    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
29        write!(
30            f,
31            "{} currencies found, expecting only one.",
32            self.num_currencies
33        )
34    }
35}
36
37impl Balance {
38    pub fn new() -> Balance {
39        Balance {
40            balance: HashMap::new(),
41        }
42    }
43
44    /// Automatic conversion from balance to regular money
45    /// it can only be done if the balance has only one currency
46    pub fn to_money(&self) -> Result<Money, BalanceError> {
47        let vec = self
48            .balance
49            .values()
50            .filter(|x| !x.is_zero())
51            .collect::<Vec<&Money>>();
52        match vec.len() {
53            0 => Ok(Money::Zero),
54            1 => Ok(vec[0].clone()),
55            _ => Err(BalanceError::TooManyCurrencies(self.clone())),
56        }
57    }
58    pub fn is_zero(&self) -> bool {
59        match self.balance.is_empty() {
60            true => true,
61            false => {
62                for (_, money) in self.balance.iter() {
63                    if !money.is_zero() {
64                        return false;
65                    }
66                }
67                true
68            }
69        }
70    }
71
72    /// Whether a balance can be zero
73    /// To be true, there must be positive and negative amounts
74    pub fn can_be_zero(&self) -> bool {
75        if self.is_zero() {
76            return true;
77        }
78        let mut positive = false;
79        let mut negative = false;
80        for (_, m) in self.balance.iter() {
81            positive |= m.is_positive();
82            negative |= m.is_negative();
83            if positive & negative {
84                return true;
85            }
86        }
87        false
88    }
89    pub fn len(&self) -> usize {
90        self.balance.len()
91    }
92    pub fn is_empty(&self) -> bool {
93        self.balance.is_empty()
94    }
95    pub fn iter(&self) -> Iter<'_, Option<Rc<Currency>>, Money> {
96        self.balance.iter()
97    }
98}
99
100impl Display for Balance {
101    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
102        let mut string = String::new();
103        for (_, v) in self.balance.iter() {
104            string.push_str(format!("{}", v).as_str());
105        }
106        write!(f, "{}", string)
107    }
108}
109
110impl<'a> Neg for Balance {
111    type Output = Balance;
112
113    fn neg(self) -> Self::Output {
114        let mut balance: HashMap<Option<Rc<Currency>>, Money> = HashMap::new();
115        for (k, v) in self.balance {
116            balance.insert(k, -v);
117        }
118        Balance { balance }
119    }
120}
121
122impl Add for Balance {
123    type Output = Balance;
124
125    fn add(self, rhs: Self) -> Self::Output {
126        let mut total: HashMap<Option<Rc<Currency>>, Money> = HashMap::new();
127        let balances = vec![self, rhs];
128        for bal in balances.iter() {
129            for (cur, money) in bal.balance.iter() {
130                match total.to_owned().get(cur) {
131                    None => total.insert(cur.clone(), money.clone()),
132                    Some(total_money) => match total_money {
133                        Money::Zero => total.insert(cur.clone(), money.clone()),
134                        Money::Money {
135                            amount: already, ..
136                        } => match money {
137                            Money::Zero => None,
138                            Money::Money { amount, .. } => total.insert(
139                                cur.clone(),
140                                Money::from((
141                                    cur.as_ref().unwrap().clone(),
142                                    amount + already.to_owned(),
143                                )),
144                            ),
145                        },
146                    },
147                };
148            }
149        }
150
151        Balance {
152            balance: total.into_iter().filter(|(_, v)| !v.is_zero()).collect(),
153        }
154    }
155}
156
157impl Sub for Balance {
158    type Output = Balance;
159
160    fn sub(self, rhs: Self) -> Self::Output {
161        let negative = -rhs;
162        self + negative
163    }
164}
165
166// Converter
167impl From<Money> for Balance {
168    fn from(money: Money) -> Self {
169        let mut balance: HashMap<Option<Rc<Currency>>, Money> = HashMap::new();
170        match money {
171            Money::Zero => balance.insert(None, Money::Zero),
172            Money::Money { ref currency, .. } => {
173                balance.insert(Some(currency.clone()), money.clone())
174            }
175        };
176        Balance { balance }
177    }
178}
179
180impl PartialEq for Balance {
181    fn eq(&self, other: &Self) -> bool {
182        for (k, v) in self.balance.iter() {
183            let other_money = other.balance.get(k);
184            match other_money {
185                None => {
186                    if !v.is_zero() {
187                        return false;
188                    }
189                }
190                Some(money) => {
191                    if money != v {
192                        return false;
193                    }
194                }
195            }
196        }
197        true
198    }
199}