|
@@ -1,5 +1,7 @@
|
|
use std::collections::BTreeMap;
|
|
use std::collections::BTreeMap;
|
|
|
|
|
|
|
|
+use itertools::Itertools;
|
|
|
|
+
|
|
use ariadne::Span as ASpan;
|
|
use ariadne::Span as ASpan;
|
|
use chumsky::span::Span as CSpan;
|
|
use chumsky::span::Span as CSpan;
|
|
|
|
|
|
@@ -105,6 +107,61 @@ fn check_precision(root: &data::Hoard) -> Result<(), DataError> {
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+fn check_balance_group<'a>(
|
|
|
|
+ account: data::AccountName,
|
|
|
|
+ running: &mut BTreeMap<data::UnitName, Spanned<data::Decimal>>,
|
|
|
|
+ group: impl Iterator<Item = &'a data::TransactionRef>,
|
|
|
|
+) -> Result<(), DataError> {
|
|
|
|
+ let group = group.collect_vec();
|
|
|
|
+ let group_len = group.len();
|
|
|
|
+
|
|
|
|
+ 'order_loop: for order in group.iter().permutations(group_len) {
|
|
|
|
+ let mut balance = running.clone();
|
|
|
|
+
|
|
|
|
+ for txn_ref in order {
|
|
|
|
+ let txn = txn_ref.borrow();
|
|
|
|
+ let change = txn.change_for(account).unwrap();
|
|
|
|
+
|
|
|
|
+ let bal = balance
|
|
|
|
+ .entry(*change.unit)
|
|
|
|
+ .or_insert_with(|| Spanned::new(data::Decimal::default(), io::Span::default()));
|
|
|
|
+ bal.0 = bal.checked_add(*change.amount).unwrap();
|
|
|
|
+ bal.1 = change.source.unwrap();
|
|
|
|
+
|
|
|
|
+ // check if assertion(s) are met
|
|
|
|
+ if let Some(assertion) = change.balance.as_ref() {
|
|
|
|
+ if **assertion != bal.0 {
|
|
|
|
+ continue 'order_loop;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* found an ordering that works! */
|
|
|
|
+ *running = balance;
|
|
|
|
+ return Ok(());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let first = group[0].borrow();
|
|
|
|
+ let report = ariadne::Report::build(
|
|
|
|
+ ariadne::ReportKind::Error,
|
|
|
|
+ first.source.unwrap().union(first.change_for(account).unwrap().source.unwrap()),
|
|
|
|
+ )
|
|
|
|
+ .with_message("No valid ordering of transactions to satisfy balance assertions")
|
|
|
|
+ .with_labels(
|
|
|
|
+ group
|
|
|
|
+ .iter()
|
|
|
|
+ .map(|txn| {
|
|
|
|
+ let txn = txn.borrow();
|
|
|
|
+ let span = txn.source.unwrap().union(txn.change_for(account).unwrap().source.unwrap());
|
|
|
|
+ ariadne::Label::new(span)
|
|
|
|
+ .with_message("transaction in group")
|
|
|
|
+ }),
|
|
|
|
+ )
|
|
|
|
+ .finish();
|
|
|
|
+
|
|
|
|
+ Err(DataError::Report(Box::new(report)))
|
|
|
|
+}
|
|
|
|
+
|
|
fn check_balances(root: &data::Hoard) -> Result<(), DataError> {
|
|
fn check_balances(root: &data::Hoard) -> Result<(), DataError> {
|
|
log::trace!("Checking balance consistency...");
|
|
log::trace!("Checking balance consistency...");
|
|
|
|
|
|
@@ -114,6 +171,13 @@ fn check_balances(root: &data::Hoard) -> Result<(), DataError> {
|
|
};
|
|
};
|
|
|
|
|
|
let mut running_balance = BTreeMap::<data::UnitName, Spanned<data::Decimal>>::new();
|
|
let mut running_balance = BTreeMap::<data::UnitName, Spanned<data::Decimal>>::new();
|
|
|
|
+ let date_groups = ledger.iter().chunk_by(|tx| tx.borrow().datestamp);
|
|
|
|
+
|
|
|
|
+ for group in date_groups.into_iter() {
|
|
|
|
+ check_balance_group(account, &mut running_balance, group.1)?;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
for txn_ref in ledger {
|
|
for txn_ref in ledger {
|
|
let txn = txn_ref.borrow();
|
|
let txn = txn_ref.borrow();
|
|
let change = txn.change_for(account).unwrap();
|
|
let change = txn.change_for(account).unwrap();
|
|
@@ -147,6 +211,7 @@ fn check_balances(root: &data::Hoard) -> Result<(), DataError> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ */
|
|
}
|
|
}
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|