diff options
| author | Joe Carstairs <me@joeac.net> | 2024-11-26 23:08:35 +0000 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2024-11-26 23:08:35 +0000 |
| commit | c2d04b27767105b04df1f2784ca3f3d905ec9755 (patch) | |
| tree | 768799a937ae9e9cabc2bfa70b8bf8234f01e85f /rust/actualbudget_to_schist_transformer/src/transform_state | |
| parent | 58873c034847557122905cd893678eae5469b503 (diff) | |
Transform AB transactions
Diffstat (limited to 'rust/actualbudget_to_schist_transformer/src/transform_state')
| -rw-r--r-- | rust/actualbudget_to_schist_transformer/src/transform_state/transform_date.rs | 14 | ||||
| -rw-r--r-- | rust/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs | 184 |
2 files changed, 166 insertions, 32 deletions
diff --git a/rust/actualbudget_to_schist_transformer/src/transform_state/transform_date.rs b/rust/actualbudget_to_schist_transformer/src/transform_state/transform_date.rs index 885ed84..57adae0 100644 --- a/rust/actualbudget_to_schist_transformer/src/transform_state/transform_date.rs +++ b/rust/actualbudget_to_schist_transformer/src/transform_state/transform_date.rs @@ -21,11 +21,11 @@ mod test { assert_eq!(expected_date_utc, date_utc); } - #[test] - fn when_utc_epoch_as_month_then_utc_epoch() { - let actualbudget_date = ActualbudgetDate::from_i32(197001); - let date_utc = transform_date(&actualbudget_date).unwrap(); - let expected_date_utc: DateUtc = "1970-01-01".parse().unwrap(); - assert_eq!(expected_date_utc, date_utc); - } + #[test] + fn when_utc_epoch_as_month_then_utc_epoch() { + let actualbudget_date = ActualbudgetDate::from_i32(197001); + let date_utc = transform_date(&actualbudget_date).unwrap(); + let expected_date_utc: DateUtc = "1970-01-01".parse().unwrap(); + assert_eq!(expected_date_utc, date_utc); + } } diff --git a/rust/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs b/rust/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs index 36f3d81..3e9b49a 100644 --- a/rust/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs +++ b/rust/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs @@ -8,27 +8,39 @@ pub fn transform_transactions( actualbudget_transactions: &[ActualbudgetTransaction], actualbudget_category_transfers_account: &Option<ActualbudgetAccount>, ) -> Result<(Vec<Transaction>, Vec<TransactionCategorisation>)> { + transform_transactions_without_context(actualbudget_transactions, actualbudget_category_transfers_account) + .with_context(|| "failed to transform actualbudget transactions to schist transactions") +} + +fn transform_transactions_without_context( + actualbudget_transactions: &[ActualbudgetTransaction], + actualbudget_category_transfers_account: &Option<ActualbudgetAccount>, +) -> Result<(Vec<Transaction>, Vec<TransactionCategorisation>)> { let mut transactions = Vec::<Transaction>::new(); let mut transaction_categorisations = Vec::<TransactionCategorisation>::new(); - let mut actualbudget_transactions = actualbudget_transactions.to_vec(); - actualbudget_transactions.retain(|transaction| - is_not_category_transfer(transaction, actualbudget_category_transfers_account) - ); - let mut parent_actualbudget_transactions = actualbudget_transactions.to_vec(); - parent_actualbudget_transactions.retain(|t| t.is_parent); - let mut child_actualbudget_transactions = actualbudget_transactions.to_vec(); - child_actualbudget_transactions.retain(|t| t.is_child); - let mut atomic_actualbudget_transactions = actualbudget_transactions.to_vec(); - atomic_actualbudget_transactions.retain(|t| !t.is_parent && !t.is_child); + let actualbudget_transactions = actualbudget_transactions + .iter() + .filter(|t| is_not_category_transfer(t, actualbudget_category_transfers_account)); + let parent_actualbudget_transactions: Vec<&ActualbudgetTransaction> = actualbudget_transactions + .clone() + .filter(|t| t.is_parent) + .collect(); + let child_actualbudget_transactions: Vec<&ActualbudgetTransaction> = actualbudget_transactions + .clone() + .filter(|t| t.is_child) + .collect(); + let atomic_actualbudget_transactions: Vec<&ActualbudgetTransaction> = actualbudget_transactions + .filter(|t| !t.is_parent && !t.is_child) + .collect(); for atomic_actualbudget_transaction in atomic_actualbudget_transactions { let transaction_id = transform_id(&atomic_actualbudget_transaction.id); transactions.push(Transaction { id: transaction_id, - description: atomic_actualbudget_transaction.notes.unwrap_or_else(String::new), - payee: atomic_actualbudget_transaction.payee.unwrap_or_else(String::new), + description: atomic_actualbudget_transaction.notes.clone().unwrap_or_else(String::new), + payee: atomic_actualbudget_transaction.payee.clone().unwrap_or_else(String::new), quantity: atomic_actualbudget_transaction.amount.try_into().unwrap(), date: transform_date(&atomic_actualbudget_transaction.date)?, account_id: transform_id(&atomic_actualbudget_transaction.account_id), @@ -36,7 +48,11 @@ pub fn transform_transactions( let actualbudget_category_id = atomic_actualbudget_transaction .category_id - .expect("actualbudget transaction should have been atomic, but in fact had no category ID"); + .clone() + .with_context(|| format!( + "atomic actualbudget transaction {} did not have a category ID", + atomic_actualbudget_transaction.id, + ))?; transaction_categorisations.push(TransactionCategorisation { id: transaction_id, @@ -48,19 +64,20 @@ pub fn transform_transactions( } for parent_actualbudget_transaction in parent_actualbudget_transactions { - let mut child_actualbudget_transactions = child_actualbudget_transactions.to_vec(); - child_actualbudget_transactions.retain(|t| { - t.parent_id - .clone() - .is_some_and(|parent_id| parent_id == parent_actualbudget_transaction.id.clone()) - }); + let child_actualbudget_transactions = child_actualbudget_transactions + .iter() + .filter(|t| t + .parent_id + .clone() + .is_some_and(|parent_id| parent_id == parent_actualbudget_transaction.id.clone()) + ); let transaction_id = transform_id(&parent_actualbudget_transaction.id); transactions.push(Transaction { id: transaction_id, - description: parent_actualbudget_transaction.notes.unwrap_or_else(String::new), - payee: parent_actualbudget_transaction.payee.unwrap_or_else(String::new), + description: parent_actualbudget_transaction.notes.clone().unwrap_or_else(String::new), + payee: parent_actualbudget_transaction.payee.clone().unwrap_or_else(String::new), quantity: parent_actualbudget_transaction.amount.try_into().unwrap(), date: transform_date(&parent_actualbudget_transaction.date)?, account_id: transform_id(&parent_actualbudget_transaction.account_id), @@ -69,11 +86,15 @@ pub fn transform_transactions( for child_actualbudget_transaction in child_actualbudget_transactions { let actualbudget_category_id = child_actualbudget_transaction .category_id - .expect("actualbudget transaction should have been a child transaction, but in fact had no category ID"); + .clone() + .with_context(|| format!( + "actualbudget child transaction {} had no category ID", + child_actualbudget_transaction.id, + ))?; transaction_categorisations.push(TransactionCategorisation { id: transform_id(&child_actualbudget_transaction.id), - description: child_actualbudget_transaction.notes.unwrap_or_else(String::new), + description: child_actualbudget_transaction.notes.clone().unwrap_or_else(String::new), quantity: child_actualbudget_transaction.amount.try_into().unwrap(), transaction_id, category_id: transform_id(&actualbudget_category_id), @@ -86,8 +107,121 @@ pub fn transform_transactions( #[cfg(test)] mod test { + use actualbudget_fakes::{actualbudget_account::make_fake_actualbudget_account, actualbudget_transaction::make_fake_actualbudget_transaction_builder}; + use actualbudget_models::ActualbudgetDate; + use schist_models::date_utc::DateUtc; + + use crate::transform_state::transform_id::transform_id; + + use super::transform_transactions; + #[test] - fn todo() { - todo!() + fn when_one_category_transfer_transaction_then_returns_empty() { + let actualbudget_transactions = [ + make_fake_actualbudget_transaction_builder("0") + .category_id(Some(String::from("0"))) + .build() + .unwrap(), + ]; + let actualbudget_category_transfers_account = make_fake_actualbudget_account("0"); + + let (transactions, transaction_categorisations) = transform_transactions( + &actualbudget_transactions, + &Some(actualbudget_category_transfers_account), + ).unwrap(); + + assert_eq!(0, transactions.len()); + assert_eq!(0, transaction_categorisations.len()); + } + + #[test] + fn when_one_atomic_transaction_then_returns_transformed_transaction() { + let actualbudget_transactions = [ + make_fake_actualbudget_transaction_builder("0") + .account_id(String::from("0")) + .amount(100) + .category_id(Some(String::from("0"))) + .payee(Some(String::from("Gringotts"))) + .notes(Some(String::from("Savings"))) + .date(ActualbudgetDate::from_i32(19700101)) + .build() + .unwrap(), + ]; + + let (transactions, transaction_categorisations) = transform_transactions( + &actualbudget_transactions, + &None, + ).unwrap(); + + assert_eq!(1, transactions.len()); + let transaction = transactions.get(0).unwrap(); + assert_eq!("Savings", transaction.description); + assert_eq!("Gringotts", transaction.payee); + assert_eq!(100, transaction.quantity); + assert_eq!(DateUtc::from_ymd(1970, 1, 1).unwrap(), transaction.date); + assert_eq!(transform_id("0"), transaction.account_id); + + assert_eq!(1, transaction_categorisations.len()); + let transaction_categorisation = transaction_categorisations.get(0).unwrap(); + assert_eq!("", transaction_categorisation.description); + assert_eq!(100, transaction_categorisation.quantity); + assert_eq!(transaction.id, transaction_categorisation.transaction_id); + assert_eq!(transform_id("0"), transaction_categorisation.category_id); + } + + #[test] + fn when_one_split_transaction_then_returns_transformed_transaction() { + let actualbudget_transactions = [ + make_fake_actualbudget_transaction_builder("0") + .is_parent(true) + .amount(100) + .build() + .unwrap(), + make_fake_actualbudget_transaction_builder("1") + .amount(200) + .category_id(Some(String::from("0"))) + .is_child(true) + .parent_id(Some(String::from("0"))) + .notes(Some(String::from("notes A"))) + .build() + .unwrap(), + make_fake_actualbudget_transaction_builder("2") + .amount(300) + .category_id(Some(String::from("0"))) + .is_child(true) + .parent_id(Some(String::from("0"))) + .notes(Some(String::from("notes B"))) + .build() + .unwrap(), + ]; + + let (transactions, transaction_categorisations) = transform_transactions( + &actualbudget_transactions, + &None, + ).unwrap(); + + assert_eq!(1, transactions.len()); + assert_eq!(2, transaction_categorisations.len()); + + let transaction = transactions.get(0).unwrap(); + assert_eq!(100, transaction.quantity); + + let transaction_categorisation_0 = transaction_categorisations + .iter() + .filter(|tc| tc.quantity == 200) + .last() + .unwrap(); + assert_eq!(transaction.id, transaction_categorisation_0.transaction_id); + assert_eq!(transform_id("0"), transaction_categorisation_0.category_id); + assert_eq!("notes A", transaction_categorisation_0.description); + + let transaction_categorisation_1 = transaction_categorisations + .iter() + .filter(|tc| tc.quantity == 300) + .last() + .unwrap(); + assert_eq!(transaction.id, transaction_categorisation_1.transaction_id); + assert_eq!(transform_id("0"), transaction_categorisation_1.category_id); + assert_eq!("notes B", transaction_categorisation_1.description); } } |
