diff --git a/Cargo.toml b/Cargo.toml index b4fd0b7..95d3206 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "budgetron" -version = "2.0.0-0.0.2" +version = "2.0.0-0.0.3" authors = ["Zachary Bush "] [[bin]] diff --git a/budgetronrc.yaml b/budgetronrc.yaml index 2fc2453..6c82316 100644 --- a/budgetronrc.yaml +++ b/budgetronrc.yaml @@ -169,8 +169,6 @@ budgets: Household: 150 yearly: Home Improvement: 5000 - rolling: - barry: {} owners: joint: @@ -202,7 +200,7 @@ owners: - "Card 4.*" rolling_budget: - start_date: '08/01/2017' + start_date: '10/01/2017' split: 'joint' skip_tags: - essential diff --git a/src/loading/generic.rs b/src/loading/generic.rs index 3f29889..3ecc6f3 100644 --- a/src/loading/generic.rs +++ b/src/loading/generic.rs @@ -15,6 +15,20 @@ impl Default for TransactionType { } } +impl TransactionType { + pub fn is_credit(&self) -> bool { + TransactionType::Credit == *self + } + + pub fn is_debit(&self) -> bool { + TransactionType::Debit == *self + } + + pub fn is_transfer(&self) -> bool { + TransactionType::Transfer == *self + } +} + #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Transaction { pub date: Date, diff --git a/src/processing/transfers.rs b/src/processing/transfers.rs index 847113a..a0d76e8 100644 --- a/src/processing/transfers.rs +++ b/src/processing/transfers.rs @@ -3,6 +3,7 @@ use loading::{Transaction, TransactionType}; use processing::Collate; use std::cmp::min; use std::collections::{HashMap, HashSet}; +use std::i64; pub struct TransferCollator { pub horizon: usize, @@ -19,28 +20,57 @@ impl Collate for TransferCollator { let mut to_delete = HashSet::new(); let mut to_update = HashMap::new(); for (i, t) in transactions.iter().enumerate() { - for j in i..min(transactions.len(), i + self.horizon) { - let ref tn = transactions[j]; - if tn.amount == t.amount && tn.transaction_type != t.transaction_type && - !to_delete.contains(&i) && !to_delete.contains(&j) && - !to_update.contains_key(&i) && !to_update.contains_key(&j) - { - if t.account_name == tn.account_name { - to_delete.insert(i); - to_delete.insert(j); - } else { - match t.transaction_type { - TransactionType::Debit => { - to_delete.insert(j); - to_update.insert(i, tn.account_name.clone()); - }, - TransactionType::Credit => { - to_delete.insert(i); - to_update.insert(j, t.account_name.clone()); - }, - TransactionType::Transfer => unreachable!(), - }; + loop { + let candidates: Vec<_> = (i..min(transactions.len(), i + self.horizon)) + .filter_map(|j| { + let ref tn = transactions[j]; + if tn.amount == t.amount && !to_delete.contains(&i) && + !to_delete.contains(&j) && + !to_update.contains_key(&i) && + !to_update.contains_key(&j) + { + Some(j) + } else { + None + } + }) + .collect(); + + if candidates.len() <= 1 { + break; + } + + let mut mindelta = i64::MAX; + let mut found_transfer = (0, 0); + let debits = candidates.iter().filter(|&i| { + transactions[*i].transaction_type.is_debit() + }); + + for debit_ix in debits { + let ref debit = transactions[*debit_ix]; + let credits = candidates.iter().filter(|&i| { + transactions[*i].transaction_type.is_credit() + }); + for credit_ix in credits { + let ref credit = transactions[*credit_ix]; + if (debit.date - credit.date).abs() < mindelta { + found_transfer = (*debit_ix, *credit_ix); + mindelta = (debit.date - credit.date).abs(); + } + } + } + + if found_transfer != (0, 0) { + let ref tn = transactions[found_transfer.1]; + + to_delete.insert(found_transfer.1); + to_update.insert(found_transfer.0, tn.account_name.clone()); + + if found_transfer.0 == i || found_transfer.1 == i { + break; } + } else { + break; } } } diff --git a/src/reporting/rolling_budget.rs b/src/reporting/rolling_budget.rs index fc134b5..c7078d8 100644 --- a/src/reporting/rolling_budget.rs +++ b/src/reporting/rolling_budget.rs @@ -86,16 +86,6 @@ impl Reporter for RollingBudget { _ => {}, } } - println!( - "Processing transaction: {:?} {} (${:.02}) [{}/{}/{}]", - transaction.transaction_type, - transaction.description, - transaction.amount, - transaction.person, - transaction.category, - transaction.original_category, - ); - println!(" -- {:?}", report.budgets["Zach"]); } } serde_json::to_value(&report).expect("Couldn't serialize")