summaryrefslogtreecommitdiff
path: root/backend/actualbudget_importer/src/transformers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'backend/actualbudget_importer/src/transformers.rs')
-rw-r--r--backend/actualbudget_importer/src/transformers.rs218
1 files changed, 218 insertions, 0 deletions
diff --git a/backend/actualbudget_importer/src/transformers.rs b/backend/actualbudget_importer/src/transformers.rs
new file mode 100644
index 0000000..dd4d154
--- /dev/null
+++ b/backend/actualbudget_importer/src/transformers.rs
@@ -0,0 +1,218 @@
+use anyhow::Result;
+use budgeting_app_core::models::{
+ account::Account, budget_update::BudgetUpdate, category::Category, category_transfer::CategoryTransfer, date_utc::DateUtc, datetime_utc::DatetimeUtc, transaction::Transaction, transaction_categorisation::TransactionCategorisation
+};
+use core::panic;
+use std::hash::{DefaultHasher, Hash, Hasher};
+
+use crate::models::{actualbudget_account::ActualbudgetAccount, actualbudget_category::ActualbudgetCategory, actualbudget_date::ActualbudgetDate, actualbudget_transaction::ActualbudgetTransaction, actualbudget_zero_budget::ActualbudgetZeroBudget};
+
+pub fn transform_date(actualbudget_date: &ActualbudgetDate) -> Result<DatetimeUtc> {
+ actualbudget_date.to_iso().parse()
+}
+
+pub fn transform_accounts(
+ actualbudget_accounts: &[ActualbudgetAccount],
+ first_transaction_date: &DatetimeUtc,
+) -> Vec<Account> {
+ let mut accounts = Vec::<Account>::new();
+ for account in actualbudget_accounts {
+ accounts.push(transform_account(account, first_transaction_date));
+ }
+ accounts
+}
+
+fn transform_account(
+ actualbudget_account: &ActualbudgetAccount,
+ opening_date: &DatetimeUtc,
+) -> Account {
+ Account {
+ id: transform_id(&actualbudget_account.id.clone()),
+ name: actualbudget_account.name.clone(),
+ opening_balance: 0,
+ opening_date: opening_date.clone(),
+ }
+}
+
+pub fn transform_categories(
+ actualbudget_categories: &[ActualbudgetCategory],
+) -> Vec<Category> {
+ let mut categories = Vec::<Category>::new();
+ for actualbudget_category in actualbudget_categories {
+ categories.push(Category {
+ id: transform_id(&actualbudget_category.id.clone()),
+ name: actualbudget_category.name.clone(),
+ })
+ }
+ categories
+}
+
+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()
+}
+
+pub fn transform_budget_updates(
+ actualbudget_zero_budgets: &[ActualbudgetZeroBudget],
+) -> Result<Vec<BudgetUpdate>> {
+ let mut budget_updates = Vec::<BudgetUpdate>::new();
+ for zero_budget in actualbudget_zero_budgets {
+ let first_zero_budget_in_category = get_first_zero_budget_in_category(
+ actualbudget_zero_budgets,
+ &zero_budget.category_id.clone(),
+ );
+ if first_zero_budget_in_category.is_some_and(|zb| zb.id == zero_budget.id) {
+ continue;
+ }
+ budget_updates.push(BudgetUpdate {
+ id: transform_id(&zero_budget.id.clone()),
+ category_id: transform_id(&zero_budget.category_id.clone()),
+ date: DateUtc::from(transform_date(&zero_budget.month.clone())?),
+ new_budget: zero_budget.amount.try_into().unwrap(),
+ new_period: 30,
+ })
+ }
+ Ok(budget_updates)
+}
+
+fn get_first_zero_budget_in_category(
+ actualbudget_zero_budgets: &[ActualbudgetZeroBudget],
+ actualbudget_category_id: &str,
+) -> Option<ActualbudgetZeroBudget> {
+ let mut category_zero_budgets = actualbudget_zero_budgets.to_vec();
+ category_zero_budgets.retain(|zb| zb.category_id == actualbudget_category_id);
+ category_zero_budgets
+ .iter()
+ .min_by_key(|zb| zb.month.clone())
+ .cloned()
+}
+
+pub fn transform_transactions(
+ actualbudget_transactions: &[ActualbudgetTransaction],
+ actualbudget_category_transfers_account: &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(|t| t.category_id.clone() != actualbudget_category_transfers_account.id.clone());
+ 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);
+
+ 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,
+ payee: atomic_actualbudget_transaction.payee,
+ quantity: atomic_actualbudget_transaction.amount.try_into().unwrap(),
+ date: transform_date(&atomic_actualbudget_transaction.date)?,
+ account_id: transform_id(&atomic_actualbudget_transaction.account_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(&atomic_actualbudget_transaction.category_id),
+ });
+ }
+
+ 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 transaction_id = transform_id(&parent_actualbudget_transaction.id);
+
+ transactions.push(Transaction {
+ id: transaction_id,
+ description: parent_actualbudget_transaction.notes,
+ payee: parent_actualbudget_transaction.payee,
+ 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 {
+ transaction_categorisations.push(TransactionCategorisation {
+ id: transform_id(&child_actualbudget_transaction.id),
+ description: child_actualbudget_transaction.notes,
+ quantity: child_actualbudget_transaction.amount.try_into().unwrap(),
+ transaction_id,
+ category_id: transform_id(&child_actualbudget_transaction.category_id),
+ });
+ }
+ }
+
+ Ok((transactions, transaction_categorisations))
+}
+
+pub fn transform_category_transfers(
+ actualbudget_transactions: &[ActualbudgetTransaction],
+ actualbudget_category_transfers_account: &ActualbudgetAccount,
+) -> Vec<CategoryTransfer> {
+ let mut category_transfers = Vec::<CategoryTransfer>::new();
+
+ let mut actualbudget_category_transfers = actualbudget_transactions.to_vec();
+ actualbudget_category_transfers
+ .retain(|t| t.category_id.clone() == actualbudget_category_transfers_account.id.clone());
+ let mut actualbudget_parent_category_transfers = actualbudget_category_transfers.to_vec();
+ actualbudget_parent_category_transfers.retain(|t| t.is_parent);
+ let mut actualbudget_child_category_transfers = actualbudget_category_transfers.to_vec();
+ actualbudget_child_category_transfers.retain(|t| t.is_child);
+
+ for actualbudget_parent_category_transfer in actualbudget_parent_category_transfers {
+ let mut actualbudget_child_category_transfers =
+ actualbudget_child_category_transfers.to_vec();
+ actualbudget_child_category_transfers.retain(|t| {
+ t.parent_id
+ .clone()
+ .is_some_and(|parent_id| parent_id == actualbudget_parent_category_transfer.id)
+ });
+
+ if actualbudget_child_category_transfers.len() != 2 {
+ panic!("Category transfers should always have exactly two children.");
+ }
+
+ let category_1 = actualbudget_child_category_transfers[0].clone();
+ let category_2 = actualbudget_child_category_transfers[1].clone();
+
+ // To avoid duplicates
+ if category_1.amount >= 0 {
+ continue;
+ }
+
+ if actualbudget_child_category_transfers[0].amount
+ != -actualbudget_child_category_transfers[1].amount
+ {
+ panic!("Category transfer children should sum to zero.");
+ }
+
+ category_transfers.push(CategoryTransfer {
+ id: transform_id(&actualbudget_parent_category_transfer.id),
+ description: actualbudget_parent_category_transfer.notes,
+ quantity: actualbudget_child_category_transfers[0]
+ .amount
+ .try_into()
+ .unwrap(),
+ from_category_id: transform_id(&category_1.id),
+ to_category_id: transform_id(&category_2.id),
+ });
+ }
+
+ category_transfers
+}