use crate::export::Export;
use crate::kernel::balance::Balance;
use crate::kernel::report_item_selector::{
BalanceNonZeroByAccountSelector, BalanceNonZeroSelector, BalanceSelector,
};
use crate::kernel::Settings;
use crate::model::{Transaction, TxnSet};
use crate::report::get_account_selector_checksum;
use itertools::Itertools;
use rust_decimal::Decimal;
use std::error::Error;
use std::io;
use tackler_api::metadata::items::Text;
use time::format_description::well_known::Rfc3339;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct EquitySettings<'a> {
pub eqa: Option<String>,
pub ras: &'a Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct EquityExporter<'a> {
pub export_settings: EquitySettings<'a>,
}
impl EquityExporter<'_> {
fn get_acc_selector(&self) -> Result<Box<dyn BalanceSelector>, Box<dyn Error>> {
match self.export_settings.ras {
Some(v) => {
if v.is_empty() {
Ok(Box::new(BalanceNonZeroSelector {}))
} else {
let s: Vec<_> = v.iter().map(|s| s.as_str()).collect();
let ras = BalanceNonZeroByAccountSelector::from(&s)?;
Ok(Box::new(ras))
}
}
None => Ok(Box::<BalanceNonZeroSelector>::default()),
}
}
}
impl Export for EquityExporter<'_> {
fn write_export<W: io::Write + ?Sized>(
&self,
cfg: &Settings,
writer: &mut W,
txn_data: &TxnSet,
) -> Result<(), Box<dyn Error>> {
let bal_acc_sel = self.get_acc_selector()?;
let bal = Balance::from(&String::default(), txn_data, bal_acc_sel.as_ref());
if bal.is_empty() {
return Ok(());
};
let eq_txn_indent = " ";
let equity_account = "Equity:Default·Account".to_string();
let hdr_str = |last_txn: Option<&&Transaction>, c: &String| -> String {
let comm_str = || -> String {
if c.is_empty() {
String::default()
} else {
format!(" for {}", c)
}
};
let txn_uuid_str = |uuid: Option<Uuid>| -> String {
match uuid {
Some(u) => format!(": last txn (uuid): {}", u),
None => String::default(),
}
};
match last_txn {
Some(txn) => {
format!(
"{} 'Equity{}{}",
txn.header.timestamp.format(&Rfc3339).unwrap(), comm_str(),
txn_uuid_str(txn.header.uuid)
)
}
_ => {
"Internal logic error".to_string() }
}
};
let last_txn = txn_data.txns.last();
let equity_txn_str: Vec<String> = bal
.bal
.iter()
.chunk_by(|btn| &btn.acctn.commodity_str)
.into_iter()
.flat_map(|(c, bs)| {
let btns: Vec<_> = bs.collect();
let dsum: Decimal = btns.clone().into_iter().map(|b| b.account_sum).sum();
let bal_posting = {
let value = if c.is_empty() {
format!("{}", -dsum)
} else {
format!("{} {}", -dsum, c)
};
let ea = match &self.export_settings.eqa {
Some(eqa) => eqa,
None => &equity_account,
};
format!("{}{} {}", eq_txn_indent, ea, value)
};
let eq_postings = btns
.into_iter()
.map(|b| {
format!(
"{}{} {}{}",
eq_txn_indent,
b.acctn.account,
b.account_sum,
b.acctn
.commodity
.as_ref()
.map(|c| { format!(" {}", c.name) })
.get_or_insert(String::default())
)
})
.collect::<Vec<String>>();
let mut eq_txn = Vec::<String>::new();
eq_txn.push(hdr_str(last_txn, c));
if let Some(md) = &txn_data.metadata {
for mdi in md.items.clone() {
eq_txn.extend(mdi.text().iter().map(|v| {
format!("{}; {}", eq_txn_indent, v)
}).collect::<Vec<_>>());
eq_txn.push(format!("{};", eq_txn_indent));
}
if let Some(asc) = get_account_selector_checksum(cfg, self.export_settings.ras).unwrap() { for v in asc.text() {
eq_txn.push(format!("{}; {}", eq_txn_indent, &v));
}
eq_txn.push(format!("{};", eq_txn_indent));
}
};
if dsum.is_zero() {
eq_txn.push(format!("{}; WARNING:", eq_txn_indent));
eq_txn.push(format!("{}; WARNING: The sum of equity transaction is zero without equity account.", eq_txn_indent));
eq_txn.push(format!("{}; WARNING: Therefore there is no equity posting row, and this is probably not right.", eq_txn_indent));
eq_txn.push(format!("{}; WARNING: Is account selector correct for this Equity Export?", eq_txn_indent));
eq_txn.push(format!("{}; WARNING:", eq_txn_indent));
}
eq_txn.extend(eq_postings);
if !dsum.is_zero() {
eq_txn.push(bal_posting);
}
eq_txn.push("".to_string());
eq_txn
})
.collect();
for i in equity_txn_str {
writeln!(writer, "{}", i)?;
}
Ok(())
}
}