|
@@ -1,4 +1,4 @@
|
|
|
-use std::collections::HashMap;
|
|
|
+use std::collections::{HashMap,BTreeSet};
|
|
|
|
|
|
use crate::data::{
|
|
|
AccountName, Datestamp, Decimal, UnitName,
|
|
@@ -167,78 +167,74 @@ fn import_from_csv(
|
|
|
Ok(txns)
|
|
|
}
|
|
|
|
|
|
-fn postprocess(account: AccountName, transactions: &mut Vec<Transaction>) {
|
|
|
- // check if we need to re-order transactions due to balances not lining up because of ordering
|
|
|
- let mut running_balances = HashMap::<UnitName, Decimal>::new();
|
|
|
- let mut idx = 0;
|
|
|
-
|
|
|
- // first get things vaguely sorted
|
|
|
- transactions.sort_by_key(|tx| tx.datestamp);
|
|
|
-
|
|
|
- let check_for_match = |running_balances: &mut HashMap<UnitName, Decimal>, change: &Change| {
|
|
|
- let bal = *change.balance.unwrap();
|
|
|
- match running_balances.entry(*change.unit) {
|
|
|
- std::collections::hash_map::Entry::Vacant(entry) => {
|
|
|
- entry.insert(bal);
|
|
|
- return true;
|
|
|
+fn recursive_order_search(txns: &[Transaction], account: AccountName, order: &mut Vec<usize>, remaining: &mut BTreeSet<usize>) -> bool {
|
|
|
+ if remaining.is_empty() {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ let possibles = remaining.iter().cloned().collect::<Vec<_>>();
|
|
|
+
|
|
|
+ if let Some(last) = order.last().as_deref() {
|
|
|
+ let Some(last_balance) = txns[*last].change_for(account).unwrap().balance.as_deref() else {
|
|
|
+ return false
|
|
|
+ };
|
|
|
+ for possible in possibles.iter() {
|
|
|
+ // check if balances line up
|
|
|
+ let change = txns[*possible].change_for(account).unwrap();
|
|
|
+ let Some(nbal) = change.balance else {
|
|
|
+ continue
|
|
|
+ };
|
|
|
+
|
|
|
+ if last_balance.checked_add(*change.amount) != Some(*nbal) {
|
|
|
+ continue
|
|
|
}
|
|
|
- std::collections::hash_map::Entry::Occupied(mut entry) => {
|
|
|
- let rbal = entry.get_mut();
|
|
|
- let new_rbal = rbal.checked_add(*change.amount).unwrap();
|
|
|
- if new_rbal != bal {
|
|
|
- return false;
|
|
|
- } else {
|
|
|
- *rbal = new_rbal;
|
|
|
- return true;
|
|
|
- }
|
|
|
+
|
|
|
+ remaining.remove(possible);
|
|
|
+ order.push(*possible);
|
|
|
+ if recursive_order_search(txns, account, order, remaining) {
|
|
|
+ return true
|
|
|
}
|
|
|
+ order.pop();
|
|
|
+ remaining.insert(*possible);
|
|
|
}
|
|
|
- };
|
|
|
-
|
|
|
- let mut removed: Vec<Transaction> = vec![];
|
|
|
-
|
|
|
- 'outer: loop {
|
|
|
- for ridx in 0..removed.len() {
|
|
|
- if check_for_match(
|
|
|
- &mut running_balances,
|
|
|
- removed[ridx].change_for(account).unwrap(),
|
|
|
- ) {
|
|
|
- transactions.insert(idx, removed.remove(ridx));
|
|
|
- log::trace!("pulling transaction out of removed");
|
|
|
- idx += 1;
|
|
|
- continue 'outer;
|
|
|
+ } else {
|
|
|
+ for possible in possibles.into_iter() {
|
|
|
+ remaining.remove(&possible);
|
|
|
+ order.push(possible);
|
|
|
+ if recursive_order_search(txns, account, order, remaining) {
|
|
|
+ return true
|
|
|
}
|
|
|
+ order.pop();
|
|
|
+ remaining.insert(possible);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if idx >= transactions.len() {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- let tx = &transactions[idx];
|
|
|
- let change = tx.change_for(account).unwrap();
|
|
|
- if change.balance.is_none() {
|
|
|
- idx += 1;
|
|
|
- continue;
|
|
|
- };
|
|
|
+ false
|
|
|
+}
|
|
|
|
|
|
- if check_for_match(&mut running_balances, change) {
|
|
|
- log::trace!(
|
|
|
- "transaction is good! balance is now: {}",
|
|
|
- running_balances[&*change.unit]
|
|
|
- );
|
|
|
- idx += 1;
|
|
|
- } else {
|
|
|
- log::trace!("shifting transaction to removed");
|
|
|
- removed.push(transactions.remove(idx));
|
|
|
- }
|
|
|
+fn postprocess(account: AccountName, transactions: &mut Vec<Transaction>) {
|
|
|
+ // check if we're sorted by datestamp already
|
|
|
+ if transactions.is_sorted_by_key(|tx| tx.datestamp) {
|
|
|
+ // already vaguely in the right order
|
|
|
+ } else if transactions.iter().rev().is_sorted_by_key(|tx| tx.datestamp) {
|
|
|
+ // reverse everything
|
|
|
+ transactions.reverse();
|
|
|
+ } else {
|
|
|
+ // otherwise try to get things vaguely sorted
|
|
|
+ transactions.sort_by_key(|tx| tx.datestamp);
|
|
|
}
|
|
|
|
|
|
- if removed.len() > 0 {
|
|
|
- log::error!(
|
|
|
- "Not all transactions are consistent! Inconsistent transactions below will be discarded:"
|
|
|
- );
|
|
|
- // crate::show::TransactionTable::default().show(
|
|
|
+ let mut to_assign = BTreeSet::from_iter(0..transactions.len());
|
|
|
+ let mut order = vec![];
|
|
|
+
|
|
|
+ if !recursive_order_search(transactions, account, &mut order, &mut to_assign) {
|
|
|
+ log::warn!("Unable to determine transaction ordering!");
|
|
|
+ return
|
|
|
}
|
|
|
+
|
|
|
+ let mut ntransact = order.iter().map(|v| transactions[*v].clone()).collect::<Vec<_>>();
|
|
|
+
|
|
|
+ std::mem::swap(&mut ntransact, transactions);
|
|
|
}
|
|
|
|
|
|
pub fn import_from(
|