ledger_utils/
account_balance.rs1use crate::prices::{Prices, PricesError};
2use crate::Amount;
3use chrono::NaiveDate;
4use rust_decimal::Decimal;
5use rust_decimal::RoundingStrategy;
6use std::collections::HashMap;
7use std::fmt;
8use std::ops::AddAssign;
9use std::ops::SubAssign;
10
11#[derive(Clone)]
15pub struct AccountBalance {
16 pub amounts: HashMap<String, Amount>,
17}
18
19impl Default for AccountBalance {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl AccountBalance {
26 pub fn new() -> AccountBalance {
27 AccountBalance {
28 amounts: HashMap::new(),
29 }
30 }
31
32 pub fn value_in_commodity(
33 &self,
34 commodity_name: &str,
35 date: NaiveDate,
36 prices: &Prices,
37 ) -> Result<Decimal, PricesError> {
38 let mut result = Decimal::new(0, 0);
39 for amount in self.amounts.values() {
40 if amount.commodity.name == commodity_name {
41 result += amount.quantity;
42 } else {
43 result += prices.convert(
44 amount.quantity,
45 &amount.commodity.name,
46 commodity_name,
47 date,
48 )?;
49 }
50 }
51 Ok(result)
52 }
53
54 pub fn value_in_commodity_rounded(
55 &self,
56 commodity_name: &str,
57 decimal_points: u32,
58 date: NaiveDate,
59 prices: &Prices,
60 ) -> Decimal {
61 let assets_value = self.value_in_commodity(commodity_name, date, prices);
62 if let Ok(value) = assets_value {
63 value.round_dp_with_strategy(decimal_points, RoundingStrategy::MidpointAwayFromZero)
64 } else {
65 panic!("{:?}", assets_value);
66 }
67 }
68
69 pub fn is_zero(&self) -> bool {
70 self.amounts
71 .iter()
72 .all(|(_, amount)| amount.quantity == Decimal::ZERO)
73 }
74
75 fn remove_empties(&mut self) {
76 let empties: Vec<String> = self
77 .amounts
78 .iter()
79 .filter(|(_, amount)| amount.quantity == Decimal::ZERO)
80 .map(|(k, _)| k.clone())
81 .collect();
82 for empty in empties {
83 self.amounts.remove(&empty);
84 }
85 }
86}
87
88impl fmt::Display for AccountBalance {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 if self.amounts.is_empty() {
91 write!(f, "0")?;
92 return Ok(());
93 }
94
95 let mut amounts: Vec<_> = self.amounts.values().collect();
96 amounts.sort_by_key(|a| &a.commodity.name);
97
98 write!(f, "{}", amounts[0])?;
99
100 for amount in amounts[1..].iter() {
101 write!(f, ", {}", amount)?;
102 }
103
104 Ok(())
105 }
106}
107
108impl<'a> AddAssign<&'a AccountBalance> for AccountBalance {
109 fn add_assign(&mut self, other: &'a AccountBalance) {
110 for (currrency_name, amount) in &other.amounts {
111 self.amounts
112 .entry(currrency_name.clone())
113 .and_modify(|a| a.quantity += amount.quantity)
114 .or_insert_with(|| amount.clone());
115 }
116 self.remove_empties();
117 }
118}
119
120impl<'a> AddAssign<&'a Amount> for AccountBalance {
121 fn add_assign(&mut self, amount: &'a Amount) {
122 self.amounts
123 .entry(amount.commodity.name.clone())
124 .and_modify(|a| a.quantity += amount.quantity)
125 .or_insert_with(|| amount.clone());
126 self.remove_empties();
127 }
128}
129
130impl<'a> SubAssign<&'a AccountBalance> for AccountBalance {
131 fn sub_assign(&mut self, other: &'a AccountBalance) {
132 for (currrency_name, amount) in &other.amounts {
133 self.amounts
134 .entry(currrency_name.clone())
135 .and_modify(|a| a.quantity -= amount.quantity)
136 .or_insert_with(|| amount.clone());
137 }
138 self.remove_empties();
139 }
140}
141
142impl<'a> SubAssign<&'a Amount> for AccountBalance {
143 fn sub_assign(&mut self, amount: &'a Amount) {
144 self.amounts
145 .entry(amount.commodity.name.clone())
146 .and_modify(|a| a.quantity -= amount.quantity)
147 .or_insert_with(|| amount.clone());
148 self.remove_empties();
149 }
150}
151
152impl fmt::Debug for AccountBalance {
153 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
154 let mut values: Vec<Amount> = self.amounts.values().cloned().collect();
155 values.sort_by(|a, b| a.commodity.name.partial_cmp(&b.commodity.name).unwrap());
156 write!(f, "{:?}", values)
157 }
158}