use crate::kernel::report_item_selector::BalanceSelector;
use crate::model::balance_tree_node::ord_by_btn;
use crate::model::{AccountTreeNode, BalanceTreeNode, Commodity, TxnRefs, TxnSet};
use itertools::Itertools;
use rust_decimal::Decimal;
use std::collections::{HashMap, HashSet};
pub type Deltas = HashMap<Option<Commodity>, Decimal>;
pub type BTNs = Vec<BalanceTreeNode>;
#[derive(Debug)]
pub struct Balance {
pub(crate) title: String,
pub(crate) bal: BTNs,
pub(crate) deltas: Deltas,
}
impl Balance {
pub(crate) fn is_empty(&self) -> bool {
self.bal.is_empty()
}
}
impl Balance {
fn get_balance_tree_nodes(
me: &(AccountTreeNode, Decimal),
acc_sums: &[(AccountTreeNode, Decimal)],
) -> Vec<BalanceTreeNode> {
let (my_acctn, my_sum) = me;
let childs = acc_sums
.iter()
.filter(|(atn, _)| my_acctn.is_parent_of(atn));
let childs_balance_trees = childs
.flat_map(|c| Balance::get_balance_tree_nodes(c, acc_sums))
.collect::<Vec<BalanceTreeNode>>();
let my_childs_sum = childs_balance_trees
.iter()
.filter(|btn| btn.acctn.parent == my_acctn.account)
.map(|btn| btn.sub_acc_tree_sum)
.sum::<Decimal>();
let my_btn = BalanceTreeNode {
acctn: my_acctn.clone(),
sub_acc_tree_sum: my_childs_sum + my_sum,
account_sum: *my_sum,
};
let mut x = vec![my_btn];
x.extend(childs_balance_trees);
x
}
fn bubble_up_acctn(
my_acctn_sum: &(AccountTreeNode, Decimal),
acc_sums: &[(AccountTreeNode, Decimal)],
) -> Vec<(AccountTreeNode, Decimal)> {
let my_acctn = &my_acctn_sum.0;
if my_acctn.depth == 1 {
vec![my_acctn_sum.clone()]
} else {
let parent = acc_sums
.iter()
.filter(|(atn, _)| atn.is_parent_of(my_acctn))
.collect::<Vec<&(AccountTreeNode, Decimal)>>();
assert!(parent.is_empty() || parent.len() == 1);
if parent.is_empty() {
if my_acctn.depth > 2 {
let new_parent_atn = AccountTreeNode::from(my_acctn.parent.clone(), my_acctn.commodity.clone())
.unwrap();
let mut sub_tree = vec![my_acctn_sum.clone()];
let mut x =
Balance::bubble_up_acctn(&(new_parent_atn, Decimal::ZERO), acc_sums);
x.append(&mut sub_tree);
x
} else {
let new_parent_atn = AccountTreeNode::from(my_acctn.parent.clone(), my_acctn.commodity.clone())
.unwrap();
vec![(new_parent_atn, Decimal::ZERO), my_acctn_sum.clone()]
}
} else {
let mut sub_tree = vec![my_acctn_sum.clone()];
let mut x = Balance::bubble_up_acctn(parent[0], acc_sums);
x.append(&mut sub_tree);
x
}
}
}
fn balance(txns: &TxnRefs) -> Vec<BalanceTreeNode> {
let account_sums: Vec<_> = txns
.iter()
.flat_map(|txn| &txn.posts)
.sorted_by_key(|p| p.acctn.get_full())
.chunk_by(|p| p.acctn.get_full())
.into_iter()
.map(|(_, postings)| {
let mut ps = postings.peekable();
let acctn = ps.peek().unwrap().acctn.clone();
let acc_sum = ps.map(|p| p.amount).sum::<Decimal>();
(acctn, acc_sum)
})
.collect();
let complete_coa_sum_tree = account_sums
.iter()
.flat_map(|acc| Balance::bubble_up_acctn(acc, &account_sums))
.collect::<HashSet<_>>() .into_iter()
.collect::<Vec<(AccountTreeNode, Decimal)>>();
let roots = complete_coa_sum_tree
.iter()
.filter(|(acctn, _)| acctn.depth == 1);
let mut bal = roots
.flat_map(|root_acc_sum| {
Balance::get_balance_tree_nodes(root_acc_sum, &complete_coa_sum_tree)
})
.collect::<Vec<BalanceTreeNode>>();
bal.sort_by(ord_by_btn);
bal
}
pub fn from<T>(title: &str, txn_set: &TxnSet, accounts: &T) -> Balance
where
T: BalanceSelector + ?Sized,
{
let bal = Balance::balance(&txn_set.txns);
let filt_bal: Vec<_> = bal.iter().filter(|b| accounts.eval(b)).cloned().collect();
if filt_bal.is_empty() {
Balance {
title: title.to_string(),
bal: Default::default(),
deltas: Default::default(),
}
} else {
let deltas = filt_bal
.iter()
.chunk_by(|btn| &btn.acctn.commodity_str)
.into_iter()
.map(|(c, bs)| {
let dsum = bs.map(|b| b.account_sum).sum();
if c.is_empty() {
(None, dsum)
} else {
(Some(Commodity { name: c.clone() }), dsum)
}
})
.collect();
Balance {
title: title.to_string(),
bal: filt_bal,
deltas,
}
}
}
}