use super::Conversion;
use anyhow::{Context, Result};
use rust_decimal;
use rustledger_core::Directive;
use rustledger_loader::Loader;
use std::collections::BTreeMap;
use std::io::Write;
use std::path::PathBuf;
pub(super) fn cmd_region<W: Write>(
file: &PathBuf,
start_line: usize,
end_line: usize,
conversion: Option<Conversion>,
writer: &mut W,
) -> Result<()> {
let mut loader = Loader::new();
let load_result = loader
.load(file)
.with_context(|| format!("failed to load {}", file.display()))?;
let conversion_str = match conversion {
Some(Conversion::Value) => " (converted to value)",
Some(Conversion::Cost) => " (converted to cost)",
None => "",
};
writeln!(
writer,
"Transactions in region {}:{}-{}{}",
file.display(),
start_line,
end_line,
conversion_str
)?;
writeln!(writer, "{}", "=".repeat(60))?;
writeln!(writer)?;
let source_file = load_result.source_map.get(0);
let mut region_transactions: Vec<&rustledger_core::Transaction> = Vec::new();
for spanned in &load_result.directives {
if let Directive::Transaction(txn) = &spanned.value {
let txn_line = match source_file {
Some(sf) => sf.line_col(spanned.span.start).0,
None => continue,
};
if txn_line >= start_line && txn_line <= end_line {
region_transactions.push(txn);
}
}
}
if region_transactions.is_empty() {
writeln!(
writer,
"No transactions found in lines {start_line}-{end_line}"
)?;
return Ok(());
}
writeln!(
writer,
"Found {} transaction(s):",
region_transactions.len()
)?;
writeln!(writer)?;
let mut balances: BTreeMap<String, rust_decimal::Decimal> = BTreeMap::new();
for txn in ®ion_transactions {
writeln!(writer, "{} {} \"{}\"", txn.date, txn.flag, txn.narration)?;
for posting in &txn.postings {
if let Some(amount) = posting.amount() {
let (display_number, display_currency) = match conversion {
Some(Conversion::Cost) => {
if let Some(ref cost) = posting.cost {
let total_cost = if let Some(total) = cost.number_total {
total
} else if let Some(per_unit) = cost.number_per {
amount.number * per_unit
} else {
amount.number
};
let currency = cost
.currency
.clone()
.unwrap_or_else(|| amount.currency.clone());
(total_cost, currency)
} else {
(amount.number, amount.currency.clone())
}
}
Some(Conversion::Value) => {
writeln!(
writer,
" (Note: value conversion requires price database, showing units)"
)?;
(amount.number, amount.currency.clone())
}
None => (amount.number, amount.currency.clone()),
};
writeln!(
writer,
" {} {} {}",
posting.account, display_number, display_currency
)?;
*balances
.entry(format!("{}:{}", posting.account, display_currency))
.or_default() += display_number;
} else {
writeln!(writer, " {}", posting.account)?;
}
}
writeln!(writer)?;
}
writeln!(writer, "Net changes{conversion_str}:")?;
for (key, balance) in &balances {
if !balance.is_zero() {
writeln!(writer, " {key}: {balance}")?;
}
}
Ok(())
}