summaryrefslogtreecommitdiff
path: root/actualbudget_to_schist_transformer/src/transform_state
diff options
context:
space:
mode:
authorJoe Carstairs <jcarstairs@scottlogic.com>2025-07-28 13:22:53 +0100
committerJoe Carstairs <jcarstairs@scottlogic.com>2025-07-28 13:30:27 +0100
commit2361adeb0ca77904138e3466d51c2f44cf6df613 (patch)
treef6e7192908f99fab62061913d8a1e9d2b918414b /actualbudget_to_schist_transformer/src/transform_state
parentc7ed1e61ce2218be0b9e60577454124c4e43a366 (diff)
Tidier folders
Diffstat (limited to 'actualbudget_to_schist_transformer/src/transform_state')
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/is_account_transfer.rs13
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/is_category_transfer.rs18
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/mod.rs25
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_account_transfers.rs306
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs30
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_budget_drips.rs306
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_categories.rs204
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_category_transfers.rs346
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_date.rs31
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_id.rs26
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_state.rs64
-rw-r--r--actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs253
12 files changed, 0 insertions, 1622 deletions
diff --git a/actualbudget_to_schist_transformer/src/transform_state/is_account_transfer.rs b/actualbudget_to_schist_transformer/src/transform_state/is_account_transfer.rs
deleted file mode 100644
index c9540ed..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/is_account_transfer.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use actualbudget_models::ActualbudgetTransaction;
-
-pub fn is_account_transfer(
- transaction: &ActualbudgetTransaction,
-) -> bool {
- transaction.transfer_id.is_some()
-}
-
-pub fn is_not_account_transfer(
- transaction: &ActualbudgetTransaction,
-) -> bool {
- transaction.transfer_id.is_none()
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/is_category_transfer.rs b/actualbudget_to_schist_transformer/src/transform_state/is_category_transfer.rs
deleted file mode 100644
index c82387c..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/is_category_transfer.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use actualbudget_models::{ActualbudgetAccount, ActualbudgetTransaction};
-
-pub fn is_category_transfer(
- transaction: &ActualbudgetTransaction,
- actualbudget_category_transfers_account: &Option<ActualbudgetAccount>
-) -> bool {
- match actualbudget_category_transfers_account {
- None => false,
- Some(acc) => transaction.account_id.clone() == acc.id.clone(),
- }
-}
-
-pub fn is_not_category_transfer(
- transaction: &ActualbudgetTransaction,
- actualbudget_category_transfers_account: &Option<ActualbudgetAccount>
-) -> bool {
- !is_category_transfer(transaction, actualbudget_category_transfers_account)
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/mod.rs b/actualbudget_to_schist_transformer/src/transform_state/mod.rs
deleted file mode 100644
index 8787699..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/mod.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-mod is_account_transfer;
-mod is_category_transfer;
-mod transform_account_transfers;
-mod transform_accounts;
-mod transform_budget_drips;
-mod transform_categories;
-mod transform_category_transfers;
-mod transform_date;
-mod transform_id;
-mod transform_state;
-mod transform_transactions;
-
-use is_account_transfer::is_account_transfer;
-use is_account_transfer::is_not_account_transfer;
-use is_category_transfer::is_category_transfer;
-use is_category_transfer::is_not_category_transfer;
-use transform_accounts::transform_accounts;
-use transform_account_transfers::transform_account_transfers;
-use transform_categories::transform_categories;
-use transform_category_transfers::transform_category_transfers;
-use transform_date::transform_date;
-use transform_id::transform_id;
-use transform_transactions::transform_transactions;
-
-pub use transform_state::transform_state;
diff --git a/actualbudget_to_schist_transformer/src/transform_state/transform_account_transfers.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_account_transfers.rs
deleted file mode 100644
index a0c426d..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_account_transfers.rs
+++ /dev/null
@@ -1,306 +0,0 @@
-use actualbudget_models::ActualbudgetTransaction;
-use anyhow::{Context, Result};
-use itertools::Itertools;
-use schist_models::account_transfer::AccountTransfer;
-
-use super::{is_account_transfer, is_not_account_transfer, transform_date::transform_date, transform_id::transform_id};
-
-pub fn transform_account_transfers(
- actualbudget_transactions: &[ActualbudgetTransaction],
-) -> Result<(Vec<ActualbudgetTransaction>, Vec<AccountTransfer>)> {
- transform_account_transfers_without_context(actualbudget_transactions)
- .with_context(|| "failed to transform account transfers")
-}
-
-pub fn transform_account_transfers_without_context(
- actualbudget_transactions: &[ActualbudgetTransaction],
-) -> Result<(Vec<ActualbudgetTransaction>, Vec<AccountTransfer>)> {
- let mut account_transfer_transaction_pairs: Vec<(ActualbudgetTransaction, ActualbudgetTransaction)> = Vec::new();
- let mut transactions_not_transformed: Vec<ActualbudgetTransaction> = Vec::new();
- let account_transfer_transactions = actualbudget_transactions
- .iter()
- .cloned()
- .filter(is_account_transfer);
-
- for actualbudget_transaction in actualbudget_transactions {
- if is_not_account_transfer(actualbudget_transaction) {
- transactions_not_transformed.push(actualbudget_transaction.clone());
- continue;
- }
-
- let is_already_transformed = |t: &ActualbudgetTransaction|
- account_transfer_transaction_pairs
- .iter().any(|(t1, t2)| *t1 == *t || *t2 == *t);
-
- if is_already_transformed(actualbudget_transaction) {
- continue;
- }
-
- let companion = account_transfer_transactions
- .clone()
- .filter(|t| !is_already_transformed(t)
- && t.date == actualbudget_transaction.date
- && t.amount == -actualbudget_transaction.amount
- ).last();
-
- if companion.is_none() {
- transactions_not_transformed.push(actualbudget_transaction.clone());
- continue;
- }
- let companion = companion.unwrap();
-
- let (from, to) = if actualbudget_transaction.amount >= 0 {
- (companion.clone(), actualbudget_transaction.clone())
- } else {
- (actualbudget_transaction.clone(), companion.clone())
- };
-
- account_transfer_transaction_pairs.push((from, to));
- }
-
- Ok((
- transactions_not_transformed,
- account_transfer_transaction_pairs
- .iter()
- .map(transform_account_transfer)
- .try_collect()?,
- ))
-}
-
-fn transform_account_transfer(
- (actualbudget_transaction_from, actualbudget_transaction_to):
- &(ActualbudgetTransaction, ActualbudgetTransaction),
-) -> Result<AccountTransfer> {
- let context = || format!(
- "failed to transform account transfer from transactions ({:?}, {:?})",
- actualbudget_transaction_from,
- actualbudget_transaction_to,
- );
-
- Ok(AccountTransfer {
- id: transform_id(&actualbudget_transaction_from.id),
- date: transform_date(&actualbudget_transaction_from.date)
- .with_context(context)?,
- description: actualbudget_transaction_from.notes.clone().unwrap_or_else(||
- actualbudget_transaction_to.notes.clone().unwrap_or_else(String::new)
- ),
- quantity: actualbudget_transaction_to.amount,
- from_account_id: transform_id(actualbudget_transaction_from.account_id.as_str()),
- to_account_id: transform_id(actualbudget_transaction_to.account_id.as_str()),
- })
-}
-
-#[cfg(test)]
-mod test {
- use actualbudget_fakes::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_account_transfers;
-
- #[test]
- fn when_one_account_transfer_then_transforms() {
- let transactions = [
- make_fake_actualbudget_transaction_builder("0")
- .amount(-100)
- .account_id(String::from("0"))
- .payee(Some(String::from("0")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .notes(Some(String::from("My notes")))
- .build()
- .unwrap(),
- make_fake_actualbudget_transaction_builder("1")
- .amount(100)
- .account_id(String::from("1"))
- .payee(Some(String::from("1")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .notes(Some(String::from("My notes")))
- .build()
- .unwrap(),
- ];
-
- let (remaining_transactions, account_transfers) =
- transform_account_transfers(&transactions).unwrap();
-
- assert_eq!(0, remaining_transactions.len());
- assert_eq!(1, account_transfers.len());
- let account_transfer = account_transfers.get(0).unwrap();
- assert_eq!("2024-08-01".parse::<DateUtc>().unwrap(), account_transfer.date);
- assert_eq!("My notes", account_transfer.description);
- assert_eq!(100, account_transfer.quantity);
- assert_eq!(transform_id("0"), account_transfer.from_account_id);
- assert_eq!(transform_id("1"), account_transfer.to_account_id);
- }
-
- #[test]
- fn when_one_account_transfer_in_reverse_order_then_transforms() {
- let transactions = [
- make_fake_actualbudget_transaction_builder("0")
- .amount(100)
- .account_id(String::from("1"))
- .payee(Some(String::from("1")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .notes(Some(String::from("My notes")))
- .build()
- .unwrap(),
- make_fake_actualbudget_transaction_builder("1")
- .amount(-100)
- .account_id(String::from("0"))
- .payee(Some(String::from("0")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .notes(Some(String::from("My notes")))
- .build()
- .unwrap(),
- ];
-
- let (remaining_transactions, account_transfers) =
- transform_account_transfers(&transactions).unwrap();
-
- assert_eq!(0, remaining_transactions.len());
- assert_eq!(1, account_transfers.len());
- let account_transfer = account_transfers.get(0).unwrap();
- assert_eq!("2024-08-01".parse::<DateUtc>().unwrap(), account_transfer.date);
- assert_eq!("My notes", account_transfer.description);
- assert_eq!(100, account_transfer.quantity);
- assert_eq!(transform_id("0"), account_transfer.from_account_id);
- assert_eq!(transform_id("1"), account_transfer.to_account_id);
- }
-
- #[test]
- fn when_two_account_transfers_different_amounts_then_transforms() {
- let transactions = [
- make_fake_actualbudget_transaction_builder("0")
- .amount(-100)
- .account_id(String::from("0"))
- .payee(Some(String::from("0")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .notes(Some(String::from("My notes 1")))
- .build()
- .unwrap(),
- make_fake_actualbudget_transaction_builder("1")
- .amount(-200)
- .account_id(String::from("0"))
- .payee(Some(String::from("0")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .notes(Some(String::from("My notes 2")))
- .build()
- .unwrap(),
- make_fake_actualbudget_transaction_builder("2")
- .amount(100)
- .account_id(String::from("1"))
- .payee(Some(String::from("1")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .build()
- .unwrap(),
- make_fake_actualbudget_transaction_builder("2")
- .amount(200)
- .account_id(String::from("1"))
- .payee(Some(String::from("1")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .build()
- .unwrap(),
- ];
-
- let (remaining_transactions, account_transfers) =
- transform_account_transfers(&transactions).unwrap();
-
- assert_eq!(0, remaining_transactions.len());
- assert_eq!(2, account_transfers.len());
-
- let account_transfer_0 = account_transfers
- .iter()
- .filter(|at| at.quantity == 100)
- .last()
- .unwrap();
- assert_eq!("2024-08-01".parse::<DateUtc>().unwrap(), account_transfer_0.date);
- assert_eq!(transform_id("0"), account_transfer_0.from_account_id);
- assert_eq!(transform_id("1"), account_transfer_0.to_account_id);
- assert_eq!("My notes 1", account_transfer_0.description);
-
- let account_transfer_1 = account_transfers
- .iter()
- .filter(|at| at.quantity == 200)
- .last()
- .unwrap();
- assert_eq!("2024-08-01".parse::<DateUtc>().unwrap(), account_transfer_1.date);
- assert_eq!(transform_id("0"), account_transfer_1.from_account_id);
- assert_eq!(transform_id("1"), account_transfer_1.to_account_id);
- assert_eq!("My notes 2", account_transfer_1.description);
- }
-
- #[test]
- fn when_two_account_transfers_different_dates_then_transforms() {
- let transactions = [
- make_fake_actualbudget_transaction_builder("0")
- .amount(-100)
- .account_id(String::from("0"))
- .payee(Some(String::from("0")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .notes(Some(String::from("My notes 1")))
- .build()
- .unwrap(),
- make_fake_actualbudget_transaction_builder("1")
- .amount(-100)
- .account_id(String::from("0"))
- .payee(Some(String::from("0")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240802))
- .notes(Some(String::from("My notes 2")))
- .build()
- .unwrap(),
- make_fake_actualbudget_transaction_builder("2")
- .amount(100)
- .account_id(String::from("1"))
- .payee(Some(String::from("1")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240801))
- .build()
- .unwrap(),
- make_fake_actualbudget_transaction_builder("3")
- .amount(100)
- .account_id(String::from("1"))
- .payee(Some(String::from("1")))
- .transfer_id(Some(String::from("0")))
- .date(ActualbudgetDate::from_i32(20240802))
- .build()
- .unwrap(),
- ];
-
- let (remaining_transactions, account_transfers) =
- transform_account_transfers(&transactions).unwrap();
-
- assert_eq!(0, remaining_transactions.len());
- assert_eq!(2, account_transfers.len());
-
- let account_transfer_0 = account_transfers
- .iter()
- .filter(|at| at.date == "2024-08-01".parse().unwrap())
- .last()
- .unwrap();
- assert_eq!(100, account_transfer_0.quantity);
- assert_eq!(transform_id("0"), account_transfer_0.from_account_id);
- assert_eq!(transform_id("1"), account_transfer_0.to_account_id);
- assert_eq!("My notes 1", account_transfer_0.description);
-
- let account_transfer_1 = account_transfers
- .iter()
- .filter(|at| at.date == "2024-08-02".parse().unwrap())
- .last()
- .unwrap();
- assert_eq!(100, account_transfer_1.quantity);
- assert_eq!(transform_id("0"), account_transfer_1.from_account_id);
- assert_eq!(transform_id("1"), account_transfer_1.to_account_id);
- assert_eq!("My notes 2", account_transfer_1.description);
- }
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs
deleted file mode 100644
index 28d8162..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use actualbudget_models::ActualbudgetAccount;
-use schist_models::{account::Account, date_utc::DateUtc};
-
-use super::transform_id;
-
-pub fn transform_accounts(
- actualbudget_accounts: &[ActualbudgetAccount],
- actualbudget_category_transfers_account: &Option<ActualbudgetAccount>,
- first_activity_date: &DateUtc,
-) -> Vec<Account> {
- actualbudget_accounts
- .iter()
- .filter(|a| actualbudget_category_transfers_account
- .clone()
- .is_some_and(|cta| cta.id != a.id)
- ).map(|a| transform_account(a, first_activity_date))
- .collect()
-}
-
-fn transform_account(
- actualbudget_account: &ActualbudgetAccount,
- opening_date: &DateUtc,
-) -> Account {
- Account {
- id: transform_id(&actualbudget_account.id.clone()),
- name: actualbudget_account.name.clone(),
- opening_balance: 0,
- opening_date: opening_date.clone(),
- }
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/transform_budget_drips.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_budget_drips.rs
deleted file mode 100644
index 1163e56..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_budget_drips.rs
+++ /dev/null
@@ -1,306 +0,0 @@
-use std::hash::{DefaultHasher, Hash, Hasher};
-
-use actualbudget_models::ActualbudgetZeroBudget;
-use anyhow::{Context, Result};
-use itertools::Itertools;
-use schist_models::{budget_drip::BudgetDrip, date_utc::DateUtc};
-use schist_traits::dateable::Dateable;
-
-use super::{transform_date::transform_date, transform_id::transform_id};
-
-pub fn transform_budget_drips(actualbudget_zero_budgets: &[ActualbudgetZeroBudget]) -> Result<Vec<BudgetDrip>> {
- let non_zero_actualbudget_zero_budgets: Vec<ActualbudgetZeroBudget> = actualbudget_zero_budgets
- .iter()
- .filter(|zb| zb.amount != 0)
- .cloned()
- .collect();
-
- let budget_drips = group_actualbudget_zero_budgets_by_category_id(
- &non_zero_actualbudget_zero_budgets,
- ).iter()
- .map(sort_actualbudget_zero_budgets_by_month)
- .flat_map(|sorted_actualbudget_zero_budgets| sorted_actualbudget_zero_budgets
- .iter()
- .map(map_actualbudget_zero_budget_to_budget_drips)
- .collect::<Result<Vec<Vec<BudgetDrip>>>>()
- ).flatten()
- .flatten()
- .filter(|bd| bd.quantity != 0)
- .collect();
-
- Ok(budget_drips)
-}
-
-fn map_actualbudget_zero_budget_to_budget_drips(
- actualbudget_zero_budget: &ActualbudgetZeroBudget,
-) -> Result<Vec<BudgetDrip>> {
- let context = || "failed to map actualbudget zero budgets to budget drips";
-
- let first_day_of_month: DateUtc = Into::<DateUtc>::into(transform_date(&actualbudget_zero_budget.month).with_context(context)?);
- let month = first_day_of_month.month();
- let days_in_month: Vec<DateUtc> = (0..31)
- .map(|i| first_day_of_month.add_days(i))
- .take_while(|d| match d {
- Err(_) => true,
- Ok(d) => d.month() == month,
- })
- .filter(Result::is_ok)
- .map(Result::unwrap)
- .collect();
-
- // unwrap() can panic, but days_in_month is guaranteed to have between 0 and
- // 31 elements, so we should be grand
- let num_days_in_month: i32 = days_in_month.len().try_into().unwrap();
-
- let quantity = actualbudget_zero_budget.amount / num_days_in_month;
- let remainder_quantity = actualbudget_zero_budget.amount - (quantity * num_days_in_month);
-
- let budget_drips: Vec<BudgetDrip> = days_in_month.iter().map(|&date|
- BudgetDrip {
- id: transform_budget_drip_id(&actualbudget_zero_budget, &date),
- category_id: transform_id(&actualbudget_zero_budget.category_id),
- date,
- quantity: if date == first_day_of_month {
- quantity + remainder_quantity
- } else {
- quantity
- }
- }
- ).collect();
-
- Ok(budget_drips)
-}
-
-fn sort_actualbudget_zero_budgets_by_month(
- actualbudget_zero_budgets_by_category_id: &Vec<ActualbudgetZeroBudget>,
-) -> Vec<ActualbudgetZeroBudget> {
- let mut actualbudget_zero_budgets = actualbudget_zero_budgets_by_category_id.clone();
- actualbudget_zero_budgets.sort_by_key(|zb| zb.month.clone());
- actualbudget_zero_budgets
-}
-
-fn group_actualbudget_zero_budgets_by_category_id(
- actualbudget_zero_budgets: &[ActualbudgetZeroBudget],
-) -> Vec<Vec<ActualbudgetZeroBudget>> {
- let category_ids = actualbudget_zero_budgets
- .iter()
- .map(|zb| zb.category_id.clone())
- .unique();
-
- category_ids
- .map(|category_id| actualbudget_zero_budgets
- .iter()
- .filter(|zb| zb.category_id == category_id)
- .map(ActualbudgetZeroBudget::clone)
- .collect()
- ).collect()
-}
-
-fn transform_budget_drip_id(
- actualbudget_zero_budget: &ActualbudgetZeroBudget,
- date: &DateUtc,
-) -> i32 {
- let mut hasher = DefaultHasher::new();
- let unique_str = actualbudget_zero_budget.id.clone() + date.to_string().as_str();
- unique_str.hash(&mut hasher);
- (hasher.finish() % std::convert::TryInto::<u64>::try_into(i32::MAX).unwrap())
- .try_into()
- .unwrap()
-}
-
-#[cfg(test)]
-mod test {
- use actualbudget_fakes::actualbudget_zero_budget::make_fake_actualbudget_zero_budget_builder;
- use actualbudget_models::ActualbudgetDate;
- use schist_models::budget_drip::BudgetDrip;
- use schist_traits::dateable::Dateable;
-
- use crate::transform_state::transform_id::transform_id;
-
- use super::transform_budget_drips;
-
- mod map_actualbudget_zero_budget_to_budget_drips {
- use actualbudget_fakes::actualbudget_zero_budget::make_fake_actualbudget_zero_budget_builder;
- use actualbudget_models::ActualbudgetDate;
- use schist_models::budget_drip::BudgetDrip;
- use schist_traits::dateable::Dateable;
-
- use crate::transform_state::transform_budget_drips::map_actualbudget_zero_budget_to_budget_drips;
-
- #[test]
- fn when_zero_budget_with_integer_budget_per_day_then_evenly_divided_budget_drips() {
- let actualbudget_zero_budget = make_fake_actualbudget_zero_budget_builder("0")
- .amount(31 * 100)
- .category_id(String::from("0"))
- .month(ActualbudgetDate::from_i32(202401))
- .build()
- .unwrap();
-
- let result = map_actualbudget_zero_budget_to_budget_drips(&actualbudget_zero_budget);
-
- assert!(result.is_ok());
- let mut budget_drips = result.unwrap();
- assert_eq!(31, budget_drips.len());
- assert!(budget_drips.iter().all(|bd| bd.quantity == 100));
- budget_drips.sort_by_key(|bd| bd.date);
- for i in 1..31 {
- assert_eq!(Ok(i), budget_drips.get(i - 1).unwrap().date.day().try_into());
- }
- }
-
- #[test]
- fn when_zero_budget_with_non_integer_budget_per_day_then_remainder_on_one_day() {
- let actualbudget_zero_budget = make_fake_actualbudget_zero_budget_builder("0")
- .amount(31 * 100 + 30)
- .category_id(String::from("0"))
- .month(ActualbudgetDate::from_i32(202401))
- .build()
- .unwrap();
-
- let result = map_actualbudget_zero_budget_to_budget_drips(&actualbudget_zero_budget);
-
- assert!(result.is_ok());
- let budget_drips = result.unwrap();
- assert_eq!(31, budget_drips.len());
-
- let budget_drips_quantity_not_floored: Vec<&BudgetDrip> = budget_drips
- .iter()
- .filter(|bd| bd.quantity != 100)
- .collect();
- assert_eq!(1, budget_drips_quantity_not_floored.len());
-
- let budget_drips_floored: Vec<&BudgetDrip> = budget_drips
- .iter()
- .filter(|bd| bd.quantity == 100)
- .collect();
- assert_eq!(30, budget_drips_floored.len());
- }
- }
-
- #[test]
- fn when_no_zero_budgets_then_no_budget_drips() {
- let actualbudget_zero_budgets = [];
- let result = transform_budget_drips(&actualbudget_zero_budgets);
- assert!(result.is_ok());
- assert_eq!(result.unwrap().len(), 0);
- }
-
- #[test]
- fn when_one_zero_budget_with_integer_budget_per_day_then_evenly_divided_budget_drips() {
- let actualbudget_zero_budgets = [
- make_fake_actualbudget_zero_budget_builder("0")
- .amount(31 * 100)
- .category_id(String::from("0"))
- .month(ActualbudgetDate::from_i32(202401))
- .build()
- .unwrap()
- ];
-
- let result = transform_budget_drips(&actualbudget_zero_budgets);
-
- assert!(result.is_ok());
- let mut budget_drips = result.unwrap();
- assert_eq!(31, budget_drips.len());
- assert!(budget_drips.iter().all(|bd| bd.quantity == 100));
- budget_drips.sort_by_key(|bd| bd.date);
- for i in 1..31 {
- assert_eq!(Ok(i), budget_drips.get(i - 1).unwrap().date.day().try_into());
- }
- }
-
- #[test]
- fn when_one_zero_budgets_with_non_integer_budget_per_day_then_remainder_on_one_day() {
- let actualbudget_zero_budgets = [
- make_fake_actualbudget_zero_budget_builder("0")
- .amount(31 * 100 + 30)
- .category_id(String::from("0"))
- .month(ActualbudgetDate::from_i32(202401))
- .build()
- .unwrap()
- ];
-
- let result = transform_budget_drips(&actualbudget_zero_budgets);
-
- assert!(result.is_ok());
- let budget_drips = result.unwrap();
- assert_eq!(31, budget_drips.len());
-
- let budget_drips_quantity_not_floored: Vec<&BudgetDrip> = budget_drips
- .iter()
- .filter(|bd| bd.quantity != 100)
- .collect();
- assert_eq!(1, budget_drips_quantity_not_floored.len());
-
- let budget_drips_floored: Vec<&BudgetDrip> = budget_drips
- .iter()
- .filter(|bd| bd.quantity == 100)
- .collect();
- assert_eq!(30, budget_drips_floored.len());
- }
-
- #[test]
- fn when_multiple_zero_budgets_with_integer_budget_per_day_across_multiple_categories_then_evenly_divided_budget_drips() {
- let actualbudget_zero_budgets = [
- make_fake_actualbudget_zero_budget_builder("0")
- .amount(31 * 100)
- .category_id(String::from("0"))
- .month(ActualbudgetDate::from_i32(202401))
- .build()
- .unwrap(),
- make_fake_actualbudget_zero_budget_builder("1")
- .amount(31 * 200)
- .category_id(String::from("1"))
- .month(ActualbudgetDate::from_i32(202401))
- .build()
- .unwrap(),
- make_fake_actualbudget_zero_budget_builder("2")
- .amount(29 * 110)
- .category_id(String::from("0"))
- .month(ActualbudgetDate::from_i32(202402))
- .build()
- .unwrap(),
- make_fake_actualbudget_zero_budget_builder("3")
- .amount(29 * 210)
- .category_id(String::from("1"))
- .month(ActualbudgetDate::from_i32(202402))
- .build()
- .unwrap(),
- ];
-
- let result = transform_budget_drips(&actualbudget_zero_budgets);
- let category_0_id = transform_id("0");
- let category_1_id = transform_id("1");
-
- assert!(result.is_ok());
- let budget_drips = result.unwrap();
- assert_eq!(31 + 31 + 29 + 29, budget_drips.len());
-
- let budget_drips_jan_category_0: Vec<&BudgetDrip> = budget_drips
- .iter()
- .filter(|bd| bd.category_id == category_0_id && bd.date.month() == 1)
- .collect();
- assert_eq!(31, budget_drips_jan_category_0.len());
- assert!(budget_drips_jan_category_0.iter().all(|bd| bd.quantity == 100));
-
- let budget_drips_jan_category_1: Vec<&BudgetDrip> = budget_drips
- .iter()
- .filter(|bd| bd.category_id == category_1_id && bd.date.month() == 1)
- .collect();
- assert_eq!(31, budget_drips_jan_category_1.len());
- assert!(budget_drips_jan_category_1.iter().all(|bd| bd.quantity == 200));
-
- let budget_drips_feb_category_0: Vec<&BudgetDrip> = budget_drips
- .iter()
- .filter(|bd| bd.category_id == category_0_id && bd.date.month() == 2)
- .collect();
- assert_eq!(29, budget_drips_feb_category_0.len());
- assert!(budget_drips_feb_category_0.iter().all(|bd| bd.quantity == 110));
-
- let budget_drips_feb_category_1: Vec<&BudgetDrip> = budget_drips
- .iter()
- .filter(|bd| bd.category_id == category_1_id && bd.date.month() == 2)
- .collect();
- assert_eq!(29, budget_drips_feb_category_1.len());
- assert!(budget_drips_feb_category_1.iter().all(|bd| bd.quantity == 210));
- }
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/transform_categories.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_categories.rs
deleted file mode 100644
index 17ce194..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_categories.rs
+++ /dev/null
@@ -1,204 +0,0 @@
-use actualbudget_models::{ActualbudgetCategory, ActualbudgetZeroBudget};
-use itertools::Itertools;
-use schist_models::{budget_period_unit::BudgetPeriodUnit, category::Category, date_utc::DateUtc};
-
-use super::transform_id;
-
-pub fn transform_categories(
- actualbudget_categories: &[ActualbudgetCategory],
- actualbudget_zero_budgets: &[ActualbudgetZeroBudget],
- before_first_activity: &DateUtc,
-) -> Vec<Category> {
- let last_non_zero_month = actualbudget_zero_budgets
- .iter()
- .map(|zb| zb.month.clone())
- .unique()
- .filter(|month| actualbudget_zero_budgets
- .iter()
- .any(|zb| zb.month == *month && zb.amount != 0)
- ).max();
-
- let actualbudget_zero_budgets = if let Some(last_non_zero_month) = last_non_zero_month {
- &actualbudget_zero_budgets
- .iter()
- .filter(|zb| zb.month == last_non_zero_month)
- .cloned()
- .collect::<Vec<_>>()
- } else {
- actualbudget_zero_budgets
- };
-
- actualbudget_categories.iter().map(|actualbudget_category| Category {
- id: transform_id(&actualbudget_category.id.clone()),
- name: actualbudget_category.name.clone(),
- balance: 0,
- balance_date: before_first_activity.clone(),
- budget_period: 1,
- budget_period_unit: BudgetPeriodUnit::Month,
- budget_quantity: get_budget_quantity(actualbudget_category, actualbudget_zero_budgets),
- }).collect()
-}
-
-fn get_budget_quantity(
- actualbudget_category: &ActualbudgetCategory,
- actualbudget_zero_budgets: &[ActualbudgetZeroBudget]
-) -> i32 {
- let latest_zero_budget_for_category = actualbudget_zero_budgets
- .iter()
- .filter(|zb| zb.category_id == actualbudget_category.id)
- .max_by_key(|zb| zb.month.clone());
-
- match latest_zero_budget_for_category {
- Some(latest_zero_budget_for_category) => latest_zero_budget_for_category.amount,
- None => 0,
- }
-}
-
-#[cfg(test)]
-mod test {
- use actualbudget_fakes::{actualbudget_category::make_fake_actualbudget_category_builder, actualbudget_zero_budget::make_fake_actualbudget_zero_budget_builder};
- use actualbudget_models::ActualbudgetDate;
- use schist_models::{budget_period_unit::BudgetPeriodUnit, date_utc::DateUtc};
- use schist_traits::nowlike::Nowlike;
-
- use crate::transform_state::transform_id::transform_id;
-
- use super::transform_categories;
-
- #[test]
- fn when_one_category_one_zero_budget_then_transforms_category() {
- let actualbudget_categories = [
- make_fake_actualbudget_category_builder("0").name(String::from("Food")).build().unwrap(),
- ];
- let actualbudget_zero_budgets = [
- make_fake_actualbudget_zero_budget_builder("0").amount(100).build().unwrap(),
- ];
- let before_first_transaction = DateUtc::now();
-
- let categories = transform_categories(
- &actualbudget_categories,
- &actualbudget_zero_budgets,
- &before_first_transaction,
- );
-
- assert_eq!(1, categories.len());
- let category = categories.get(0).unwrap();
- assert_eq!("Food", category.name);
- assert_eq!(0, category.balance);
- assert_eq!(before_first_transaction, category.balance_date);
- assert_eq!(1, category.budget_period);
- assert_eq!(BudgetPeriodUnit::Month, category.budget_period_unit);
- assert_eq!(100, category.budget_quantity);
- }
-
- #[test]
- fn when_one_category_no_zero_budget_then_transforms_category() {
- let actualbudget_categories = [
- make_fake_actualbudget_category_builder("0").name(String::from("Food")).build().unwrap(),
- ];
- let actualbudget_zero_budgets = [];
- let before_first_transaction = DateUtc::now();
-
- let categories = transform_categories(
- &actualbudget_categories,
- &actualbudget_zero_budgets,
- &before_first_transaction,
- );
-
- assert_eq!(1, categories.len());
- let category = categories.get(0).unwrap();
- assert_eq!("Food", category.name);
- assert_eq!(0, category.balance);
- assert_eq!(before_first_transaction, category.balance_date);
- assert_eq!(1, category.budget_period);
- assert_eq!(BudgetPeriodUnit::Month, category.budget_period_unit);
- assert_eq!(0, category.budget_quantity);
- }
-
- #[test]
- fn when_one_category_multiple_zero_budgets_then_transforms_category() {
- let actualbudget_categories = [
- make_fake_actualbudget_category_builder("0").name(String::from("Food")).build().unwrap(),
- ];
- let jan = ActualbudgetDate::from_i32(202401);
- let feb = ActualbudgetDate::from_i32(202402);
- let mar = ActualbudgetDate::from_i32(202403);
- let actualbudget_zero_budgets = [
- make_fake_actualbudget_zero_budget_builder("0").amount(100).month(jan).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("1").amount(200).month(feb).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("2").amount(300).month(mar).build().unwrap(),
- ];
- let before_first_transaction = DateUtc::now();
-
- let categories = transform_categories(
- &actualbudget_categories,
- &actualbudget_zero_budgets,
- &before_first_transaction,
- );
-
- assert_eq!(1, categories.len());
- let category = categories.get(0).unwrap();
- assert_eq!("Food", category.name);
- assert_eq!(0, category.balance);
- assert_eq!(before_first_transaction, category.balance_date);
- assert_eq!(1, category.budget_period);
- assert_eq!(BudgetPeriodUnit::Month, category.budget_period_unit);
- assert_eq!(300, category.budget_quantity);
- }
-
- #[test]
- fn when_multiple_categories_multiple_zero_budgets_then_transforms_category() {
- let actualbudget_categories = [
- make_fake_actualbudget_category_builder("0").name(String::from("Food")).build().unwrap(),
- make_fake_actualbudget_category_builder("1").name(String::from("Clothes")).build().unwrap(),
- make_fake_actualbudget_category_builder("2").name(String::from("Rent")).build().unwrap(),
- ];
- let jan = ActualbudgetDate::from_i32(202401);
- let feb = ActualbudgetDate::from_i32(202402);
- let mar = ActualbudgetDate::from_i32(202403);
- let actualbudget_zero_budgets = [
- make_fake_actualbudget_zero_budget_builder("0").amount(100).month(jan.clone()).category_id(String::from("0")).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("1").amount(200).month(feb.clone()).category_id(String::from("0")).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("2").amount(300).month(mar.clone()).category_id(String::from("0")).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("3").amount(110).month(jan.clone()).category_id(String::from("1")).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("4").amount(210).month(feb.clone()).category_id(String::from("1")).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("5").amount(310).month(mar.clone()).category_id(String::from("1")).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("6").amount(120).month(jan.clone()).category_id(String::from("2")).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("7").amount(220).month(feb.clone()).category_id(String::from("2")).build().unwrap(),
- make_fake_actualbudget_zero_budget_builder("8").amount(320).month(mar.clone()).category_id(String::from("2")).build().unwrap(),
- ];
- let before_first_transaction = DateUtc::now();
-
- let categories = transform_categories(
- &actualbudget_categories,
- &actualbudget_zero_budgets,
- &before_first_transaction,
- );
-
- assert_eq!(3, categories.len());
-
- let category_0 = categories.iter().filter(|c| c.id == transform_id("0")).last().unwrap();
- assert_eq!("Food", category_0.name);
- assert_eq!(0, category_0.balance);
- assert_eq!(before_first_transaction, category_0.balance_date);
- assert_eq!(1, category_0.budget_period);
- assert_eq!(BudgetPeriodUnit::Month, category_0.budget_period_unit);
- assert_eq!(300, category_0.budget_quantity);
-
- let category_1 = categories.iter().filter(|c| c.id == transform_id("1")).last().unwrap();
- assert_eq!("Clothes", category_1.name);
- assert_eq!(0, category_1.balance);
- assert_eq!(before_first_transaction, category_1.balance_date);
- assert_eq!(1, category_1.budget_period);
- assert_eq!(BudgetPeriodUnit::Month, category_1.budget_period_unit);
- assert_eq!(310, category_1.budget_quantity);
-
- let category_2 = categories.iter().filter(|c| c.id == transform_id("2")).last().unwrap();
- assert_eq!("Rent", category_2.name);
- assert_eq!(0, category_2.balance);
- assert_eq!(before_first_transaction, category_2.balance_date);
- assert_eq!(1, category_2.budget_period);
- assert_eq!(BudgetPeriodUnit::Month, category_2.budget_period_unit);
- assert_eq!(320, category_2.budget_quantity);
- }
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/transform_category_transfers.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_category_transfers.rs
deleted file mode 100644
index 318e88c..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_category_transfers.rs
+++ /dev/null
@@ -1,346 +0,0 @@
-use actualbudget_models::{
- ActualbudgetAccount,
- ActualbudgetTransaction,
-};
-use anyhow::{bail, Context, Result};
-use itertools::Itertools;
-use schist_models::category_transfer::CategoryTransfer;
-
-use super::{is_category_transfer, transform_id};
-
-pub fn transform_category_transfers(
- actualbudget_transactions: &[ActualbudgetTransaction],
- actualbudget_category_transfers_account: &Option<ActualbudgetAccount>,
-) -> Result<Vec<CategoryTransfer>> {
- let actualbudget_category_transfers = actualbudget_transactions
- .iter()
- .filter(|transaction|
- is_category_transfer(transaction, actualbudget_category_transfers_account)
- );
- let actualbudget_parent_category_transfers: Vec<ActualbudgetTransaction> = actualbudget_category_transfers
- .clone()
- .filter(|t| t.is_parent)
- .map(ActualbudgetTransaction::clone)
- .collect();
- let actualbudget_child_category_transfers: Vec<ActualbudgetTransaction> = actualbudget_category_transfers
- .filter(|t| t.is_child)
- .map(ActualbudgetTransaction::clone)
- .collect();
-
- Ok(actualbudget_parent_category_transfers
- .iter()
- .map(|pct| transform_category_transfer(pct, &actualbudget_child_category_transfers))
- .filter_ok(Option::is_some)
- .map_ok(Option::unwrap)
- .collect::<Result<Vec<CategoryTransfer>>>()
- .with_context(|| "failed to transform category transfers")?
- )
-}
-
-fn transform_category_transfer(
- actualbudget_parent_category_transfer: &ActualbudgetTransaction,
- actualbudget_child_category_transfers: &[ActualbudgetTransaction],
-) -> Result<Option<CategoryTransfer>> {
- 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<Option<CategoryTransfer>> {
- 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 from_transfers: Vec<ActualbudgetTransaction> = actualbudget_child_category_transfers
- .clone()
- .filter(|t| t.amount < 0)
- .collect();
- if from_transfers.len() != 1 {
- bail!(
- "actualbudget parent category transfer {} had {} from child transfers",
- actualbudget_parent_category_transfer.id,
- from_transfers.len(),
- );
- }
- let from_transfer = from_transfers.get(0).unwrap();
-
- if from_transfer.category_id.is_none() {
- bail!("actualbudget child from transfer {} had no category ID", from_transfer.id);
- }
- let from_category_id: i32 = transform_id(&from_transfer.category_id.clone().unwrap());
-
- let to_transfers: Vec<ActualbudgetTransaction> = actualbudget_child_category_transfers
- .clone()
- .filter(|t| t.amount > 0)
- .collect();
- if to_transfers.len() != 1 {
- bail!(
- "actualbudget parent category transfer {} had {} to child transfers",
- actualbudget_parent_category_transfer.id,
- to_transfers.len(),
- );
- }
- let to_transfer = to_transfers.get(0).unwrap();
-
- if to_transfer.category_id.is_none() {
- bail!("actualbudget child to transfer {} had no category ID", to_transfer.id);
- }
- let to_category_id: i32 = transform_id(&to_transfer.category_id.clone().unwrap());
-
- if from_transfer.amount != -to_transfer.amount
- {
- bail!("actualbudget category transfer amounts did not sum to zero");
- }
-
- Ok(Some(CategoryTransfer {
- id: transform_id(&actualbudget_parent_category_transfer.id),
- description: actualbudget_parent_category_transfer
- .notes
- .clone()
- .unwrap_or_else(String::new),
- quantity: to_transfer.amount.try_into().unwrap(),
- from_category_id,
- to_category_id,
- }))
-}
-
-#[cfg(test)]
-mod test {
- use actualbudget_fakes::{actualbudget_account::make_fake_actualbudget_account, actualbudget_transaction::make_fake_actualbudget_transaction_builder};
- use actualbudget_models::{ActualbudgetTransaction, ActualbudgetTransactionBuilder};
- use schist_models::category_transfer::CategoryTransfer;
-
- use crate::transform_state::transform_id::transform_id;
-
- use super::transform_category_transfers;
-
- #[test]
- fn when_one_category_transfer_then_transforms() {
- let actualbudget_category_transfers_account = make_fake_actualbudget_account("0");
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction("1", "0"),
- make_fake_to_child_transaction("2", "0"),
- ];
-
- let result = transform_category_transfers(
- &actualbudget_transactions,
- &Some(actualbudget_category_transfers_account),
- );
-
- assert!(result.is_ok());
- let category_transfers = result.unwrap();
- assert_eq!(1, category_transfers.len());
- let category_transfer = category_transfers.get(0).unwrap();
- assert_eq!(100, category_transfer.quantity);
- assert_eq!("I was a parent transaction", category_transfer.description);
- assert_eq!(transform_id("0"), category_transfer.from_category_id);
- assert_eq!(transform_id("1"), category_transfer.to_category_id);
- }
-
- #[test]
- fn when_many_transactions_then_transforms() {
- let actualbudget_category_transfers_account = make_fake_actualbudget_account("0");
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction_builder("1", 100, "0").category_id(Some(String::from("0"))).build().unwrap(),
- make_fake_to_child_transaction_builder("2", 100, "0").category_id(Some(String::from("1"))).build().unwrap(),
- make_fake_parent_transaction("3"),
- make_fake_from_child_transaction_builder("4", 200, "3").category_id(Some(String::from("1"))).build().unwrap(),
- make_fake_to_child_transaction_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_category_transfers(
- &actualbudget_transactions,
- &Some(actualbudget_category_transfers_account),
- );
-
- assert!(result.is_ok());
- let category_transfers = result.unwrap();
- assert_eq!(2, category_transfers.len());
-
- let category_transfer_0: Vec<&CategoryTransfer> = category_transfers
- .iter()
- .filter(|ct| ct.quantity == 100)
- .collect();
- assert_eq!(1, category_transfer_0.len());
- let category_transfer_0 = category_transfer_0.get(0).unwrap();
- assert_eq!(transform_id("0"), category_transfer_0.from_category_id);
- assert_eq!(transform_id("1"), category_transfer_0.to_category_id);
-
- let category_transfer_1: Vec<&CategoryTransfer> = category_transfers
- .iter()
- .filter(|ct| ct.quantity == 200)
- .collect();
- assert_eq!(1, category_transfer_1.len());
- let category_transfer_1 = category_transfer_1.get(0).unwrap();
- assert_eq!(transform_id("1"), category_transfer_1.from_category_id);
- assert_eq!(transform_id("0"), category_transfer_1.to_category_id);
- }
-
- #[test]
- fn when_multiple_child_from_transactions_then_err() {
- let actualbudget_category_transfers_account = make_fake_actualbudget_account("0");
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction_builder("1", 50, "0").build().unwrap(),
- make_fake_from_child_transaction_builder("2", 50, "0").build().unwrap(),
- make_fake_to_child_transaction_builder("3", 100, "0").build().unwrap(),
- ];
-
- let result = transform_category_transfers(
- &actualbudget_transactions,
- &Some(actualbudget_category_transfers_account),
- );
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_multiple_child_to_transactions_then_err() {
- let actualbudget_category_transfers_account = make_fake_actualbudget_account("0");
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction_builder("1", 100, "0").build().unwrap(),
- make_fake_to_child_transaction_builder("2", 50, "0").build().unwrap(),
- make_fake_to_child_transaction_builder("3", 50, "0").build().unwrap(),
- ];
-
- let result = transform_category_transfers(
- &actualbudget_transactions,
- &Some(actualbudget_category_transfers_account),
- );
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_no_child_from_transactions_then_err() {
- let actualbudget_category_transfers_account = make_fake_actualbudget_account("0");
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_to_child_transaction("1", "0"),
- ];
-
- let result = transform_category_transfers(
- &actualbudget_transactions,
- &Some(actualbudget_category_transfers_account),
- );
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_no_child_to_transactions_then_err() {
- let actualbudget_category_transfers_account = make_fake_actualbudget_account("0");
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction("1", "0"),
- ];
-
- let result = transform_category_transfers(
- &actualbudget_transactions,
- &Some(actualbudget_category_transfers_account),
- );
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_child_amounts_dont_add_up_to_zero_then_err() {
- let actualbudget_category_transfers_account = make_fake_actualbudget_account("0");
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction_builder("1", 50, "0").build().unwrap(),
- make_fake_to_child_transaction_builder("2", 100, "0").build().unwrap(),
- ];
-
- let result = transform_category_transfers(
- &actualbudget_transactions,
- &Some(actualbudget_category_transfers_account),
- );
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_child_category_transfer_has_no_category_then_err() {
- let actualbudget_category_transfers_account = make_fake_actualbudget_account("0");
- let actualbudget_transactions_0 = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction_builder("1", 100, "0").category_id(None).build().unwrap(),
- make_fake_to_child_transaction_builder("2", 100, "0").build().unwrap(),
- ];
- let actualbudget_transactions_1 = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction_builder("1", 100, "0").build().unwrap(),
- make_fake_to_child_transaction_builder("2", 100, "0").category_id(None).build().unwrap(),
- ];
-
- let result_0 = transform_category_transfers(
- &actualbudget_transactions_0,
- &Some(actualbudget_category_transfers_account.clone()),
- );
- let result_1 = transform_category_transfers(
- &actualbudget_transactions_1,
- &Some(actualbudget_category_transfers_account),
- );
-
- 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_from_child_transaction(id: &str, parent_id: &str) -> ActualbudgetTransaction {
- make_fake_from_child_transaction_builder(id, 100, parent_id).build().unwrap()
- }
- fn make_fake_from_child_transaction_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 from child transaction")))
- .parent_id(Some(String::from(parent_id)));
- builder
- }
-
- fn make_fake_to_child_transaction(id: &str, parent_id: &str) -> ActualbudgetTransaction {
- make_fake_to_child_transaction_builder(id, 100, parent_id).build().unwrap()
- }
-
- fn make_fake_to_child_transaction_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 to child transaction")))
- .parent_id(Some(String::from(parent_id)));
- builder
- }
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/transform_date.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_date.rs
deleted file mode 100644
index 57adae0..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_date.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use actualbudget_models::ActualbudgetDate;
-use anyhow::Result;
-use schist_models::date_utc::DateUtc;
-
-pub fn transform_date(actualbudget_date: &ActualbudgetDate) -> Result<DateUtc> {
- actualbudget_date.to_iso_date().parse()
-}
-
-#[cfg(test)]
-mod test {
- use actualbudget_models::ActualbudgetDate;
- use schist_models::date_utc::DateUtc;
-
- use super::transform_date;
-
- #[test]
- fn when_utc_epoch_as_day_then_utc_epoch() {
- let actualbudget_date = ActualbudgetDate::from_i32(19700101);
- 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/actualbudget_to_schist_transformer/src/transform_state/transform_id.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_id.rs
deleted file mode 100644
index 2d2f3af..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_id.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use std::hash::{DefaultHasher, Hash, Hasher};
-
-pub fn transform_id(actualbudget_id: &str) -> i32 {
- let mut hasher = DefaultHasher::new();
- actualbudget_id.hash(&mut hasher);
- (hasher.finish() % std::convert::TryInto::<u64>::try_into(i32::MAX).unwrap())
- .try_into()
- .unwrap()
-}
-
-#[cfg(test)]
-mod test {
- use crate::test_resources::RANDOM_UUIDS;
-
- use super::transform_id;
-
- #[test]
- fn when_transform_many_random_uuids_then_no_repeats() {
- let mut results_so_far: Vec<i32> = Vec::new();
- for &uuid in RANDOM_UUIDS {
- let transformed_uuid = transform_id(uuid);
- assert!(!results_so_far.contains(&transformed_uuid));
- results_so_far.push(transformed_uuid);
- }
- }
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/transform_state.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_state.rs
deleted file mode 100644
index 7c64e35..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_state.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use anyhow::{bail, Context, Result};
-use schist_models::{account::Account, category::Category, category_transfer::CategoryTransfer, date_utc::DateUtc, transaction::Transaction, transaction_categorisation::TransactionCategorisation};
-
-use crate::{actualbudget_state::ActualbudgetState, schist_state::SchistState};
-
-use super::{transform_account_transfers, transform_accounts, transform_budget_drips::transform_budget_drips, transform_categories, transform_category_transfers, transform_date, transform_transactions};
-
-pub fn transform_state(actualbudget_state: ActualbudgetState) -> Result<SchistState> {
- transform_state_without_context(actualbudget_state)
- .with_context(|| "failed to transform actualbudget state to schist state")
-}
-
-fn transform_state_without_context(actualbudget_state: ActualbudgetState) -> Result<SchistState> {
- let (remaining_transactions, account_transfers) = transform_account_transfers(
- &actualbudget_state.actualbudget_transactions,
- )?;
- let first_activity_date =
- transform_date(&actualbudget_state.actualbudget_first_activity_date)?;
- let before_first_activity_date = find_date_before(&first_activity_date)?;
- let accounts: Vec<Account> =
- transform_accounts(
- &actualbudget_state.actualbudget_accounts,
- &actualbudget_state.actualbudget_category_transfers_account,
- &first_activity_date,
- );
- let budget_drips = transform_budget_drips(&actualbudget_state.actualbudget_zero_budgets)?;
- let categories: Vec<Category> = transform_categories(
- &actualbudget_state.actualbudget_categories,
- &actualbudget_state.actualbudget_zero_budgets,
- &before_first_activity_date,
- );
- let (transactions, transaction_categorisations): (Vec<Transaction>, Vec<TransactionCategorisation>) =
- transform_transactions(
- &remaining_transactions,
- &actualbudget_state.actualbudget_category_transfers_account,
- &actualbudget_state.actualbudget_payees,
- )?;
- let category_transfers: Vec<CategoryTransfer> = transform_category_transfers(
- &remaining_transactions,
- &actualbudget_state.actualbudget_category_transfers_account,
- )?;
-
- Ok(SchistState {
- first_transaction_date: first_activity_date,
- accounts,
- account_transfers,
- categories,
- budget_drips,
- transactions,
- transaction_categorisations,
- category_transfers,
- })
-}
-
-fn find_date_before(date: &DateUtc) -> Result<DateUtc> {
- for days_before in 1..31 {
- let result = date.sub_days(days_before);
- if result.is_ok() {
- return result;
- }
- }
-
- bail!("failed to find a date before {}", date)
-}
diff --git a/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs b/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs
deleted file mode 100644
index f7fb098..0000000
--- a/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs
+++ /dev/null
@@ -1,253 +0,0 @@
-use anyhow::{Context, Result};
-use actualbudget_models::{ActualbudgetAccount, ActualbudgetPayee, ActualbudgetTransaction};
-use schist_models::{transaction::Transaction, transaction_categorisation::TransactionCategorisation};
-
-use super::{is_not_category_transfer, transform_date, transform_id};
-
-pub fn transform_transactions(
- actualbudget_transactions: &[ActualbudgetTransaction],
- actualbudget_category_transfers_account: &Option<ActualbudgetAccount>,
- actualbudget_payees: &[ActualbudgetPayee],
-) -> Result<(Vec<Transaction>, Vec<TransactionCategorisation>)> {
- transform_transactions_without_context(
- actualbudget_transactions,
- actualbudget_category_transfers_account,
- actualbudget_payees,
- )
- .with_context(|| "failed to transform actualbudget transactions to schist transactions")
-}
-
-fn transform_transactions_without_context(
- actualbudget_transactions: &[ActualbudgetTransaction],
- actualbudget_category_transfers_account: &Option<ActualbudgetAccount>,
- actualbudget_payees: &[ActualbudgetPayee],
-) -> Result<(Vec<Transaction>, Vec<TransactionCategorisation>)> {
- let mut transactions = Vec::<Transaction>::new();
- let mut transaction_categorisations = Vec::<TransactionCategorisation>::new();
-
- 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);
-
- let payee_name = atomic_actualbudget_transaction
- .payee
- .clone()
- .map(|p| actualbudget_payees
- .iter()
- .find(|pp| pp.id == p)
- .map(|pp| pp.name.clone())
- ).unwrap_or(None)
- .unwrap_or_else(String::new);
-
- transactions.push(Transaction {
- id: transaction_id,
- description: atomic_actualbudget_transaction.notes.clone().unwrap_or_else(String::new),
- payee: payee_name,
- quantity: atomic_actualbudget_transaction.amount.try_into().unwrap(),
- date: transform_date(&atomic_actualbudget_transaction.date)?,
- account_id: transform_id(&atomic_actualbudget_transaction.account_id),
- });
-
- let actualbudget_category_id = atomic_actualbudget_transaction
- .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,
- description: String::new(),
- quantity: atomic_actualbudget_transaction.amount.try_into().unwrap(),
- transaction_id,
- category_id: transform_id(&actualbudget_category_id),
- });
- }
-
- for parent_actualbudget_transaction in parent_actualbudget_transactions {
- 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.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),
- });
-
- for child_actualbudget_transaction in child_actualbudget_transactions {
- let actualbudget_category_id = child_actualbudget_transaction
- .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.clone().unwrap_or_else(String::new),
- quantity: child_actualbudget_transaction.amount.try_into().unwrap(),
- transaction_id,
- category_id: transform_id(&actualbudget_category_id),
- });
- }
- }
-
- Ok((transactions, transaction_categorisations))
-}
-
-#[cfg(test)]
-mod test {
- use actualbudget_fakes::{actualbudget_account::make_fake_actualbudget_account, actualbudget_payee::{make_fake_actualbudget_payee, make_fake_actualbudget_payee_builder}, 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 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 actualbudget_payees = [make_fake_actualbudget_payee("0")];
-
- let (transactions, transaction_categorisations) = transform_transactions(
- &actualbudget_transactions,
- &Some(actualbudget_category_transfers_account),
- &actualbudget_payees,
- ).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("0")))
- .notes(Some(String::from("Savings")))
- .date(ActualbudgetDate::from_i32(19700101))
- .build()
- .unwrap(),
- ];
- let actualbudget_payees = [
- make_fake_actualbudget_payee_builder("0")
- .name(String::from("Gringotts"))
- .build()
- .unwrap(),
- ];
-
- let (transactions, transaction_categorisations) = transform_transactions(
- &actualbudget_transactions,
- &None,
- &actualbudget_payees,
- ).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);
- }
-}