|
@@ -1,3 +1,5 @@
|
|
|
+use std::collections::HashMap;
|
|
|
+
|
|
|
use crate::data::{
|
|
|
AccountName, Datestamp, Decimal, UnitName,
|
|
|
ledger::{Change, Transaction},
|
|
@@ -165,6 +167,72 @@ 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
|
|
|
+ },
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if idx >= transactions.len() {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ let tx = &transactions[idx];
|
|
|
+ let change = tx.change_for(account).unwrap();
|
|
|
+ if change.balance.is_none() {
|
|
|
+ idx += 1;
|
|
|
+ continue
|
|
|
+ };
|
|
|
+
|
|
|
+ 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));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if removed.len() > 0 {
|
|
|
+ log::error!("Not all transactions are consistent!");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
pub fn import_from(
|
|
|
aspec: &AccountSpec,
|
|
|
target: AccountName,
|
|
@@ -172,10 +240,14 @@ pub fn import_from(
|
|
|
) -> Result<Vec<Transaction>, ImportError> {
|
|
|
let reader = std::fs::File::open(path)?;
|
|
|
|
|
|
- match &aspec.import {
|
|
|
+ let mut output = match &aspec.import {
|
|
|
Some(ImportFileFormat::Csv(csv)) => import_from_csv(csv, aspec, target, reader),
|
|
|
None => Err(ImportError::ConfigError(format!(
|
|
|
"no import configuration for {target}"
|
|
|
))),
|
|
|
- }
|
|
|
+ }?;
|
|
|
+
|
|
|
+ postprocess(target, &mut output);
|
|
|
+
|
|
|
+ Ok(output)
|
|
|
}
|