1use crate::config::BalanceType;
7use crate::kernel::Settings;
8use crate::kernel::price_lookup::PriceLookupCtx;
9use crate::kernel::report_item_selector::BalanceSelector;
10use crate::model::balance_tree_node::ord_by_btn;
11use crate::model::{BalanceTreeNode, Commodity, Transaction, TxnAccount, TxnSet};
12use crate::tackler;
13use itertools::Itertools;
14use rust_decimal::Decimal;
15use std::collections::BTreeMap;
16use std::{collections::HashSet, sync::Arc};
17
18pub type Deltas = BTreeMap<Option<Arc<Commodity>>, Decimal>;
20pub type BTNs = Vec<BalanceTreeNode>;
21#[derive(Debug)]
22pub struct Balance {
23 pub(crate) title: String,
24 pub(crate) bal: BTNs,
25 pub(crate) deltas: Deltas,
26}
27
28impl Balance {
29 pub(crate) fn is_empty(&self) -> bool {
30 self.bal.is_empty()
31 }
32}
33
34impl Balance {
35 fn get_balance_tree_nodes(
45 me: &(TxnAccount, Decimal),
46 acc_sums: &[(TxnAccount, Decimal)],
47 ) -> Vec<BalanceTreeNode> {
48 let (my_acctn, my_sum) = me;
49
50 let childs = acc_sums
52 .iter()
53 .filter(|(atn, _)| my_acctn.is_parent_of(atn));
54
55 let childs_balance_trees = childs
57 .flat_map(|c| Balance::get_balance_tree_nodes(c, acc_sums))
58 .collect::<Vec<BalanceTreeNode>>();
59
60 let my_childs_sum = childs_balance_trees
62 .iter()
63 .filter(|btn| btn.acctn.atn.parent == my_acctn.atn.account)
64 .map(|btn| btn.sub_acc_tree_sum)
65 .sum::<Decimal>();
66
67 let my_btn = BalanceTreeNode {
68 acctn: my_acctn.clone(),
69 sub_acc_tree_sum: my_childs_sum + my_sum,
70 account_sum: *my_sum,
71 };
72
73 let mut x = vec![my_btn];
74 x.extend(childs_balance_trees);
75 x
76 }
77
78 fn bubble_up_acctn(
91 my_acctn_sum: &(TxnAccount, Decimal),
92 acc_sums: &[(TxnAccount, Decimal)],
93 settings: &Settings,
94 ) -> Result<Vec<(TxnAccount, Decimal)>, tackler::Error> {
95 let my_acctn = &my_acctn_sum.0;
96 if my_acctn.is_root() {
97 Ok(vec![my_acctn_sum.clone()])
101 } else {
102 let parent = acc_sums
104 .iter()
105 .filter(|(atn, _)| atn.is_parent_of(my_acctn))
106 .collect::<Vec<&(TxnAccount, Decimal)>>();
107
108 assert!(parent.is_empty() || parent.len() == 1);
109
110 if parent.is_empty() {
111 if my_acctn.my_parent_is_root() {
112 let new_parent_atn = settings
116 .get_txn_account(my_acctn.atn.parent.as_str(), my_acctn.comm.clone())?;
117 Ok(vec![(new_parent_atn, Decimal::ZERO), my_acctn_sum.clone()])
118 } else {
119 let new_parent_atn = settings
120 .get_txn_account(my_acctn.atn.parent.as_str(), my_acctn.comm.clone())?;
121 let mut sub_tree = vec![my_acctn_sum.clone()];
122 let mut x = Balance::bubble_up_acctn(
123 &(new_parent_atn, Decimal::ZERO),
124 acc_sums,
125 settings,
126 )?;
127 x.append(&mut sub_tree);
128 Ok(x)
129 }
130 } else {
131 let mut sub_tree = vec![my_acctn_sum.clone()];
133 let mut x = Balance::bubble_up_acctn(parent[0], acc_sums, settings)?;
134 x.append(&mut sub_tree);
135 Ok(x)
136 }
137 }
138 }
139
140 fn calculate_account_sums<'a, I>(
145 txns: I,
146 price_lookup_ctx: &PriceLookupCtx<'_>,
147 ) -> Vec<(TxnAccount, Decimal)>
148 where
149 I: Iterator<Item = &'a &'a Transaction>,
150 {
151 txns.flat_map(|txn| price_lookup_ctx.convert_prices(txn))
156 .sorted_by_key(|(acctn, _, _)| acctn.clone())
157 .chunk_by(|(acctn, _, _)| acctn.clone())
158 .into_iter()
159 .map(|(_, postings)| {
160 let mut ps = postings.peekable();
161 let acctn = ps.peek().unwrap().0.clone();
163 let acc_sum = ps.map(|(_, amount, _)| amount).sum::<Decimal>();
164 (acctn, acc_sum)
165 })
166 .collect()
167 }
168
169 fn balance_tree<'a, I>(
177 txns: I,
178 price_lookup_ctx: &PriceLookupCtx<'_>,
179 settings: &Settings,
180 ) -> Result<Vec<BalanceTreeNode>, tackler::Error>
181 where
182 I: Iterator<Item = &'a &'a Transaction>,
183 {
184 let account_sums: Vec<(TxnAccount, Decimal)> =
189 Self::calculate_account_sums(txns, price_lookup_ctx);
190
191 let complete_coa_sum_tree: &Vec<(TxnAccount, Decimal)> = &account_sums
205 .iter()
206 .try_fold(
207 Vec::new(),
208 |mut trees: Vec<Vec<(TxnAccount, Decimal)>>, acc| {
209 let bua = Balance::bubble_up_acctn(acc, &account_sums, settings)?;
210 trees.push(bua);
211 Ok::<Vec<Vec<(TxnAccount, Decimal)>>, tackler::Error>(trees)
212 },
213 )?
214 .into_iter()
215 .flatten()
216 .collect::<HashSet<_>>() .into_iter()
218 .collect::<Vec<(TxnAccount, Decimal)>>();
219
220 let roots = complete_coa_sum_tree
224 .iter()
225 .filter(|(acctn, _)| acctn.atn.depth == 1);
226
227 let mut bal = roots
231 .flat_map(|root_acc_sum| {
232 Balance::get_balance_tree_nodes(root_acc_sum, complete_coa_sum_tree)
233 })
234 .collect::<Vec<BalanceTreeNode>>();
235
236 bal.sort_by(ord_by_btn);
237 Ok(bal)
238 }
239
240 pub fn from<T>(
241 title: &str,
242 txn_set: &TxnSet<'_>,
243 price_lookup_ctx: &PriceLookupCtx<'_>,
244 accounts: &T,
245 settings: &Settings,
246 ) -> Result<Balance, tackler::Error>
247 where
248 T: BalanceSelector + ?Sized,
249 {
250 Self::from_iter(
251 title,
252 &txn_set.txns,
253 price_lookup_ctx,
254 accounts,
255 settings,
256 settings.report.balance.bal_type.clone(),
257 )
258 }
259
260 pub(crate) fn from_iter<'a, I, T>(
261 title: &str,
262 txns: I,
263 price_lookup_ctx: &PriceLookupCtx<'_>,
264 accounts: &T,
265 settings: &Settings,
266 bal_type: BalanceType,
267 ) -> Result<Balance, tackler::Error>
268 where
269 T: BalanceSelector + ?Sized,
270 I: IntoIterator<Item = &'a &'a Transaction>,
271 {
272 let bal = match bal_type {
273 BalanceType::Tree => {
274 Balance::balance_tree(txns.into_iter(), price_lookup_ctx, settings)?
275 }
276 BalanceType::Flat => {
277 Balance::balance_flat(txns.into_iter(), price_lookup_ctx, settings)?
278 }
279 };
280
281 let filt_bal: Vec<_> = bal.into_iter().filter(|b| accounts.eval(b)).collect();
282
283 if filt_bal.is_empty() {
284 Ok(Balance {
285 title: title.to_string(),
286 bal: Default::default(),
287 deltas: Default::default(),
288 })
289 } else {
290 let deltas = filt_bal
291 .iter()
292 .chunk_by(|btn| btn.acctn.comm.clone())
293 .into_iter()
294 .map(|(c, bs)| {
295 let dsum = bs.map(|b| b.account_sum).sum();
296 (c.is_any().then_some(c), dsum)
297 })
298 .collect();
299
300 Ok(Balance {
301 title: title.to_string(),
302 bal: filt_bal,
303 deltas,
304 })
305 }
306 }
307
308 fn balance_flat<'a, I>(
309 txns: I,
310 price_lookup_ctx: &PriceLookupCtx<'_>,
311 _settings: &Settings,
312 ) -> Result<Vec<BalanceTreeNode>, tackler::Error>
313 where
314 I: Iterator<Item = &'a &'a Transaction>,
315 {
316 let account_sums: Vec<(TxnAccount, Decimal)> =
317 Self::calculate_account_sums(txns, price_lookup_ctx);
318
319 let mut v: Vec<BalanceTreeNode> = account_sums
320 .into_iter()
321 .map(|(acctn, acc_sum)| BalanceTreeNode {
322 acctn: acctn.clone(),
323 sub_acc_tree_sum: Decimal::ZERO,
324 account_sum: acc_sum,
325 })
326 .collect();
327
328 v.sort_by(ord_by_btn);
329 Ok(v)
330 }
331}