Browse Source

Improved import code to produce consistent balances when present.

Kestrel 2 weeks ago
parent
commit
4d1228a551
2 changed files with 76 additions and 4 deletions
  1. 74 2
      src/import.rs
  2. 2 2
      testdata/ledger

+ 74 - 2
src/import.rs

@@ -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)
 }

+ 2 - 2
testdata/ledger

@@ -1,3 +1,5 @@
+# vim: set filetype=hoard :
+
 2001-01-05: initial balance
  - initial    : -400.00 CAD
  - chequing   :  400.00 CAD
@@ -14,5 +16,3 @@
  - savings    : -100.00 CAD
  - savings_usd:  80.00 USD
 
-# vim: set filetype=hoard :
-