From fa1d9941e2d3969a1035d36c97f3363628a409c0 Mon Sep 17 00:00:00 2001 From: Joe Carstairs Date: Sat, 17 Jan 2026 07:53:58 +0000 Subject: Refactor transform_bucket_transactions -> transform_bucket_transactions_from_category_transfers --- .../src/transform_state.rs | 5 +- .../transform_bucket_transactions.rs | 406 -------------------- ..._bucket_transactions_from_category_transfers.rs | 415 +++++++++++++++++++++ .../src/transform_state/transform_state.rs | 16 +- 4 files changed, 429 insertions(+), 413 deletions(-) delete mode 100644 transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions.rs create mode 100644 transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_category_transfers.rs diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state.rs index 1f5ead3..ae560c6 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state.rs @@ -2,7 +2,7 @@ mod is_account_transfer; mod is_category_transfer; mod transform_account_transfers; mod transform_accounts; -mod transform_bucket_transactions; +mod transform_bucket_transactions_from_category_transfers; mod transform_categories_to_buckets; mod transform_date; mod transform_id; @@ -17,11 +17,12 @@ use is_category_transfer::is_category_transfer; use is_category_transfer::is_not_category_transfer; use transform_account_transfers::transform_account_transfers; use transform_accounts::transform_accounts; -use transform_bucket_transactions::transform_bucket_transactions; +use transform_bucket_transactions_from_category_transfers::transform_bucket_transactions_from_category_transfers; use transform_categories_to_buckets::transform_categories_to_buckets; use transform_date::transform_date; use transform_id::transform_id; use transform_transactions::transform_transactions; +use transform_zero_budgets_to_drips::transform_budget_drips; use transform_zero_budgets_to_pipes::transform_zero_budgets_to_pipes; pub use transform_state::transform_state; diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions.rs deleted file mode 100644 index 22ee95e..0000000 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions.rs +++ /dev/null @@ -1,406 +0,0 @@ -use actualbudget_models::ActualbudgetTransaction; -use anyhow::{bail, Context, Result}; -use schist_models::BucketTransaction; - -use super::{is_category_transfer, transform_id}; - -pub fn transform_bucket_transactions( - actualbudget_transactions: &[ActualbudgetTransaction], -) -> Result> { - let actualbudget_category_transfers = actualbudget_transactions - .iter() - .filter(|transaction| is_category_transfer(transaction, actualbudget_transactions)); - let actualbudget_parent_category_transfers: Vec = - actualbudget_category_transfers - .clone() - .filter(|t| t.is_parent) - .map(ActualbudgetTransaction::clone) - .collect(); - let actualbudget_child_category_transfers: Vec = - actualbudget_category_transfers - .filter(|t| t.is_child) - .map(ActualbudgetTransaction::clone) - .collect(); - - let mut result = Vec::new(); - for pct in actualbudget_parent_category_transfers { - result.extend( - transform_category_transfer(&pct, &actualbudget_child_category_transfers) - .with_context(|| "failed to transform bucket transactions")?, - ) - } - Ok(result) -} - -fn transform_category_transfer( - actualbudget_parent_category_transfer: &ActualbudgetTransaction, - actualbudget_child_category_transfers: &[ActualbudgetTransaction], -) -> Result> { - transform_category_transfer_without_context( - actualbudget_parent_category_transfer, - actualbudget_child_category_transfers, - ) - .with_context(|| "failed to transform category transfer") -} - -fn transform_category_transfer_without_context( - actualbudget_parent_category_transfer: &ActualbudgetTransaction, - actualbudget_child_category_transfers: &[ActualbudgetTransaction], -) -> Result> { - let actualbudget_child_category_transfers = actualbudget_child_category_transfers - .iter() - .filter(|t| { - t.parent_id - .clone() - .is_some_and(|parent_id| parent_id == actualbudget_parent_category_transfer.id) - }) - .map(ActualbudgetTransaction::clone); - - let withdrawals: Vec = actualbudget_child_category_transfers - .clone() - .filter(|t| t.amount < 0) - .collect(); - if withdrawals.len() != 1 { - bail!( - "actualbudget parent category transfer {} had {} withdrawals", - actualbudget_parent_category_transfer.id, - withdrawals.len(), - ); - } - let withdrawal = withdrawals.get(0).unwrap(); - println!("withdrawal: {:?}", withdrawal); - - if withdrawal.category_id.is_none() { - bail!( - "actualbudget child withdrawal {} had no category ID", - withdrawal.id - ); - } - let withdrawal_bucket_id: i32 = transform_id(&withdrawal.category_id.clone().unwrap()); - - let deposits: Vec = actualbudget_child_category_transfers - .clone() - .filter(|t| t.amount > 0) - .collect(); - if deposits.len() != 1 { - bail!( - "actualbudget parent category transfer {} had {} deposits", - actualbudget_parent_category_transfer.id, - deposits.len(), - ); - } - let deposit = deposits.get(0).unwrap(); - println!("deposit: {:?}", deposit); - - if deposit.category_id.is_none() { - bail!( - "actualbudget child deposit {} had no category ID", - deposit.id - ); - } - let deposit_id: i32 = transform_id(&deposit.category_id.clone().unwrap()); - - if withdrawal.amount != -deposit.amount { - bail!("actualbudget category transfer amounts did not sum to zero"); - } - - let quantity: i32 = deposit.amount.try_into().unwrap(); - - Ok(vec![ - BucketTransaction { - id: transform_id(&deposit.id), - description: actualbudget_parent_category_transfer - .notes - .clone() - .unwrap_or_else(String::new), - amount: -quantity, - bucket_id: withdrawal_bucket_id, - }, - BucketTransaction { - id: transform_id(&withdrawal.id), - description: actualbudget_parent_category_transfer - .notes - .clone() - .unwrap_or_else(String::new), - amount: quantity, - bucket_id: deposit_id, - }, - ]) -} - -#[cfg(test)] -mod test { - use actualbudget_fakes::actualbudget_transaction::make_fake_actualbudget_transaction_builder; - use actualbudget_models::{ActualbudgetTransaction, ActualbudgetTransactionBuilder}; - use schist_models::BucketTransaction; - - use crate::transform_state::transform_id::transform_id; - - use super::transform_bucket_transactions; - - #[test] - fn when_one_category_transfer_then_transforms() { - let actualbudget_transactions = [ - make_fake_parent_transaction("0"), - make_fake_child_withdrawal("1", "0"), - make_fake_child_deposit("2", "0"), - ]; - - let result = transform_bucket_transactions(&actualbudget_transactions); - - assert!(result.is_ok()); - let bucket_transactions = result.unwrap(); - assert_eq!(2, bucket_transactions.len()); - - let bucket_withdrawal = bucket_transactions.iter().find(|bt| bt.amount < 0).unwrap(); - assert_eq!(-100, bucket_withdrawal.amount); - assert_eq!("I was a parent transaction", bucket_withdrawal.description); - assert_eq!(transform_id("0"), bucket_withdrawal.bucket_id); - - let bucket_deposit = bucket_transactions.iter().find(|bt| bt.amount > 0).unwrap(); - assert_eq!(100, bucket_deposit.amount); - assert_eq!("I was a parent transaction", bucket_deposit.description); - assert_eq!(transform_id("1"), bucket_deposit.bucket_id); - } - - #[test] - fn when_many_transactions_then_transforms() { - let actualbudget_transactions = [ - make_fake_parent_transaction("0"), - make_fake_child_withdrawal_builder("1", 100, "0") - .category_id(Some(String::from("0"))) - .build() - .unwrap(), - make_fake_child_deposit_builder("2", 100, "0") - .category_id(Some(String::from("1"))) - .build() - .unwrap(), - make_fake_parent_transaction("3"), - make_fake_child_withdrawal_builder("4", 200, "3") - .category_id(Some(String::from("1"))) - .build() - .unwrap(), - make_fake_child_deposit_builder("5", 200, "3") - .category_id(Some(String::from("0"))) - .build() - .unwrap(), - make_fake_actualbudget_transaction_builder("6") - .amount(300) - .build() - .unwrap(), - make_fake_actualbudget_transaction_builder("7") - .amount(-400) - .build() - .unwrap(), - ]; - - let result = transform_bucket_transactions(&actualbudget_transactions); - - assert!(result.is_ok()); - let bucket_transactions = result.unwrap(); - assert_eq!(4, bucket_transactions.len()); - - let bucket_withdrawal_0: Vec<&BucketTransaction> = bucket_transactions - .iter() - .filter(|ct| ct.amount == -100) - .collect(); - assert_eq!(1, bucket_withdrawal_0.len()); - let bucket_withdrawal_0 = bucket_withdrawal_0.get(0).unwrap(); - assert_eq!(transform_id("0"), bucket_withdrawal_0.bucket_id); - - let bucket_deposit_0: Vec<&BucketTransaction> = bucket_transactions - .iter() - .filter(|ct| ct.amount == 100) - .collect(); - assert_eq!(1, bucket_deposit_0.len()); - let bucket_deposit_0 = bucket_deposit_0.get(0).unwrap(); - assert_eq!(transform_id("1"), bucket_deposit_0.bucket_id); - - let bucket_withdrawal_1: Vec<&BucketTransaction> = bucket_transactions - .iter() - .filter(|ct| ct.amount == -200) - .collect(); - assert_eq!(1, bucket_withdrawal_1.len()); - let bucket_withdrawal_1 = bucket_withdrawal_1.get(0).unwrap(); - assert_eq!(transform_id("1"), bucket_withdrawal_1.bucket_id); - - let bucket_deposit_1: Vec<&BucketTransaction> = bucket_transactions - .iter() - .filter(|ct| ct.amount == 200) - .collect(); - assert_eq!(1, bucket_deposit_1.len()); - let bucket_deposit_1 = bucket_deposit_1.get(0).unwrap(); - assert_eq!(transform_id("0"), bucket_deposit_1.bucket_id); - } - - #[test] - fn when_multiple_child_withdrawals_then_err() { - let actualbudget_transactions = [ - make_fake_parent_transaction("0"), - make_fake_child_withdrawal_builder("1", 50, "0") - .build() - .unwrap(), - make_fake_child_withdrawal_builder("2", 50, "0") - .build() - .unwrap(), - make_fake_child_deposit_builder("3", 100, "0") - .build() - .unwrap(), - ]; - - let result = transform_bucket_transactions(&actualbudget_transactions); - - assert!(result.is_err()); - } - - #[test] - fn when_multiple_child_deposits_then_err() { - let actualbudget_transactions = [ - make_fake_parent_transaction("0"), - make_fake_child_withdrawal_builder("1", 100, "0") - .build() - .unwrap(), - make_fake_child_deposit_builder("2", 50, "0") - .build() - .unwrap(), - make_fake_child_deposit_builder("3", 50, "0") - .build() - .unwrap(), - ]; - - let result = transform_bucket_transactions(&actualbudget_transactions); - - assert!(result.is_err()); - } - - #[test] - fn when_no_child_withdrawals_then_return_empty() { - let actualbudget_transactions = [ - make_fake_parent_transaction("0"), - make_fake_child_deposit("1", "0"), - ]; - - let result = transform_bucket_transactions(&actualbudget_transactions); - - assert!(result.is_ok()); - assert_eq!(0, result.unwrap().len()); - } - - #[test] - fn when_no_child_deposits_then_return_empty() { - let actualbudget_transactions = [ - make_fake_parent_transaction("0"), - make_fake_child_withdrawal("1", "0"), - ]; - - let result = transform_bucket_transactions(&actualbudget_transactions); - - assert!(result.is_ok()); - assert_eq!(0, result.unwrap().len()); - } - - #[test] - fn when_child_amounts_dont_add_up_to_zero_then_return_empty() { - let actualbudget_transactions = [ - make_fake_parent_transaction("0"), - make_fake_child_withdrawal_builder("1", 50, "0") - .build() - .unwrap(), - make_fake_child_deposit_builder("2", 100, "0") - .build() - .unwrap(), - ]; - - let result = transform_bucket_transactions(&actualbudget_transactions); - - assert!(result.is_ok()); - assert_eq!(0, result.unwrap().len()); - } - - #[test] - fn when_child_category_transfer_has_no_category_then_err() { - let actualbudget_transactions_0 = [ - make_fake_parent_transaction("0"), - make_fake_child_withdrawal_builder("1", 100, "0") - .category_id(None) - .build() - .unwrap(), - make_fake_child_deposit_builder("2", 100, "0") - .build() - .unwrap(), - ]; - let actualbudget_transactions_1 = [ - make_fake_parent_transaction("0"), - make_fake_child_withdrawal_builder("1", 100, "0") - .build() - .unwrap(), - make_fake_child_deposit_builder("2", 100, "0") - .category_id(None) - .build() - .unwrap(), - ]; - - let result_0 = transform_bucket_transactions(&actualbudget_transactions_0); - let result_1 = transform_bucket_transactions(&actualbudget_transactions_1); - - assert!(result_0.is_err()); - assert!(result_1.is_err()); - } - - fn make_fake_parent_transaction(id: &str) -> ActualbudgetTransaction { - make_fake_parent_transaction_builder(id).build().unwrap() - } - - fn make_fake_parent_transaction_builder(id: &str) -> ActualbudgetTransactionBuilder { - let mut builder = make_fake_actualbudget_transaction_builder(id); - builder - .account_id(String::from("0")) - .notes(Some(String::from("I was a parent transaction"))) - .is_parent(true); - builder - } - - fn make_fake_child_withdrawal(id: &str, parent_id: &str) -> ActualbudgetTransaction { - make_fake_child_withdrawal_builder(id, 100, parent_id) - .build() - .unwrap() - } - - fn make_fake_child_withdrawal_builder( - id: &str, - amount: i32, - parent_id: &str, - ) -> ActualbudgetTransactionBuilder { - let mut builder = make_fake_actualbudget_transaction_builder(id); - builder - .account_id(String::from("0")) - .category_id(Some(String::from("0"))) - .amount(-amount) - .is_child(true) - .notes(Some(String::from("I was a child withdrawal"))) - .parent_id(Some(String::from(parent_id))); - builder - } - - fn make_fake_child_deposit(id: &str, parent_id: &str) -> ActualbudgetTransaction { - make_fake_child_deposit_builder(id, 100, parent_id) - .build() - .unwrap() - } - - fn make_fake_child_deposit_builder( - id: &str, - amount: i32, - parent_id: &str, - ) -> ActualbudgetTransactionBuilder { - let mut builder = make_fake_actualbudget_transaction_builder(id); - builder - .account_id(String::from("0")) - .category_id(Some(String::from("1"))) - .amount(amount) - .is_child(true) - .notes(Some(String::from("I was a child deposit"))) - .parent_id(Some(String::from(parent_id))); - builder - } -} diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_category_transfers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_category_transfers.rs new file mode 100644 index 0000000..952405e --- /dev/null +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_category_transfers.rs @@ -0,0 +1,415 @@ +use actualbudget_models::ActualbudgetTransaction; +use anyhow::{bail, Context, Result}; +use schist_models::BucketTransaction; + +use super::{is_category_transfer, transform_id}; + +pub fn transform_bucket_transactions_from_category_transfers( + actualbudget_transactions: &[ActualbudgetTransaction], +) -> Result> { + let actualbudget_category_transfers = actualbudget_transactions + .iter() + .filter(|transaction| is_category_transfer(transaction, actualbudget_transactions)); + let actualbudget_parent_category_transfers: Vec = + actualbudget_category_transfers + .clone() + .filter(|t| t.is_parent) + .map(ActualbudgetTransaction::clone) + .collect(); + let actualbudget_child_category_transfers: Vec = + actualbudget_category_transfers + .filter(|t| t.is_child) + .map(ActualbudgetTransaction::clone) + .collect(); + + let mut result = Vec::new(); + for pct in actualbudget_parent_category_transfers { + result.extend( + transform_category_transfer(&pct, &actualbudget_child_category_transfers) + .with_context(|| "failed to transform bucket transactions")?, + ) + } + Ok(result) +} + +fn transform_category_transfer( + actualbudget_parent_category_transfer: &ActualbudgetTransaction, + actualbudget_child_category_transfers: &[ActualbudgetTransaction], +) -> Result> { + transform_category_transfer_without_context( + actualbudget_parent_category_transfer, + actualbudget_child_category_transfers, + ) + .with_context(|| "failed to transform category transfer") +} + +fn transform_category_transfer_without_context( + actualbudget_parent_category_transfer: &ActualbudgetTransaction, + actualbudget_child_category_transfers: &[ActualbudgetTransaction], +) -> Result> { + let actualbudget_child_category_transfers = actualbudget_child_category_transfers + .iter() + .filter(|t| { + t.parent_id + .clone() + .is_some_and(|parent_id| parent_id == actualbudget_parent_category_transfer.id) + }) + .map(ActualbudgetTransaction::clone); + + let withdrawals: Vec = actualbudget_child_category_transfers + .clone() + .filter(|t| t.amount < 0) + .collect(); + if withdrawals.len() != 1 { + bail!( + "actualbudget parent category transfer {} had {} withdrawals", + actualbudget_parent_category_transfer.id, + withdrawals.len(), + ); + } + let withdrawal = withdrawals.get(0).unwrap(); + println!("withdrawal: {:?}", withdrawal); + + if withdrawal.category_id.is_none() { + bail!( + "actualbudget child withdrawal {} had no category ID", + withdrawal.id + ); + } + let withdrawal_bucket_id: i32 = transform_id(&withdrawal.category_id.clone().unwrap()); + + let deposits: Vec = actualbudget_child_category_transfers + .clone() + .filter(|t| t.amount > 0) + .collect(); + if deposits.len() != 1 { + bail!( + "actualbudget parent category transfer {} had {} deposits", + actualbudget_parent_category_transfer.id, + deposits.len(), + ); + } + let deposit = deposits.get(0).unwrap(); + println!("deposit: {:?}", deposit); + + if deposit.category_id.is_none() { + bail!( + "actualbudget child deposit {} had no category ID", + deposit.id + ); + } + let deposit_id: i32 = transform_id(&deposit.category_id.clone().unwrap()); + + if withdrawal.amount != -deposit.amount { + bail!("actualbudget category transfer amounts did not sum to zero"); + } + + let quantity: i32 = deposit.amount.try_into().unwrap(); + + Ok(vec![ + BucketTransaction { + id: transform_id(&deposit.id), + description: actualbudget_parent_category_transfer + .notes + .clone() + .unwrap_or_else(String::new), + amount: -quantity, + bucket_id: withdrawal_bucket_id, + }, + BucketTransaction { + id: transform_id(&withdrawal.id), + description: actualbudget_parent_category_transfer + .notes + .clone() + .unwrap_or_else(String::new), + amount: quantity, + bucket_id: deposit_id, + }, + ]) +} + +#[cfg(test)] +mod test { + use actualbudget_fakes::actualbudget_transaction::make_fake_actualbudget_transaction_builder; + use actualbudget_models::{ActualbudgetTransaction, ActualbudgetTransactionBuilder}; + use schist_models::BucketTransaction; + + use crate::transform_state::transform_id::transform_id; + + use super::transform_bucket_transactions_from_category_transfers; + + #[test] + fn when_one_category_transfer_then_transforms() { + let actualbudget_transactions = [ + make_fake_parent_transaction("0"), + make_fake_child_withdrawal("1", "0"), + make_fake_child_deposit("2", "0"), + ]; + + let result = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions); + + assert!(result.is_ok()); + let bucket_transactions = result.unwrap(); + assert_eq!(2, bucket_transactions.len()); + + let bucket_withdrawal = bucket_transactions.iter().find(|bt| bt.amount < 0).unwrap(); + assert_eq!(-100, bucket_withdrawal.amount); + assert_eq!("I was a parent transaction", bucket_withdrawal.description); + assert_eq!(transform_id("0"), bucket_withdrawal.bucket_id); + + let bucket_deposit = bucket_transactions.iter().find(|bt| bt.amount > 0).unwrap(); + assert_eq!(100, bucket_deposit.amount); + assert_eq!("I was a parent transaction", bucket_deposit.description); + assert_eq!(transform_id("1"), bucket_deposit.bucket_id); + } + + #[test] + fn when_many_transactions_then_transforms() { + let actualbudget_transactions = [ + make_fake_parent_transaction("0"), + make_fake_child_withdrawal_builder("1", 100, "0") + .category_id(Some(String::from("0"))) + .build() + .unwrap(), + make_fake_child_deposit_builder("2", 100, "0") + .category_id(Some(String::from("1"))) + .build() + .unwrap(), + make_fake_parent_transaction("3"), + make_fake_child_withdrawal_builder("4", 200, "3") + .category_id(Some(String::from("1"))) + .build() + .unwrap(), + make_fake_child_deposit_builder("5", 200, "3") + .category_id(Some(String::from("0"))) + .build() + .unwrap(), + make_fake_actualbudget_transaction_builder("6") + .amount(300) + .build() + .unwrap(), + make_fake_actualbudget_transaction_builder("7") + .amount(-400) + .build() + .unwrap(), + ]; + + let result = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions); + + assert!(result.is_ok()); + let bucket_transactions = result.unwrap(); + assert_eq!(4, bucket_transactions.len()); + + let bucket_withdrawal_0: Vec<&BucketTransaction> = bucket_transactions + .iter() + .filter(|ct| ct.amount == -100) + .collect(); + assert_eq!(1, bucket_withdrawal_0.len()); + let bucket_withdrawal_0 = bucket_withdrawal_0.get(0).unwrap(); + assert_eq!(transform_id("0"), bucket_withdrawal_0.bucket_id); + + let bucket_deposit_0: Vec<&BucketTransaction> = bucket_transactions + .iter() + .filter(|ct| ct.amount == 100) + .collect(); + assert_eq!(1, bucket_deposit_0.len()); + let bucket_deposit_0 = bucket_deposit_0.get(0).unwrap(); + assert_eq!(transform_id("1"), bucket_deposit_0.bucket_id); + + let bucket_withdrawal_1: Vec<&BucketTransaction> = bucket_transactions + .iter() + .filter(|ct| ct.amount == -200) + .collect(); + assert_eq!(1, bucket_withdrawal_1.len()); + let bucket_withdrawal_1 = bucket_withdrawal_1.get(0).unwrap(); + assert_eq!(transform_id("1"), bucket_withdrawal_1.bucket_id); + + let bucket_deposit_1: Vec<&BucketTransaction> = bucket_transactions + .iter() + .filter(|ct| ct.amount == 200) + .collect(); + assert_eq!(1, bucket_deposit_1.len()); + let bucket_deposit_1 = bucket_deposit_1.get(0).unwrap(); + assert_eq!(transform_id("0"), bucket_deposit_1.bucket_id); + } + + #[test] + fn when_multiple_child_withdrawals_then_err() { + let actualbudget_transactions = [ + make_fake_parent_transaction("0"), + make_fake_child_withdrawal_builder("1", 50, "0") + .build() + .unwrap(), + make_fake_child_withdrawal_builder("2", 50, "0") + .build() + .unwrap(), + make_fake_child_deposit_builder("3", 100, "0") + .build() + .unwrap(), + ]; + + let result = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions); + + assert!(result.is_err()); + } + + #[test] + fn when_multiple_child_deposits_then_err() { + let actualbudget_transactions = [ + make_fake_parent_transaction("0"), + make_fake_child_withdrawal_builder("1", 100, "0") + .build() + .unwrap(), + make_fake_child_deposit_builder("2", 50, "0") + .build() + .unwrap(), + make_fake_child_deposit_builder("3", 50, "0") + .build() + .unwrap(), + ]; + + let result = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions); + + assert!(result.is_err()); + } + + #[test] + fn when_no_child_withdrawals_then_return_empty() { + let actualbudget_transactions = [ + make_fake_parent_transaction("0"), + make_fake_child_deposit("1", "0"), + ]; + + let result = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions); + + assert!(result.is_ok()); + assert_eq!(0, result.unwrap().len()); + } + + #[test] + fn when_no_child_deposits_then_return_empty() { + let actualbudget_transactions = [ + make_fake_parent_transaction("0"), + make_fake_child_withdrawal("1", "0"), + ]; + + let result = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions); + + assert!(result.is_ok()); + assert_eq!(0, result.unwrap().len()); + } + + #[test] + fn when_child_amounts_dont_add_up_to_zero_then_return_empty() { + let actualbudget_transactions = [ + make_fake_parent_transaction("0"), + make_fake_child_withdrawal_builder("1", 50, "0") + .build() + .unwrap(), + make_fake_child_deposit_builder("2", 100, "0") + .build() + .unwrap(), + ]; + + let result = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions); + + assert!(result.is_ok()); + assert_eq!(0, result.unwrap().len()); + } + + #[test] + fn when_child_category_transfer_has_no_category_then_err() { + let actualbudget_transactions_0 = [ + make_fake_parent_transaction("0"), + make_fake_child_withdrawal_builder("1", 100, "0") + .category_id(None) + .build() + .unwrap(), + make_fake_child_deposit_builder("2", 100, "0") + .build() + .unwrap(), + ]; + let actualbudget_transactions_1 = [ + make_fake_parent_transaction("0"), + make_fake_child_withdrawal_builder("1", 100, "0") + .build() + .unwrap(), + make_fake_child_deposit_builder("2", 100, "0") + .category_id(None) + .build() + .unwrap(), + ]; + + let result_0 = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions_0); + let result_1 = + transform_bucket_transactions_from_category_transfers(&actualbudget_transactions_1); + + assert!(result_0.is_err()); + assert!(result_1.is_err()); + } + + fn make_fake_parent_transaction(id: &str) -> ActualbudgetTransaction { + make_fake_parent_transaction_builder(id).build().unwrap() + } + + fn make_fake_parent_transaction_builder(id: &str) -> ActualbudgetTransactionBuilder { + let mut builder = make_fake_actualbudget_transaction_builder(id); + builder + .account_id(String::from("0")) + .notes(Some(String::from("I was a parent transaction"))) + .is_parent(true); + builder + } + + fn make_fake_child_withdrawal(id: &str, parent_id: &str) -> ActualbudgetTransaction { + make_fake_child_withdrawal_builder(id, 100, parent_id) + .build() + .unwrap() + } + + fn make_fake_child_withdrawal_builder( + id: &str, + amount: i32, + parent_id: &str, + ) -> ActualbudgetTransactionBuilder { + let mut builder = make_fake_actualbudget_transaction_builder(id); + builder + .account_id(String::from("0")) + .category_id(Some(String::from("0"))) + .amount(-amount) + .is_child(true) + .notes(Some(String::from("I was a child withdrawal"))) + .parent_id(Some(String::from(parent_id))); + builder + } + + fn make_fake_child_deposit(id: &str, parent_id: &str) -> ActualbudgetTransaction { + make_fake_child_deposit_builder(id, 100, parent_id) + .build() + .unwrap() + } + + fn make_fake_child_deposit_builder( + id: &str, + amount: i32, + parent_id: &str, + ) -> ActualbudgetTransactionBuilder { + let mut builder = make_fake_actualbudget_transaction_builder(id); + builder + .account_id(String::from("0")) + .category_id(Some(String::from("1"))) + .amount(amount) + .is_child(true) + .notes(Some(String::from("I was a child deposit"))) + .parent_id(Some(String::from(parent_id))); + builder + } +} diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_state.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_state.rs index 8e71a47..d3ebee5 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_state.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_state.rs @@ -1,12 +1,13 @@ use anyhow::{Context, Result}; -use schist_models::{Account, Bucket, Pipe, Transaction}; +use schist_models::{Account, Bucket, BucketTransaction, Pipe, Transaction}; use crate::{actualbudget_state::ActualbudgetState, schist_state::SchistState}; use super::{ - transform_account_transfers, transform_accounts, transform_bucket_transactions, + transform_account_transfers, transform_accounts, + transform_bucket_transactions_from_category_transfers, transform_budget_drips, transform_categories_to_buckets, transform_date, transform_transactions, - transform_zero_budgets_to_drips::transform_budget_drips, transform_zero_budgets_to_pipes, + transform_zero_budgets_to_pipes, }; pub fn transform_state(actualbudget_state: ActualbudgetState) -> Result { @@ -36,8 +37,13 @@ fn transform_state_without_context(actualbudget_state: ActualbudgetState) -> Res ); let pipes: Vec = transform_zero_budgets_to_pipes(&actualbudget_state.actualbudget_zero_budgets)?; - let bucket_transactions = - transform_bucket_transactions(&actualbudget_state.actualbudget_transactions)?; + let bucket_transactions: Vec = + vec![transform_bucket_transactions_from_category_transfers( + &remaining_transactions, + )?] + .into_iter() + .flatten() + .collect(); Ok(SchistState { first_transaction_date: first_activity_date, -- cgit v1.2.3