use crate::kernel::balance::Balance;
use crate::kernel::price_lookup::PriceLookupCtx;
use crate::kernel::report_item_selector::{BalanceSelector, RegisterSelector};
use crate::kernel::{RegisterSettings, Settings};
use crate::model::{RegisterEntry, RegisterPosting, Transaction, TxnAccount, TxnRefs};
use crate::tackler;
use itertools::Itertools;
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::io;
pub(crate) type RegisterReporterFn<W> =
fn(writer: &mut W, &RegisterEntry<'_>, &RegisterSettings) -> Result<(), tackler::Error>;
pub(crate) type TxnGroupByOp<'a> = Box<dyn Fn(&Transaction) -> String + 'a>;
pub(crate) fn balance_groups<T>(
txns: &TxnRefs<'_>,
group_by_op: TxnGroupByOp<'_>,
price_lookup_ctx: &PriceLookupCtx<'_>,
ras: &T,
settings: &Settings,
) -> Vec<Balance>
where
T: BalanceSelector + ?Sized,
{
txns.iter()
.chunk_by(|txn| group_by_op(txn))
.into_iter()
.map(|(group_by_key, bal_grp_txns)| {
Balance::from_iter(&group_by_key, bal_grp_txns, price_lookup_ctx, ras, settings)
.expect("Logic error with Balance Group: inner balance failed")
})
.filter(|bal| !bal.is_empty())
.sorted_by_key(|bal| bal.title.clone())
.collect()
}
pub(crate) fn register_engine<'a, W, T>(
txns: &'a TxnRefs<'_>,
price_lookup_ctx: &PriceLookupCtx<'_>,
ras: &T,
w: &mut W,
reporter: RegisterReporterFn<W>,
register_settings: &RegisterSettings,
) -> Result<(), tackler::Error>
where
W: io::Write + ?Sized,
T: RegisterSelector<'a> + ?Sized,
{
let mut register_engine: HashMap<TxnAccount, Decimal> = HashMap::new();
for txn in txns {
let register_postings: Vec<_> = price_lookup_ctx
.convert_prices(txn)
.zip(&txn.posts)
.sorted_by(|a, b| Ord::cmp(&a.1.acctn, &b.1.acctn))
.map(|((conv_acctn, conv_amount, rate), orig_p)| {
let running_total = *register_engine
.entry(conv_acctn.clone())
.and_modify(|v| {
*v += conv_amount;
})
.or_insert(conv_amount);
RegisterPosting {
post: orig_p,
amount: running_total,
target_commodity: conv_acctn.comm,
rate,
}
})
.collect();
let mut filt_postings: Vec<_> = register_postings
.into_iter()
.filter(|p| ras.eval(p))
.collect();
filt_postings.sort();
let register_entry = RegisterEntry {
txn,
posts: filt_postings,
};
reporter(w, ®ister_entry, register_settings)?;
}
Ok(())
}