summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoeac <me@joeac.net>2026-01-17 17:17:50 +0000
committerjoeac <me@joeac.net>2026-01-17 17:17:50 +0000
commitbf74f305894bde98033932235a4210c7d6865adf (patch)
tree051422c97fcb5d7c2eafd426ea66e1a906948747
parentfe85652e4d3e8906824baddaafd05d19105579b8 (diff)
parentf69ec8416c4e6301dcfa06791b025785d7f61b6b (diff)
Merge pull request 'random-stuff' (#4) from random-stuff into main
Reviewed-on: https://git.joeac.net/joeac/schist/pulls/4
-rw-r--r--requirements/tasks/task-073.md2
-rw-r--r--schist_core/schist_fakes/src/bucket_transaction.rs16
-rw-r--r--schist_core/schist_fakes/src/bucket_transfer.rs16
-rw-r--r--schist_core/schist_fakes/src/lib.rs2
-rw-r--r--schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql2
-rw-r--r--schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql12
-rw-r--r--schist_core/schist_models/src/bucket.rs11
-rw-r--r--schist_core/schist_models/src/bucket_transaction.rs (renamed from schist_core/schist_models/src/bucket_transfer.rs)10
-rw-r--r--schist_core/schist_models/src/date_utc.rs4
-rw-r--r--schist_core/schist_models/src/lib.rs4
-rw-r--r--schist_core/schist_models/src/schema.rs9
-rw-r--r--schist_core/schist_models/user_data.sqlitebin45056 -> 0 bytes
-rw-r--r--schist_core/schist_queries/src/bucket_transactions.rs65
-rw-r--r--schist_core/schist_queries/src/bucket_transfers.rs82
-rw-r--r--schist_core/schist_queries/src/clear.rs6
-rw-r--r--schist_core/schist_queries/src/lib.rs2
-rw-r--r--schist_core/schist_queries/src/utils/sum_drips_for_bucket.rs2
-rw-r--r--schist_core/schist_queries/tests/bucket_transactions.rs160
-rw-r--r--schist_core/schist_queries/tests/buckets.rs (renamed from schist_core/schist_queries/tests/categories.rs)0
-rw-r--r--schist_core/schist_queries/tests/category_transfers.rs201
-rw-r--r--schist_core/schist_queries/tests/pipes.rs2
-rw-r--r--schist_desktop_gui/src/gui/components/balances_view.rs26
-rw-r--r--schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs60
-rw-r--r--schist_desktop_gui/src/gui/components/balances_view/view_balances_view.rs47
-rw-r--r--schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs25
-rw-r--r--schist_desktop_gui/src/gui/components/button.rs7
-rw-r--r--schist_desktop_gui/src/gui/components/navigation.rs86
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/new_navigation.rs72
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/update_navigation.rs225
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/view_navigation.rs37
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs32
-rw-r--r--schist_desktop_gui/src/gui/components/text.rs68
-rw-r--r--schist_desktop_gui/src/gui/screens/main_screen/view.rs6
-rw-r--r--schist_desktop_gui/src/paths.rs2
-rw-r--r--transformers/actualbudget_transformer/actualbudget_models/src/actualbudget_date.rs51
-rw-r--r--transformers/actualbudget_transformer/actualbudget_queries/src/actualbudget_accounts.rs21
-rw-r--r--transformers/actualbudget_transformer/actualbudget_queries/tests/actualbudget_accounts.rs15
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/actualbudget_state.rs16
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/schist_state.rs8
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state.rs13
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/is_category_transfer.rs (renamed from transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/is_bucket_transfer.rs)14
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs4
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_budgets_not_carried_over.rs350
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_category_transfers.rs452
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_state.rs31
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs4
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions_to_bucket_transfers.rs383
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_zero_budgets_to_drips.rs12
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_transaction_matchers.rs66
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_transfer_matchers.rs39
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/drip_matchers.rs6
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/mod.rs4
-rw-r--r--transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/run.rs22
53 files changed, 1618 insertions, 1194 deletions
diff --git a/requirements/tasks/task-073.md b/requirements/tasks/task-073.md
index cfb4c2f..74e39f4 100644
--- a/requirements/tasks/task-073.md
+++ b/requirements/tasks/task-073.md
@@ -9,7 +9,7 @@
- All the columns must be properly spaced out
- For this ticket, the main content can be empty or contain a placeholder
-Status: doing
+Status: done
Epic: epic-000
diff --git a/schist_core/schist_fakes/src/bucket_transaction.rs b/schist_core/schist_fakes/src/bucket_transaction.rs
new file mode 100644
index 0000000..45aa39c
--- /dev/null
+++ b/schist_core/schist_fakes/src/bucket_transaction.rs
@@ -0,0 +1,16 @@
+use schist_models::{BucketTransaction, BucketTransactionBuilder, DateUtc};
+
+pub fn make_fake_bucket_transaction(id: i32) -> BucketTransaction {
+ make_fake_bucket_transaction_builder(id).build().unwrap()
+}
+
+pub fn make_fake_bucket_transaction_builder(id: i32) -> BucketTransactionBuilder {
+ let mut builder = BucketTransactionBuilder::default();
+ builder
+ .id(id)
+ .description(String::from("Fake bucket transaction"))
+ .amount(0)
+ .date(DateUtc::from_ymd(1970, 1, 1).unwrap())
+ .bucket_id(1);
+ builder
+}
diff --git a/schist_core/schist_fakes/src/bucket_transfer.rs b/schist_core/schist_fakes/src/bucket_transfer.rs
deleted file mode 100644
index 1342ca2..0000000
--- a/schist_core/schist_fakes/src/bucket_transfer.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use schist_models::{BucketTransfer, BucketTransferBuilder};
-
-pub fn make_fake_bucket_transfer(id: i32) -> BucketTransfer {
- make_fake_bucket_transfer_builder(id).build().unwrap()
-}
-
-pub fn make_fake_bucket_transfer_builder(id: i32) -> BucketTransferBuilder {
- let mut builder = BucketTransferBuilder::default();
- builder
- .id(id)
- .description(String::from("Fake bucket transfer"))
- .amount(0)
- .from_bucket_id(0)
- .to_bucket_id(1);
- builder
-}
diff --git a/schist_core/schist_fakes/src/lib.rs b/schist_core/schist_fakes/src/lib.rs
index 0e4cbb4..7ac98fa 100644
--- a/schist_core/schist_fakes/src/lib.rs
+++ b/schist_core/schist_fakes/src/lib.rs
@@ -1,7 +1,7 @@
pub mod account;
pub mod account_transfer;
pub mod bucket;
-pub mod bucket_transfer;
+pub mod bucket_transaction;
pub mod drip;
pub mod pipe;
pub mod transaction;
diff --git a/schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql b/schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql
index 3b2c73e..f6ead07 100644
--- a/schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql
+++ b/schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql
@@ -1,7 +1,7 @@
DROP TABLE accounts;
DROP TABLE account_transfers;
DROP TABLE buckets;
-DROP TABLE bucket_transfers;
+DROP TABLE bucket_transactions;
DROP TABLE drips;
DROP TABLE pipes;
DROP TABLE transactions;
diff --git a/schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql b/schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql
index b37c626..da85b33 100644
--- a/schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql
+++ b/schist_core/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql
@@ -32,17 +32,13 @@ CREATE TABLE buckets(
name TEXT NOT NULL
);
-CREATE TABLE bucket_transfers(
+CREATE TABLE bucket_transactions(
id INTEGER NOT NULL PRIMARY KEY,
amount INTEGER NOT NULL,
+ bucket_id INTEGER NOT NULL,
+ date TEXT NOT NULL,
description TEXT NOT NULL,
- from_bucket_id INTEGER NOT NULL,
- to_bucket_id INTEGER NOT NULL,
- FOREIGN KEY (from_bucket_id)
- REFERENCES buckets (id)
- ON UPDATE CASCADE
- ON DELETE RESTRICT,
- FOREIGN KEY (to_bucket_id)
+ FOREIGN KEY (bucket_id)
REFERENCES buckets (id)
ON UPDATE CASCADE
ON DELETE RESTRICT
diff --git a/schist_core/schist_models/src/bucket.rs b/schist_core/schist_models/src/bucket.rs
index a28aa8f..debbab7 100644
--- a/schist_core/schist_models/src/bucket.rs
+++ b/schist_core/schist_models/src/bucket.rs
@@ -1,3 +1,5 @@
+use std::ops::Div;
+
use derive_builder::Builder;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
@@ -33,4 +35,13 @@ impl Bucket {
.iter()
.all(|t| t.bucket_id.is_none_or(|id| id != self.id))
}
+
+ pub fn pretty_balance_per_100(&self) -> String {
+ let abs_balance_per_100 = self.balance.unwrap_or(0).div(100).abs();
+ if self.balance.is_none_or(|balance| balance >= 0) {
+ abs_balance_per_100.to_string()
+ } else {
+ format!("({abs_balance_per_100})")
+ }
+ }
}
diff --git a/schist_core/schist_models/src/bucket_transfer.rs b/schist_core/schist_models/src/bucket_transaction.rs
index 7c3a9d2..a956d0b 100644
--- a/schist_core/schist_models/src/bucket_transfer.rs
+++ b/schist_core/schist_models/src/bucket_transaction.rs
@@ -1,12 +1,14 @@
use derive_builder::Builder;
use diesel::prelude::*;
+use crate::date_utc::DateUtc;
+
#[derive(Builder, Queryable, Identifiable, Selectable, Debug, PartialEq, Insertable)]
-#[diesel(table_name = crate::schema::bucket_transfers)]
-pub struct BucketTransfer {
+#[diesel(table_name = crate::schema::bucket_transactions)]
+pub struct BucketTransaction {
pub id: i32,
pub amount: i32,
+ pub bucket_id: i32,
+ pub date: DateUtc,
pub description: String,
- pub from_bucket_id: i32,
- pub to_bucket_id: i32,
}
diff --git a/schist_core/schist_models/src/date_utc.rs b/schist_core/schist_models/src/date_utc.rs
index 18a894b..927644c 100644
--- a/schist_core/schist_models/src/date_utc.rs
+++ b/schist_core/schist_models/src/date_utc.rs
@@ -105,7 +105,7 @@ impl DateUtc {
return true;
}
- fn first_day_in_next_month(&self) -> Self {
+ pub fn first_day_in_next_month(&self) -> Self {
let month = self.month();
let mut curr_guess: Option<DateTime<Utc>> = None;
let mut curr_add = 1;
@@ -120,7 +120,7 @@ impl DateUtc {
};
}
- fn first_day_in_month(&self) -> Self {
+ pub fn first_day_in_month(&self) -> Self {
let month = self.month();
let mut prev_guess = self.clone();
let mut curr_guess = self.clone();
diff --git a/schist_core/schist_models/src/lib.rs b/schist_core/schist_models/src/lib.rs
index 3aa4733..2a119cb 100644
--- a/schist_core/schist_models/src/lib.rs
+++ b/schist_core/schist_models/src/lib.rs
@@ -1,7 +1,7 @@
mod account;
mod account_transfer;
mod bucket;
-mod bucket_transfer;
+mod bucket_transaction;
mod budget_period_unit;
mod date_utc;
mod datetime_utc;
@@ -15,7 +15,7 @@ pub mod schema;
pub use account::Account;
pub use account_transfer::{AccountTransfer, AccountTransferBuilder};
pub use bucket::{Bucket, BucketBuilder};
-pub use bucket_transfer::{BucketTransfer, BucketTransferBuilder};
+pub use bucket_transaction::{BucketTransaction, BucketTransactionBuilder};
pub use budget_period_unit::BudgetPeriodUnit;
pub use date_utc::DateUtc;
pub use datetime_utc::DatetimeUtc;
diff --git a/schist_core/schist_models/src/schema.rs b/schist_core/schist_models/src/schema.rs
index d16e636..9579e5c 100644
--- a/schist_core/schist_models/src/schema.rs
+++ b/schist_core/schist_models/src/schema.rs
@@ -21,12 +21,12 @@ diesel::table! {
}
diesel::table! {
- bucket_transfers (id) {
+ bucket_transactions (id) {
id -> Integer,
amount -> Integer,
+ bucket_id -> Integer,
+ date -> Text,
description -> Text,
- from_bucket_id -> Integer,
- to_bucket_id -> Integer,
}
}
@@ -74,6 +74,7 @@ diesel::table! {
}
}
+diesel::joinable!(bucket_transactions -> buckets (bucket_id));
diesel::joinable!(drips -> buckets (bucket_id));
diesel::joinable!(pipes -> buckets (bucket_id));
diesel::joinable!(transactions -> accounts (account_id));
@@ -81,7 +82,7 @@ diesel::joinable!(transactions -> accounts (account_id));
diesel::allow_tables_to_appear_in_same_query!(
account_transfers,
accounts,
- bucket_transfers,
+ bucket_transactions,
buckets,
drips,
pipes,
diff --git a/schist_core/schist_models/user_data.sqlite b/schist_core/schist_models/user_data.sqlite
deleted file mode 100644
index 175ce60..0000000
--- a/schist_core/schist_models/user_data.sqlite
+++ /dev/null
Binary files differ
diff --git a/schist_core/schist_queries/src/bucket_transactions.rs b/schist_core/schist_queries/src/bucket_transactions.rs
new file mode 100644
index 0000000..da06341
--- /dev/null
+++ b/schist_core/schist_queries/src/bucket_transactions.rs
@@ -0,0 +1,65 @@
+use anyhow::{Context, Result};
+use diesel::{dsl::sum, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection};
+use schist_models::BucketTransaction;
+use schist_schema::schema::bucket_transactions::{
+ self as bucket_transactions_schema, dsl::bucket_transactions as bucket_transactions_table,
+};
+
+pub fn delete_all_bucket_transactions(connection: &mut SqliteConnection) -> Result<usize> {
+ let num_rows_deleted = diesel::delete(bucket_transactions_table)
+ .execute(connection)
+ .with_context(|| "failed to delete all bucket transactions")?;
+ Ok(num_rows_deleted)
+}
+
+pub fn get_all_bucket_transactions(
+ connection: &mut SqliteConnection,
+) -> Result<Vec<BucketTransaction>> {
+ let all_bucket_transactions = bucket_transactions_table
+ .select(BucketTransaction::as_select())
+ .load(connection)
+ .with_context(|| "failed to get all bucket transactions")?;
+ Ok(all_bucket_transactions)
+}
+
+pub fn insert_bucket_transactions(
+ bucket_transactions: &[BucketTransaction],
+ connection: &mut SqliteConnection,
+) -> Result<usize> {
+ let num_rows_inserted = diesel::insert_into(bucket_transactions_table)
+ .values(bucket_transactions)
+ .execute(connection)
+ .with_context(|| insert_err_msg(&bucket_transactions))?;
+ Ok(num_rows_inserted)
+}
+
+fn insert_err_msg(bucket_transactions: &[BucketTransaction]) -> String {
+ format!(
+ "failed to insert bucket transactions: [{}]",
+ bucket_transactions
+ .iter()
+ .map(|ct| ct.id.to_string())
+ .collect::<Vec<String>>()
+ .join(", ")
+ )
+}
+
+pub fn sum_bucket_transaction_amount_per_bucket_id(
+ connection: &mut SqliteConnection,
+) -> Result<Vec<(i32, i64)>> {
+ let sum = bucket_transactions_table
+ .group_by(bucket_transactions_schema::bucket_id)
+ .select((
+ bucket_transactions_schema::bucket_id,
+ sum(bucket_transactions_schema::amount),
+ ))
+ .load::<(i32, Option<i64>)>(connection)
+ .map(|result| {
+ result
+ .iter()
+ .map(|sum| (sum.0, sum.1.unwrap_or(0)))
+ .collect()
+ })
+ .with_context(|| "failed to sum bucket transaction amount per bucket ID")?;
+ Ok(sum)
+}
diff --git a/schist_core/schist_queries/src/bucket_transfers.rs b/schist_core/schist_queries/src/bucket_transfers.rs
deleted file mode 100644
index 652c8e0..0000000
--- a/schist_core/schist_queries/src/bucket_transfers.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-use anyhow::{Context, Result};
-use diesel::{dsl::sum, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection};
-use schist_models::BucketTransfer;
-use schist_schema::schema::bucket_transfers::{
- self as bucket_transfers_schema, dsl::bucket_transfers as bucket_transfers_table,
-};
-
-pub fn delete_all_bucket_transfers(connection: &mut SqliteConnection) -> Result<usize> {
- let num_rows_deleted = diesel::delete(bucket_transfers_table)
- .execute(connection)
- .with_context(|| "failed to delete all bucket transfers")?;
- Ok(num_rows_deleted)
-}
-
-pub fn get_all_bucket_transfers(connection: &mut SqliteConnection) -> Result<Vec<BucketTransfer>> {
- let all_bucket_transfers = bucket_transfers_table
- .select(BucketTransfer::as_select())
- .load(connection)
- .with_context(|| "failed to get all bucket transfers")?;
- Ok(all_bucket_transfers)
-}
-
-pub fn insert_bucket_transfers(
- bucket_transfers: &[BucketTransfer],
- connection: &mut SqliteConnection,
-) -> Result<usize> {
- let num_rows_inserted = diesel::insert_into(bucket_transfers_table)
- .values(bucket_transfers)
- .execute(connection)
- .with_context(|| insert_err_msg(&bucket_transfers))?;
- Ok(num_rows_inserted)
-}
-
-fn insert_err_msg(bucket_transfers: &[BucketTransfer]) -> String {
- format!(
- "failed to insert bucket transfers: [{}]",
- bucket_transfers
- .iter()
- .map(|ct| ct.id.to_string())
- .collect::<Vec<String>>()
- .join(", ")
- )
-}
-
-pub fn sum_bucket_transfer_quantity_per_from_bucket_id(
- connection: &mut SqliteConnection,
-) -> Result<Vec<(i32, i64)>> {
- let sum = bucket_transfers_table
- .group_by(bucket_transfers_schema::from_bucket_id)
- .select((
- bucket_transfers_schema::from_bucket_id,
- sum(bucket_transfers_schema::amount),
- ))
- .load::<(i32, Option<i64>)>(connection)
- .map(|result| {
- result
- .iter()
- .map(|sum| (sum.0, sum.1.unwrap_or(0)))
- .collect()
- })
- .with_context(|| "failed to sum bucket transfer quantity per from bucket ID")?;
- Ok(sum)
-}
-
-pub fn sum_bucket_transfer_quantity_per_to_bucket_id(
- connection: &mut SqliteConnection,
-) -> Result<Vec<(i32, i64)>> {
- let sum = bucket_transfers_table
- .group_by(bucket_transfers_schema::to_bucket_id)
- .select((
- bucket_transfers_schema::to_bucket_id,
- sum(bucket_transfers_schema::amount),
- ))
- .load::<(i32, Option<i64>)>(connection)
- .map(|vec| {
- vec.iter()
- .map(|(id, sum)| (*id, sum.unwrap_or(0)))
- .collect()
- })
- .with_context(|| "failed to sum bucket transfer quantity per to bucket ID")?;
- Ok(sum)
-}
diff --git a/schist_core/schist_queries/src/clear.rs b/schist_core/schist_queries/src/clear.rs
index a5a82f3..845e109 100644
--- a/schist_core/schist_queries/src/clear.rs
+++ b/schist_core/schist_queries/src/clear.rs
@@ -2,7 +2,9 @@ use anyhow::Result;
use diesel::SqliteConnection;
use crate::{
- account_transfers::delete_all_account_transfers, accounts::delete_all_accounts, bucket_transfers::delete_all_bucket_transfers, buckets::delete_all_buckets, drips::delete_all_drips, pipes::delete_all_pipes, transactions::delete_all_transactions
+ account_transfers::delete_all_account_transfers, accounts::delete_all_accounts,
+ bucket_transactions::delete_all_bucket_transactions, buckets::delete_all_buckets,
+ drips::delete_all_drips, pipes::delete_all_pipes, transactions::delete_all_transactions,
};
pub fn clear(connection: &mut SqliteConnection) -> Result<()> {
@@ -11,7 +13,7 @@ pub fn clear(connection: &mut SqliteConnection) -> Result<()> {
delete_all_pipes(connection)?;
delete_all_transactions(connection)?;
delete_all_accounts(connection)?;
- delete_all_bucket_transfers(connection)?;
+ delete_all_bucket_transactions(connection)?;
delete_all_buckets(connection)?;
Ok(())
}
diff --git a/schist_core/schist_queries/src/lib.rs b/schist_core/schist_queries/src/lib.rs
index ccd558d..ee74d6b 100644
--- a/schist_core/schist_queries/src/lib.rs
+++ b/schist_core/schist_queries/src/lib.rs
@@ -1,6 +1,6 @@
pub mod account_transfers;
pub mod accounts;
-pub mod bucket_transfers;
+pub mod bucket_transactions;
pub mod buckets;
pub mod clear;
pub mod drips;
diff --git a/schist_core/schist_queries/src/utils/sum_drips_for_bucket.rs b/schist_core/schist_queries/src/utils/sum_drips_for_bucket.rs
index 20d05a0..27f6036 100644
--- a/schist_core/schist_queries/src/utils/sum_drips_for_bucket.rs
+++ b/schist_core/schist_queries/src/utils/sum_drips_for_bucket.rs
@@ -42,7 +42,7 @@ mod test {
}
#[test]
- fn when_one_budget_drip_then_return_quantity() {
+ fn when_one_budget_drip_then_return_amount() {
let bucket = make_fake_bucket(0);
let budget_drips = [make_fake_drip_builder(0)
.amount(100)
diff --git a/schist_core/schist_queries/tests/bucket_transactions.rs b/schist_core/schist_queries/tests/bucket_transactions.rs
new file mode 100644
index 0000000..0b63458
--- /dev/null
+++ b/schist_core/schist_queries/tests/bucket_transactions.rs
@@ -0,0 +1,160 @@
+mod common;
+
+use common::test_context::TestContext;
+use diesel::{Connection, SqliteConnection};
+use schist_fakes::{
+ bucket::make_fake_bucket, bucket_transaction::make_fake_bucket_transaction_builder,
+};
+use schist_queries as queries;
+
+#[test]
+fn when_insert_bucket_transactions_then_returns_num_bucket_transactions() {
+ let context = TestContext::new();
+ let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
+ let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
+ let bucket_transactions = [
+ make_fake_bucket_transaction_builder(0)
+ .bucket_id(0)
+ .build()
+ .unwrap(),
+ make_fake_bucket_transaction_builder(1)
+ .bucket_id(1)
+ .build()
+ .unwrap(),
+ ];
+ let _ = queries::buckets::insert_buckets(&buckets, connection);
+
+ let num_bucket_transactions_inserted =
+ queries::bucket_transactions::insert_bucket_transactions(&bucket_transactions, connection)
+ .unwrap();
+
+ assert_eq!(num_bucket_transactions_inserted, 2);
+}
+
+#[test]
+fn given_bucket_transactions_when_get_all_bucket_transactions_then_return_bucket_transactions() {
+ let context = TestContext::new();
+ let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
+ let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
+ let bucket_transactions = [
+ make_fake_bucket_transaction_builder(0)
+ .bucket_id(0)
+ .build()
+ .unwrap(),
+ make_fake_bucket_transaction_builder(1)
+ .bucket_id(1)
+ .build()
+ .unwrap(),
+ ];
+ let _ = queries::buckets::insert_buckets(&buckets, connection);
+ let _ =
+ queries::bucket_transactions::insert_bucket_transactions(&bucket_transactions, connection)
+ .unwrap();
+
+ let bucket_transactions_in_db =
+ queries::bucket_transactions::get_all_bucket_transactions(connection).unwrap();
+
+ assert_eq!(bucket_transactions_in_db.len(), bucket_transactions.len());
+ for bucket_transaction in &bucket_transactions {
+ assert!(bucket_transactions_in_db
+ .iter()
+ .any(|bucket_transaction_in_db| bucket_transaction_in_db.eq(&bucket_transaction)));
+ }
+ for bucket_transaction_in_db in bucket_transactions_in_db {
+ assert!(bucket_transactions
+ .iter()
+ .any(|bucket_transaction| bucket_transaction.eq(&bucket_transaction_in_db)));
+ }
+}
+
+#[test]
+fn given_bucket_transactions_when_delete_all_bucket_transactions_then_return_num_of_bucket_transactions(
+) {
+ let context = TestContext::new();
+ let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
+ let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
+ let bucket_transactions = [
+ make_fake_bucket_transaction_builder(0)
+ .bucket_id(0)
+ .build()
+ .unwrap(),
+ make_fake_bucket_transaction_builder(1)
+ .bucket_id(1)
+ .build()
+ .unwrap(),
+ ];
+ let _ = queries::buckets::insert_buckets(&buckets, connection);
+ let _ =
+ queries::bucket_transactions::insert_bucket_transactions(&bucket_transactions, connection)
+ .unwrap();
+
+ let num_bucket_transactions_deleted =
+ queries::bucket_transactions::delete_all_bucket_transactions(connection).unwrap();
+
+ assert_eq!(num_bucket_transactions_deleted, bucket_transactions.len());
+}
+
+#[test]
+fn given_deleted_bucket_transactions_when_get_all_bucket_transactions_then_return_empty() {
+ let context = TestContext::new();
+ let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
+ let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
+ let bucket_transactions = [
+ make_fake_bucket_transaction_builder(0)
+ .bucket_id(0)
+ .build()
+ .unwrap(),
+ make_fake_bucket_transaction_builder(1)
+ .bucket_id(1)
+ .build()
+ .unwrap(),
+ ];
+ let _ = queries::buckets::insert_buckets(&buckets, connection);
+ let _ =
+ queries::bucket_transactions::insert_bucket_transactions(&bucket_transactions, connection)
+ .unwrap();
+ let _ = queries::bucket_transactions::delete_all_bucket_transactions(connection).unwrap();
+
+ let bucket_transactions_returned =
+ queries::bucket_transactions::get_all_bucket_transactions(connection).unwrap();
+
+ assert_eq!(bucket_transactions_returned.len(), 0);
+}
+
+#[test]
+fn given_bucket_transactions_when_sum_amount_per_bucket_id_then_returns_sum_per_bucket_id() {
+ let context = TestContext::new();
+ let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
+ let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
+ let bucket_transactions = [
+ make_fake_bucket_transaction_builder(0)
+ .bucket_id(0)
+ .amount(1)
+ .build()
+ .unwrap(),
+ make_fake_bucket_transaction_builder(1)
+ .bucket_id(0)
+ .amount(2)
+ .build()
+ .unwrap(),
+ make_fake_bucket_transaction_builder(2)
+ .bucket_id(1)
+ .amount(4)
+ .build()
+ .unwrap(),
+ ];
+ let _ = queries::buckets::insert_buckets(&buckets, connection);
+ let _ =
+ queries::bucket_transactions::insert_bucket_transactions(&bucket_transactions, connection)
+ .unwrap();
+
+ let sums =
+ queries::bucket_transactions::sum_bucket_transaction_amount_per_bucket_id(connection);
+
+ assert!(sums.is_ok());
+ let mut sums = sums.unwrap();
+ sums.sort_by_key(|sum| sum.0);
+ assert_eq!(buckets.len(), sums.len());
+ assert_eq!(3, sums[0].1);
+ assert_eq!(4, sums[1].1);
+}
diff --git a/schist_core/schist_queries/tests/categories.rs b/schist_core/schist_queries/tests/buckets.rs
index 72dd4c7..72dd4c7 100644
--- a/schist_core/schist_queries/tests/categories.rs
+++ b/schist_core/schist_queries/tests/buckets.rs
diff --git a/schist_core/schist_queries/tests/category_transfers.rs b/schist_core/schist_queries/tests/category_transfers.rs
deleted file mode 100644
index f7c75a7..0000000
--- a/schist_core/schist_queries/tests/category_transfers.rs
+++ /dev/null
@@ -1,201 +0,0 @@
-mod common;
-
-use common::test_context::TestContext;
-use diesel::{Connection, SqliteConnection};
-use schist_fakes::{bucket::make_fake_bucket, bucket_transfer::make_fake_bucket_transfer_builder};
-use schist_queries as queries;
-
-#[test]
-fn when_insert_bucket_transfers_then_returns_num_bucket_transfers() {
- let context = TestContext::new();
- let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
- let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
- let bucket_transfers = [
- make_fake_bucket_transfer_builder(0)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .build()
- .unwrap(),
- make_fake_bucket_transfer_builder(1)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .build()
- .unwrap(),
- ];
- let _ = queries::buckets::insert_buckets(&buckets, connection);
-
- let num_bucket_transfers_inserted =
- queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap();
-
- assert_eq!(num_bucket_transfers_inserted, 2);
-}
-
-#[test]
-fn given_bucket_transfers_when_get_all_bucket_transfers_then_return_bucket_transfers() {
- let context = TestContext::new();
- let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
- let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
- let bucket_transfers = [
- make_fake_bucket_transfer_builder(0)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .build()
- .unwrap(),
- make_fake_bucket_transfer_builder(1)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .build()
- .unwrap(),
- ];
- let _ = queries::buckets::insert_buckets(&buckets, connection);
- let _ =
- queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap();
-
- let bucket_transfers_in_db =
- queries::bucket_transfers::get_all_bucket_transfers(connection).unwrap();
-
- assert_eq!(bucket_transfers_in_db.len(), bucket_transfers.len());
- for bucket_transfer in &bucket_transfers {
- assert!(bucket_transfers_in_db
- .iter()
- .any(|bucket_transfer_in_db| bucket_transfer_in_db.eq(&bucket_transfer)));
- }
- for bucket_transfer_in_db in bucket_transfers_in_db {
- assert!(bucket_transfers
- .iter()
- .any(|bucket_transfer| bucket_transfer.eq(&bucket_transfer_in_db)));
- }
-}
-
-#[test]
-fn given_bucket_transfers_when_delete_all_bucket_transfers_then_return_num_of_bucket_transfers() {
- let context = TestContext::new();
- let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
- let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
- let bucket_transfers = [
- make_fake_bucket_transfer_builder(0)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .build()
- .unwrap(),
- make_fake_bucket_transfer_builder(1)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .build()
- .unwrap(),
- ];
- let _ = queries::buckets::insert_buckets(&buckets, connection);
- let _ =
- queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap();
-
- let num_bucket_transfers_deleted =
- queries::bucket_transfers::delete_all_bucket_transfers(connection).unwrap();
-
- assert_eq!(num_bucket_transfers_deleted, bucket_transfers.len());
-}
-
-#[test]
-fn given_deleted_bucket_transfers_when_get_all_bucket_transfers_then_return_empty() {
- let context = TestContext::new();
- let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
- let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
- let bucket_transfers = [
- make_fake_bucket_transfer_builder(0)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .build()
- .unwrap(),
- make_fake_bucket_transfer_builder(1)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .build()
- .unwrap(),
- ];
- let _ = queries::buckets::insert_buckets(&buckets, connection);
- let _ =
- queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap();
- let _ = queries::bucket_transfers::delete_all_bucket_transfers(connection).unwrap();
-
- let bucket_transfers_returned =
- queries::bucket_transfers::get_all_bucket_transfers(connection).unwrap();
-
- assert_eq!(bucket_transfers_returned.len(), 0);
-}
-
-#[test]
-fn given_bucket_transfers_when_sum_quantity_per_from_bucket_id_then_returns_sum_per_from_bucket_id()
-{
- let context = TestContext::new();
- let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
- let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
- let bucket_transfers = [
- make_fake_bucket_transfer_builder(0)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .amount(1)
- .build()
- .unwrap(),
- make_fake_bucket_transfer_builder(1)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .amount(2)
- .build()
- .unwrap(),
- make_fake_bucket_transfer_builder(2)
- .from_bucket_id(1)
- .to_bucket_id(0)
- .amount(4)
- .build()
- .unwrap(),
- ];
- let _ = queries::buckets::insert_buckets(&buckets, connection);
- let _ =
- queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap();
-
- let sums =
- queries::bucket_transfers::sum_bucket_transfer_quantity_per_from_bucket_id(connection);
-
- assert!(sums.is_ok());
- let mut sums = sums.unwrap();
- sums.sort_by_key(|sum| sum.0);
- assert_eq!(buckets.len(), sums.len());
- assert_eq!(3, sums[0].1);
- assert_eq!(4, sums[1].1);
-}
-
-#[test]
-fn given_bucket_transfers_when_sum_quantity_per_to_bucket_id_then_returns_sum_per_to_bucket_id() {
- let context = TestContext::new();
- let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
- let buckets = [make_fake_bucket(0), make_fake_bucket(1)];
- let bucket_transfers = [
- make_fake_bucket_transfer_builder(0)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .amount(1)
- .build()
- .unwrap(),
- make_fake_bucket_transfer_builder(1)
- .from_bucket_id(0)
- .to_bucket_id(1)
- .amount(2)
- .build()
- .unwrap(),
- make_fake_bucket_transfer_builder(2)
- .from_bucket_id(1)
- .to_bucket_id(0)
- .amount(4)
- .build()
- .unwrap(),
- ];
- let _ = queries::buckets::insert_buckets(&buckets, connection);
- let _ =
- queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap();
-
- let sums = queries::bucket_transfers::sum_bucket_transfer_quantity_per_to_bucket_id(connection);
-
- assert!(sums.is_ok());
- let mut sums = sums.unwrap();
- sums.sort_by_key(|sum| sum.0);
- assert_eq!([(0, 4), (1, 3)], sums.as_slice());
-}
diff --git a/schist_core/schist_queries/tests/pipes.rs b/schist_core/schist_queries/tests/pipes.rs
index 9203aa3..f883fe6 100644
--- a/schist_core/schist_queries/tests/pipes.rs
+++ b/schist_core/schist_queries/tests/pipes.rs
@@ -115,7 +115,7 @@ fn given_deleted_pipes_when_get_all_pipes_then_return_empty() {
}
#[test]
-fn given_pipes_when_sum_quantity_per_bucket_id_then_returns_sum_quantity_per_bucket_id() {
+fn given_pipes_when_sum_amount_per_bucket_id_then_returns_sum_amount_per_bucket_id() {
let context = TestContext::new();
let connection = &mut SqliteConnection::establish(&context.db_url).unwrap();
given_accounts(connection);
diff --git a/schist_desktop_gui/src/gui/components/balances_view.rs b/schist_desktop_gui/src/gui/components/balances_view.rs
index 3ca37be..a1c8db3 100644
--- a/schist_desktop_gui/src/gui/components/balances_view.rs
+++ b/schist_desktop_gui/src/gui/components/balances_view.rs
@@ -3,19 +3,13 @@ mod update_balances_view;
mod view_balances_view;
mod view_group_names;
-use self::{
- update_balances_view::update_balances_view, view_balances_view::view_balances_view,
- view_group_names::view_group_names,
-};
+use self::view_group_names::view_group_names;
use schist_models::Bucket;
-use crate::{
- gui::components::{
- balances_view::make_balances_view_greeting::make_balances_view_greeting, navigation,
- Navigation, Text,
- },
- traits::{Component, Viewable},
+use crate::gui::components::{
+ balances_view::make_balances_view_greeting::make_balances_view_greeting, navigation,
+ Navigation, Text,
};
#[derive(Clone, Debug)]
@@ -49,15 +43,3 @@ impl BalancesView {
}
}
}
-
-impl<'a> Viewable<'a, Message> for BalancesView {
- fn view(&'a self) -> iced::Element<'a, Message> {
- view_balances_view(self)
- }
-}
-
-impl<'a> Component<'a, Message, Action> for BalancesView {
- fn update(&mut self, message: Message) -> Action {
- update_balances_view(self, message)
- }
-}
diff --git a/schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs b/schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs
index e7b32c3..1dd5c6f 100644
--- a/schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs
+++ b/schist_desktop_gui/src/gui/components/balances_view/update_balances_view.rs
@@ -7,42 +7,57 @@ use crate::{
traits::Component,
};
-pub fn update_balances_view(balances_view: &mut BalancesView, message: Message) -> Action {
- match message {
- Message::NavigationMessage(message) => update_navigation(balances_view, message),
- Message::SetBuckets(buckets) => set_buckets(balances_view, buckets),
- Message::SelectNextBucket => select_next_bucket(balances_view),
- Message::SelectPrevBucket => select_prev_bucket(balances_view),
+impl<'a> Component<'a, Message, Action> for BalancesView {
+ fn update(&mut self, message: Message) -> Action {
+ match message {
+ Message::NavigationMessage(message) => {
+ update_navigation(self, message);
+ Action::None
+ }
+
+ Message::SetBuckets(buckets) => {
+ set_buckets(self, buckets);
+ Action::None
+ }
+
+ Message::SelectNextBucket => {
+ select_next_bucket(self);
+ Action::None
+ }
+
+ Message::SelectPrevBucket => {
+ select_prev_bucket(self);
+ Action::None
+ }
+ }
}
}
-fn select_prev_bucket(balances_view: &mut BalancesView) -> Action {
+fn select_prev_bucket(balances_view: &mut BalancesView) -> () {
match balances_view
.navigation
.update(navigation::Message::SelectPrev)
{
navigation::Action::SelectOption(option) => {
balances_view.greeting = make_balances_view_greeting(Some(option));
- Action::None
}
- navigation::Action::None => Action::None,
- }
+ navigation::Action::None => {}
+ };
}
-fn select_next_bucket(balances_view: &mut BalancesView) -> Action {
+fn select_next_bucket(balances_view: &mut BalancesView) -> () {
match balances_view
.navigation
.update(navigation::Message::SelectNext)
{
navigation::Action::SelectOption(option) => {
balances_view.greeting = make_balances_view_greeting(Some(option));
- Action::None
}
- navigation::Action::None => Action::None,
- }
+ navigation::Action::None => {}
+ };
}
-fn set_buckets(balances_view: &mut BalancesView, buckets: Vec<schist_models::Bucket>) -> Action {
+fn set_buckets(balances_view: &mut BalancesView, buckets: Vec<schist_models::Bucket>) -> () {
balances_view.buckets = buckets.clone();
match balances_view
.navigation
@@ -50,23 +65,18 @@ fn set_buckets(balances_view: &mut BalancesView, buckets: Vec<schist_models::Buc
{
navigation::Action::SelectOption(option) => {
balances_view.greeting = make_balances_view_greeting(Some(option));
- Action::None
}
- navigation::Action::None => Action::None,
- }
+ navigation::Action::None => {}
+ };
}
-fn update_navigation(
- balances_view: &mut BalancesView,
- message: navigation::Message<Text>,
-) -> Action {
+fn update_navigation(balances_view: &mut BalancesView, message: navigation::Message<Text>) -> () {
match balances_view.navigation.update(message) {
navigation::Action::SelectOption(group_name) => {
balances_view.greeting = make_balances_view_greeting(Some(group_name));
- Action::None
}
- navigation::Action::None => Action::None,
- }
+ navigation::Action::None => {}
+ };
}
#[cfg(test)]
diff --git a/schist_desktop_gui/src/gui/components/balances_view/view_balances_view.rs b/schist_desktop_gui/src/gui/components/balances_view/view_balances_view.rs
index 6a64508..22f22b6 100644
--- a/schist_desktop_gui/src/gui/components/balances_view/view_balances_view.rs
+++ b/schist_desktop_gui/src/gui/components/balances_view/view_balances_view.rs
@@ -1,23 +1,30 @@
-use iced::widget::{column, row, Container};
-use iced::{alignment, Element, Length};
+use iced::{
+ alignment,
+ widget::{column, row, Container},
+ Length,
+};
-use crate::gui::components::Text;
-use crate::gui::components::{balances_view::Message, BalancesView};
-use crate::style::SPACING_LG;
-use crate::traits::Viewable;
+use crate::{
+ gui::components::{balances_view::Message, BalancesView, Text},
+ style::SPACING_LG,
+ traits::Viewable,
+};
-pub fn view_balances_view<'a>(balances_view: &'a BalancesView) -> Element<'a, Message> {
- let navigation = balances_view
- .navigation
- .view()
- .map(Message::NavigationMessage);
- let main_content = column![Text::default(&balances_view.greeting).as_element()];
- row![
- Container::new(navigation).width(Length::FillPortion(1)),
- Container::new(main_content).width(Length::FillPortion(3)),
- ]
- .align_y(alignment::Vertical::Center)
- .height(Length::Fill)
- .spacing(SPACING_LG)
- .into()
+impl<'a> Viewable<'a, Message> for BalancesView {
+ fn view(&'a self) -> iced::Element<'a, Message> {
+ let balances_view: &'a BalancesView = self;
+ let navigation = balances_view
+ .navigation
+ .view()
+ .map(Message::NavigationMessage);
+ let main_content = column![Text::default(&balances_view.greeting).as_element()];
+ row![
+ Container::new(navigation).width(Length::FillPortion(1)),
+ Container::new(main_content).width(Length::FillPortion(3)),
+ ]
+ .align_y(alignment::Vertical::Center)
+ .height(Length::Fill)
+ .spacing(SPACING_LG)
+ .into()
+ }
}
diff --git a/schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs b/schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs
index 24a3952..8a5a16e 100644
--- a/schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs
+++ b/schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs
@@ -1,7 +1,10 @@
-use iced::{widget::row, Element};
+use iced::{
+ widget::{row, text},
+ Element, Length,
+};
use schist_models::Bucket;
-use crate::gui::components::{navigation, Text};
+use crate::gui::components::navigation;
#[derive(Clone, Debug, PartialEq)]
pub struct BucketNameAndBalance {
@@ -14,11 +17,21 @@ impl BucketNameAndBalance {
}
}
-impl<'a> Into<Element<'a, navigation::Message<BucketNameAndBalance>>> for BucketNameAndBalance {
- fn into(self) -> Element<'a, navigation::Message<BucketNameAndBalance>> {
+impl<'a> From<&'a BucketNameAndBalance> for Element<'a, navigation::Message<BucketNameAndBalance>> {
+ fn from(bucket_name_and_balance: &'a BucketNameAndBalance) -> Self {
+ let balance_style = if bucket_name_and_balance
+ .bucket
+ .balance
+ .is_none_or(|balance| balance >= 0)
+ {
+ text::default
+ } else {
+ text::danger
+ };
+
row![
- Text::new(&self.bucket.name).as_element(),
- Text::new(&self.bucket.balance.unwrap_or(0).to_string()).as_element(),
+ text(bucket_name_and_balance.bucket.name.clone()).width(Length::Fill),
+ text(bucket_name_and_balance.bucket.pretty_balance_per_100()).style(balance_style),
]
.into()
}
diff --git a/schist_desktop_gui/src/gui/components/button.rs b/schist_desktop_gui/src/gui/components/button.rs
index e071f33..6882273 100644
--- a/schist_desktop_gui/src/gui/components/button.rs
+++ b/schist_desktop_gui/src/gui/components/button.rs
@@ -2,9 +2,10 @@ use iced::{widget::Button, Element};
use crate::style::*;
-pub fn active_button<'a, Content: Into<Element<'a, Message>>, Message>(
- content: Content,
-) -> Button<'a, Message> {
+pub fn active_button<'a, Content, Message>(content: &'a Content) -> Button<'a, Message>
+where
+ Element<'a, Message>: From<&'a Content>,
+{
iced::widget::button(content)
.style(|theme: &iced::Theme, _status| iced::widget::button::Style {
background: Some(iced::Background::Color(
diff --git a/schist_desktop_gui/src/gui/components/navigation.rs b/schist_desktop_gui/src/gui/components/navigation.rs
index bed3221..6dbe622 100644
--- a/schist_desktop_gui/src/gui/components/navigation.rs
+++ b/schist_desktop_gui/src/gui/components/navigation.rs
@@ -1,19 +1,9 @@
-mod new_navigation;
mod update_navigation;
mod view_navigation;
mod view_navigation_button;
-use new_navigation::new_navigation;
-use update_navigation::update_navigation;
-use view_navigation::view_navigation;
-use view_navigation_button::view_navigation_button;
-
use std::fmt::Debug;
-use iced::Element;
-
-use crate::traits::{Component, Viewable};
-
#[derive(Clone, Debug)]
pub struct Navigation<TOption> {
options_before_active: Vec<TOption>,
@@ -36,33 +26,67 @@ pub enum Action<TOption> {
impl<TOption: Clone + Debug + PartialEq> Navigation<TOption> {
pub fn new(active_option: Option<TOption>, options: Vec<TOption>) -> Self {
- new_navigation(active_option, options)
+ let active_option_index = active_option.clone().map_or(Option::None, |active_option| {
+ options.iter().position(|option| option.eq(&active_option))
+ });
+ if let Some(active_option_index) = active_option_index {
+ let split = options.split_at(active_option_index);
+ Navigation {
+ options_before_active: split.0.to_vec(),
+ active_option: active_option,
+ options_after_active: split.1.split_at(1).1.to_vec(),
+ }
+ } else {
+ Navigation {
+ options_before_active: options,
+ active_option: active_option,
+ options_after_active: vec![],
+ }
+ }
}
}
-impl<'a, TOption> Navigation<TOption>
-where
- TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>,
-{
- pub fn button(&'a self, option: &'a TOption) -> Element<'a, Message<TOption>> {
- view_navigation_button(self, option)
+#[cfg(test)]
+mod test {
+ use super::Navigation;
+
+ #[test]
+ fn when_active_option_at_start_of_options_then_finds_active_option() {
+ let navigation = Navigation::new(Some(1), vec![1, 2, 3, 4, 5]);
+ assert_eq!(Vec::<i32>::new(), navigation.options_before_active);
+ assert_eq!(Some(1), navigation.active_option);
+ assert_eq!(vec![2, 3, 4, 5], navigation.options_after_active);
}
-}
-impl<'a, TOption> Viewable<'a, Message<TOption>> for Navigation<TOption>
-where
- TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>,
-{
- fn view(&'a self) -> iced::Element<'a, Message<TOption>> {
- view_navigation(self)
+ #[test]
+ fn when_active_option_in_middle_of_options_then_finds_active_option() {
+ let navigation = Navigation::new(Some(3), vec![1, 2, 3, 4, 5]);
+ assert_eq!(vec![1, 2], navigation.options_before_active);
+ assert_eq!(Some(3), navigation.active_option);
+ assert_eq!(vec![4, 5], navigation.options_after_active);
+ }
+
+ #[test]
+ fn when_active_option_at_end_of_options_then_finds_active_option() {
+ let navigation = Navigation::new(Some(5), vec![1, 2, 3, 4, 5]);
+ assert_eq!(vec![1, 2, 3, 4], navigation.options_before_active);
+ assert_eq!(Some(5), navigation.active_option);
+ assert_eq!(Vec::<i32>::new(), navigation.options_after_active);
+ }
+
+ #[test]
+ fn when_active_option_not_in_options_then_appends_active_option() {
+ let navigation = Navigation::new(Some(6), vec![1, 2, 3, 4, 5]);
+ assert_eq!(vec![1, 2, 3, 4, 5], navigation.options_before_active);
+ assert_eq!(Some(6), navigation.active_option);
+ assert_eq!(Vec::<i32>::new(), navigation.options_after_active);
}
-}
-impl<'a, TOption> Component<'a, Message<TOption>, Action<TOption>> for Navigation<TOption>
-where
- TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>,
-{
- fn update(&mut self, message: Message<TOption>) -> Action<TOption> {
- update_navigation(self, message)
+ #[test]
+ fn when_no_active_option_then_all_options_are_before_active_option() {
+ let navigation = Navigation::new(None, vec![1, 2, 3, 4, 5]);
+ assert_eq!(vec![1, 2, 3, 4, 5], navigation.options_before_active);
+ assert_eq!(None, navigation.active_option);
+ assert_eq!(Vec::<i32>::new(), navigation.options_after_active);
}
}
diff --git a/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs
deleted file mode 100644
index b5c0c19..0000000
--- a/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-use super::Navigation;
-
-pub fn new_navigation<TOption>(
- active_option: Option<TOption>,
- options: Vec<TOption>,
-) -> Navigation<TOption>
-where
- TOption: Clone + PartialEq,
-{
- let active_option_index = active_option.clone().map_or(Option::None, |active_option| {
- options.iter().position(|option| option.eq(&active_option))
- });
- if let Some(active_option_index) = active_option_index {
- let split = options.split_at(active_option_index);
- Navigation {
- options_before_active: split.0.to_vec(),
- active_option,
- options_after_active: split.1.split_at(1).1.to_vec(),
- }
- } else {
- Navigation {
- options_before_active: options,
- active_option,
- options_after_active: vec![],
- }
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::Navigation;
-
- #[test]
- fn when_active_option_at_start_of_options_then_finds_active_option() {
- let navigation = Navigation::new(Some(1), vec![1, 2, 3, 4, 5]);
- assert_eq!(Vec::<i32>::new(), navigation.options_before_active);
- assert_eq!(Some(1), navigation.active_option);
- assert_eq!(vec![2, 3, 4, 5], navigation.options_after_active);
- }
-
- #[test]
- fn when_active_option_in_middle_of_options_then_finds_active_option() {
- let navigation = Navigation::new(Some(3), vec![1, 2, 3, 4, 5]);
- assert_eq!(vec![1, 2], navigation.options_before_active);
- assert_eq!(Some(3), navigation.active_option);
- assert_eq!(vec![4, 5], navigation.options_after_active);
- }
-
- #[test]
- fn when_active_option_at_end_of_options_then_finds_active_option() {
- let navigation = Navigation::new(Some(5), vec![1, 2, 3, 4, 5]);
- assert_eq!(vec![1, 2, 3, 4], navigation.options_before_active);
- assert_eq!(Some(5), navigation.active_option);
- assert_eq!(Vec::<i32>::new(), navigation.options_after_active);
- }
-
- #[test]
- fn when_active_option_not_in_options_then_appends_active_option() {
- let navigation = Navigation::new(Some(6), vec![1, 2, 3, 4, 5]);
- assert_eq!(vec![1, 2, 3, 4, 5], navigation.options_before_active);
- assert_eq!(Some(6), navigation.active_option);
- assert_eq!(Vec::<i32>::new(), navigation.options_after_active);
- }
-
- #[test]
- fn when_no_active_option_then_all_options_are_before_active_option() {
- let navigation = Navigation::new(None, vec![1, 2, 3, 4, 5]);
- assert_eq!(vec![1, 2, 3, 4, 5], navigation.options_before_active);
- assert_eq!(None, navigation.active_option);
- assert_eq!(Vec::<i32>::new(), navigation.options_after_active);
- }
-}
diff --git a/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs
index f86867f..c44ddd8 100644
--- a/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs
@@ -1,127 +1,130 @@
use iced::Element;
+use crate::traits::Component;
+
use super::{Action, Message, Navigation};
-pub fn update_navigation<'a, TOption>(
- navigation: &mut Navigation<TOption>,
- message: Message<TOption>,
-) -> Action<TOption>
+impl<'a, TOption> Component<'a, Message<TOption>, Action<TOption>> for Navigation<TOption>
where
- TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>,
+ TOption: Clone + PartialEq + 'a,
+ Element<'a, Message<TOption>>: From<&'a TOption>,
{
- match message {
- Message::SelectOption(option) => {
- if navigation
- .active_option
- .as_ref()
- .is_some_and(|active_option| active_option.clone() == option)
- {
- Action::None
- } else if let Some(index) = navigation
- .options_before_active
- .iter()
- .position(|o| o.clone() == option)
- {
- let split = navigation.options_before_active.split_at(index);
- navigation.options_after_active = vec![
+ fn update(&mut self, message: Message<TOption>) -> Action<TOption> {
+ let navigation: &mut Navigation<TOption> = self;
+ match message {
+ Message::SelectOption(option) => {
+ if navigation
+ .active_option
+ .as_ref()
+ .is_some_and(|active_option| TOption::eq(active_option, &option))
+ {
+ Action::None
+ } else if let Some(index) = navigation
+ .options_before_active
+ .iter()
+ .position(|o| TOption::eq(o, &option))
+ {
+ let split = navigation.options_before_active.split_at(index);
+ navigation.options_after_active = vec![
+ navigation
+ .active_option
+ .clone()
+ .map_or(vec![], |active_option| vec![active_option]),
+ split.1.split_at(1).1.to_vec(),
+ navigation.options_after_active.clone(),
+ ]
+ .concat();
+ navigation.options_before_active = split.0.to_vec();
+ navigation.active_option = Some(option.clone());
+ Action::SelectOption(option)
+ } else if let Some(index) = navigation
+ .options_after_active
+ .iter()
+ .position(|o| TOption::eq(o, &option))
+ {
+ let split = navigation.options_after_active.split_at(index);
+ navigation.options_before_active = vec![
+ navigation.options_before_active.clone(),
+ split.0.to_vec(),
+ navigation
+ .active_option
+ .clone()
+ .map_or(vec![], |active_option| vec![active_option]),
+ ]
+ .concat();
+ navigation.active_option = Some(option.clone());
+ navigation.options_after_active = split.1.split_at(1).1.to_vec();
+ Action::SelectOption(option)
+ } else {
+ if let Some(active_option) = &navigation.active_option {
+ navigation.options_before_active.push(active_option.clone());
+ }
navigation
- .active_option
- .clone()
- .map_or(vec![], |active_option| vec![active_option]),
- split.1.split_at(1).1.to_vec(),
- navigation.options_after_active.clone(),
- ]
- .concat();
- navigation.options_before_active = split.0.to_vec();
- navigation.active_option = Some(option.clone());
- Action::SelectOption(option)
- } else if let Some(index) = navigation
- .options_after_active
- .iter()
- .position(|o| o.clone() == option)
- {
- let split = navigation.options_after_active.split_at(index);
- navigation.options_before_active = vec![
- navigation.options_before_active.clone(),
- split.0.to_vec(),
+ .options_before_active
+ .append(&mut navigation.options_after_active);
+ navigation.active_option = Some(option.clone());
+ navigation.options_after_active = vec![];
+ Action::SelectOption(option)
+ }
+ }
+ Message::SetOptions(options) => {
+ let active_option_index =
navigation
.active_option
.clone()
- .map_or(vec![], |active_option| vec![active_option]),
- ]
- .concat();
- navigation.active_option = Some(option.clone());
- navigation.options_after_active = split.1.split_at(1).1.to_vec();
- Action::SelectOption(option)
- } else {
- if let Some(active_option) = &navigation.active_option {
- navigation.options_before_active.push(active_option.clone());
+ .map_or(Option::None, |active_option| {
+ options
+ .iter()
+ .position(|option| active_option == option.clone())
+ });
+ if let Some(active_option_index) = active_option_index {
+ let split = options.split_at(active_option_index);
+ navigation.options_before_active = split.0.to_vec();
+ navigation.options_after_active = split.1.split_at(1).1.to_vec();
+ Action::None
+ } else {
+ navigation.options_before_active = options
+ .split_last()
+ .map_or_else(Vec::new, |(_last, rest)| rest.to_vec());
+ navigation.active_option = options.last().cloned();
+ options
+ .last()
+ .cloned()
+ .map_or(Action::None, Action::SelectOption)
}
- navigation
- .options_before_active
- .append(&mut navigation.options_after_active);
- navigation.active_option = Some(option.clone());
- navigation.options_after_active = vec![];
- Action::SelectOption(option)
}
- }
- Message::SetOptions(options) => {
- let active_option_index =
- navigation
- .active_option
- .clone()
- .map_or(Option::None, |active_option| {
- options
- .iter()
- .position(|option| active_option == option.clone())
- });
- if let Some(active_option_index) = active_option_index {
- let split = options.split_at(active_option_index);
- navigation.options_before_active = split.0.to_vec();
- navigation.options_after_active = split.1.split_at(1).1.to_vec();
- Action::None
- } else {
- navigation.options_before_active = options
- .split_last()
- .map_or_else(Vec::new, |(_last, rest)| rest.to_vec());
- navigation.active_option = options.last().cloned();
- options
- .last()
- .cloned()
- .map_or(Action::None, Action::SelectOption)
- }
- }
- Message::SelectNext => {
- if let Some(next) = navigation.options_after_active.clone().first() {
- navigation.options_before_active = vec![
- navigation.options_before_active.clone(),
- navigation.active_option.clone().map_or(vec![], |o| vec![o]),
- ]
- .concat();
- navigation.active_option = Some(next.clone());
- navigation.options_after_active =
- navigation.options_after_active.split_at(1).1.to_vec();
- Action::SelectOption(next.clone())
- } else {
- Action::None
+ Message::SelectNext => {
+ if let Some(next) = navigation.options_after_active.clone().first() {
+ navigation.options_before_active = vec![
+ navigation.options_before_active.clone(),
+ navigation.active_option.clone().map_or(vec![], |o| vec![o]),
+ ]
+ .concat();
+ navigation.active_option = Some(next.clone());
+ navigation.options_after_active =
+ navigation.options_after_active.split_at(1).1.to_vec();
+ Action::SelectOption(next.clone())
+ } else {
+ Action::None
+ }
}
- }
- Message::SelectPrev => {
- if let Some(prev) = navigation.options_before_active.clone().last() {
- navigation.options_after_active = vec![
- navigation.active_option.clone().map_or(vec![], |o| vec![o]),
- navigation.options_after_active.clone(),
- ]
- .concat();
- navigation.active_option = Some(prev.clone());
- navigation.options_before_active = navigation
- .options_before_active
- .split_last()
- .map(|split| split.1.to_vec())
- .unwrap_or_else(Vec::new);
- Action::SelectOption(prev.clone())
- } else {
- Action::None
+ Message::SelectPrev => {
+ if let Some(prev) = navigation.options_before_active.clone().last() {
+ navigation.options_after_active = vec![
+ navigation.active_option.clone().map_or(vec![], |o| vec![o]),
+ navigation.options_after_active.clone(),
+ ]
+ .concat();
+ navigation.active_option = Some(prev.clone());
+ navigation.options_before_active = navigation
+ .options_before_active
+ .split_last()
+ .map(|split| split.1.to_vec())
+ .unwrap_or_else(Vec::new);
+ Action::SelectOption(prev.clone())
+ } else {
+ Action::None
+ }
}
}
}
diff --git a/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs
index beec818..3f1da18 100644
--- a/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs
@@ -1,26 +1,27 @@
use iced::{widget::column, Element};
+use crate::traits::Viewable;
+
use super::{Message, Navigation};
-pub fn view_navigation<'a, TOption>(
- navigation: &'a Navigation<TOption>,
-) -> Element<'a, Message<TOption>>
+impl<'a, TOption> Viewable<'a, Message<TOption>> for Navigation<TOption>
where
- TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>,
+ TOption: Clone + PartialEq + 'a,
+ Element<'a, Message<TOption>>: From<&'a TOption>,
{
- let mut options = Vec::new();
- navigation
- .options_before_active
- .iter()
- .map(|option| navigation.button(&option))
- .for_each(|option| options.push(option));
- if let Some(active_option) = navigation.active_option.as_ref() {
- options.push(navigation.button(&active_option));
+ fn view(&'a self) -> iced::Element<'a, Message<TOption>> {
+ let mut options = Vec::new();
+ self.options_before_active
+ .iter()
+ .map(|option| self.view_navigation_button(&option))
+ .for_each(|option| options.push(option));
+ if let Some(active_option) = self.active_option.as_ref() {
+ options.push(self.view_navigation_button(&active_option));
+ }
+ self.options_after_active
+ .iter()
+ .map(|option| self.view_navigation_button(&option))
+ .for_each(|option| options.push(option));
+ column(options).into()
}
- navigation
- .options_after_active
- .iter()
- .map(|option| navigation.button(&option))
- .for_each(|option| options.push(option));
- column(options).into()
}
diff --git a/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs b/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs
index 3132bf1..d65328c 100644
--- a/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs
+++ b/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs
@@ -4,24 +4,24 @@ use crate::gui::components::{active_button, inactive_button};
use super::{Message, Navigation};
-pub fn view_navigation_button<'a, TOption>(
- navigation: &'a Navigation<TOption>,
- option: &'a TOption,
-) -> Element<'a, Message<TOption>>
+impl<'a, TOption> Navigation<TOption>
where
- TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>,
+ TOption: Clone + PartialEq + 'a,
+ Element<'a, Message<TOption>>: From<&'a TOption>,
{
- if navigation
- .active_option
- .as_ref()
- .is_some_and(|o| *o == *option)
- {
- active_button(Into::<Element<'a, Message<TOption>>>::into(option.clone())).into()
- } else {
- inactive_button(
- Into::<Element<'a, Message<TOption>>>::into(option.clone()),
- Message::SelectOption(option.clone()),
- )
+ pub(super) fn view_navigation_button(
+ &'a self,
+ option: &'a TOption,
+ ) -> Element<'a, Message<TOption>> {
+ if self
+ .active_option
+ .as_ref()
+ .is_some_and(|o| TOption::eq(o, option))
+ {
+ active_button(option)
+ } else {
+ inactive_button(option, Message::SelectOption(option.clone()))
+ }
.into()
}
}
diff --git a/schist_desktop_gui/src/gui/components/text.rs b/schist_desktop_gui/src/gui/components/text.rs
index 90cd7bc..a3093ac 100644
--- a/schist_desktop_gui/src/gui/components/text.rs
+++ b/schist_desktop_gui/src/gui/components/text.rs
@@ -1,3 +1,5 @@
+use std::borrow::Borrow;
+
use crate::style::*;
#[derive(Clone, Debug, PartialEq)]
@@ -71,30 +73,46 @@ impl Text {
}
}
-impl<'a, Message> Into<iced::Element<'a, Message>> for Text {
- fn into(self) -> iced::Element<'a, Message> {
- iced::widget::text(self.content.clone())
- .size(match self.size {
- Some(Size::Small) => TEXT_SIZE_SM,
- Some(Size::Base) | None => TEXT_SIZE_BASE,
- })
- .style(move |theme: &iced::Theme| iced::widget::text::Style {
- color: match (&self.colour, &self.strength) {
- (Some(Colour::Danger), Some(Strength::Base) | None) => {
- Some(theme.extended_palette().danger.base.text)
- }
- (Some(Colour::Danger), Some(Strength::Weak)) => {
- Some(theme.extended_palette().danger.weak.text)
- }
- (Some(Colour::Primary), Some(Strength::Base) | None) => {
- Some(theme.extended_palette().primary.base.text)
- }
- (Some(Colour::Primary), Some(Strength::Weak)) => {
- Some(theme.extended_palette().primary.weak.text)
- }
- (None, _) => None,
- },
- })
- .into()
+impl<'a, Message> From<Text> for iced::Element<'a, Message> {
+ fn from(text: Text) -> Self {
+ text_as_element(text)
+ }
+}
+
+impl<'a, Message> From<&'a Text> for iced::Element<'a, Message> {
+ fn from(text: &'a Text) -> Self {
+ text_as_element(text)
}
}
+
+fn text_as_element<'a, T, Message>(text: T) -> iced::Element<'a, Message>
+where
+ T: Borrow<Text> + 'a,
+{
+ iced::widget::text(text.borrow().content.clone())
+ .size(match text.borrow().size {
+ Some(Size::Small) => TEXT_SIZE_SM,
+ Some(Size::Base) | None => TEXT_SIZE_BASE,
+ })
+ .style(move |theme: &iced::Theme| iced::widget::text::Style {
+ color: match (
+ &text.borrow().borrow().colour,
+ &text.borrow().borrow().strength,
+ ) {
+ (Some(Colour::Danger), Some(Strength::Base) | None) => {
+ Some(theme.extended_palette().danger.base.text)
+ }
+ (Some(Colour::Danger), Some(Strength::Weak)) => {
+ Some(theme.extended_palette().danger.weak.text)
+ }
+ (Some(Colour::Primary), Some(Strength::Base) | None) => {
+ Some(theme.extended_palette().primary.base.text)
+ }
+ (Some(Colour::Primary), Some(Strength::Weak)) => {
+ Some(theme.extended_palette().primary.weak.text)
+ }
+ (None, _) => None,
+ },
+ })
+ .into()
+}
diff --git a/schist_desktop_gui/src/gui/screens/main_screen/view.rs b/schist_desktop_gui/src/gui/screens/main_screen/view.rs
index 2be427b..5f2df4e 100644
--- a/schist_desktop_gui/src/gui/screens/main_screen/view.rs
+++ b/schist_desktop_gui/src/gui/screens/main_screen/view.rs
@@ -41,8 +41,8 @@ impl View {
}
}
-impl<'a> Into<Element<'a, navigation::Message<View>>> for View {
- fn into(self) -> Element<'a, navigation::Message<View>> {
- Text::new(self.name()).into()
+impl<'a> From<&'a View> for Element<'a, navigation::Message<View>> {
+ fn from(val: &'a View) -> Self {
+ Text::new(val.name()).into()
}
}
diff --git a/schist_desktop_gui/src/paths.rs b/schist_desktop_gui/src/paths.rs
index f5cbf33..b098859 100644
--- a/schist_desktop_gui/src/paths.rs
+++ b/schist_desktop_gui/src/paths.rs
@@ -33,7 +33,7 @@ fn get_file_paths_checked() -> anyhow::Result<Vec<PathBuf>> {
fn is_sqlite_file(entry: &std::fs::DirEntry) -> bool {
entry
.file_type()
- .is_ok_and(|ft| (FileType::is_file(&ft) || FileType::is_symlink(&ft)))
+ .is_ok_and(|ft| FileType::is_file(&ft) || FileType::is_symlink(&ft))
&& entry
.path()
.extension()
diff --git a/transformers/actualbudget_transformer/actualbudget_models/src/actualbudget_date.rs b/transformers/actualbudget_transformer/actualbudget_models/src/actualbudget_date.rs
index 13525fc..d8b6e87 100644
--- a/transformers/actualbudget_transformer/actualbudget_models/src/actualbudget_date.rs
+++ b/transformers/actualbudget_transformer/actualbudget_models/src/actualbudget_date.rs
@@ -1,8 +1,14 @@
-use diesel::{backend::Backend, deserialize::{FromSql, FromSqlRow}, expression::AsExpression, sql_types::Integer, sqlite::Sqlite};
+use diesel::{
+ backend::Backend,
+ deserialize::{FromSql, FromSqlRow},
+ expression::AsExpression,
+ sql_types::Integer,
+ sqlite::Sqlite,
+};
-#[derive(Clone, Hash, PartialEq, Eq, Debug, AsExpression, FromSqlRow)]
+#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, AsExpression, FromSqlRow)]
#[diesel(sql_type=Integer)]
-pub struct ActualbudgetDate(i32);
+pub struct ActualbudgetDate(i64);
#[derive(Clone, PartialOrd, PartialEq, Eq)]
enum DatePart {
@@ -17,7 +23,12 @@ enum DatePart {
impl ActualbudgetDate {
pub fn to_iso(&self) -> String {
- format!("{0}-{1}-{2}T00:00:00.000Z", self.year(), self.month(), self.day())
+ format!(
+ "{0}-{1}-{2}T00:00:00.000Z",
+ self.year(),
+ self.month(),
+ self.day()
+ )
}
pub fn to_iso_date(&self) -> String {
@@ -40,7 +51,20 @@ impl ActualbudgetDate {
}
pub fn from_i32(i: i32) -> Self {
- ActualbudgetDate(i)
+ ActualbudgetDate(i.into())
+ }
+
+ pub fn from_ymd<T, S, U>(year: T, month: S, day: U) -> Self
+ where
+ T: Into<i64>,
+ S: Into<i64>,
+ U: Into<i64>,
+ {
+ Self(year.into() * 10_000 + month.into() * 100 + day.into())
+ }
+
+ pub fn oldest_valid_date() -> Self {
+ Self(10000101)
}
fn date_part(&self) -> DatePart {
@@ -51,7 +75,7 @@ impl ActualbudgetDate {
}
}
- fn dayified_value(&self) -> i32 {
+ fn dayified_value(&self) -> i64 {
match self.date_part() {
DatePart::DayOfMonth => self.0,
DatePart::MonthOfYear => self.0 * 100 + 1,
@@ -74,6 +98,19 @@ impl PartialOrd for ActualbudgetDate {
impl FromSql<Integer, Sqlite> for ActualbudgetDate {
fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> diesel::deserialize::Result<Self> {
let int: i32 = FromSql::<Integer, Sqlite>::from_sql(bytes)?;
- Ok(ActualbudgetDate(int))
+ Ok(ActualbudgetDate(int.into()))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::ActualbudgetDate;
+
+ #[test]
+ fn when_from_ymd_2026_01_16_then_constructs_2026_01_16() {
+ let actualbudget_date = ActualbudgetDate::from_ymd(2026, 1, 16);
+ assert_eq!(actualbudget_date.year(), "2026");
+ assert_eq!(actualbudget_date.month(), "01");
+ assert_eq!(actualbudget_date.day(), "16");
}
}
diff --git a/transformers/actualbudget_transformer/actualbudget_queries/src/actualbudget_accounts.rs b/transformers/actualbudget_transformer/actualbudget_queries/src/actualbudget_accounts.rs
index d499208..8a590a1 100644
--- a/transformers/actualbudget_transformer/actualbudget_queries/src/actualbudget_accounts.rs
+++ b/transformers/actualbudget_transformer/actualbudget_queries/src/actualbudget_accounts.rs
@@ -1,25 +1,14 @@
use actualbudget_models::ActualbudgetAccount;
-use actualbudget_schema::actualbudget_schema::accounts::{
- self as accounts_schema,
- dsl::accounts as accounts_table,
-};
+use actualbudget_schema::actualbudget_schema::accounts::dsl::accounts as accounts_table;
use anyhow::{Context, Result};
-use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection};
+use diesel::{QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection};
-pub fn get_all_actualbudget_accounts(connection: &mut SqliteConnection) -> Result<Vec<ActualbudgetAccount>> {
+pub fn get_all_actualbudget_accounts(
+ connection: &mut SqliteConnection,
+) -> Result<Vec<ActualbudgetAccount>> {
let actualbudget_accounts = accounts_table
.select(ActualbudgetAccount::as_select())
.load(connection)
.with_context(|| "failed to get all Actualbudget accounts")?;
Ok(actualbudget_accounts)
}
-
-pub fn get_category_transfers_account(connection: &mut SqliteConnection) -> Result<Option<ActualbudgetAccount>> {
- let category_transfers_account = accounts_table
- .filter(accounts_schema::name.eq("Category transfers"))
- .select(ActualbudgetAccount::as_select())
- .first(connection)
- .optional()
- .with_context(|| "failed to get Category Transfers account")?;
- Ok(category_transfers_account)
-}
diff --git a/transformers/actualbudget_transformer/actualbudget_queries/tests/actualbudget_accounts.rs b/transformers/actualbudget_transformer/actualbudget_queries/tests/actualbudget_accounts.rs
index 954cac1..78953f2 100644
--- a/transformers/actualbudget_transformer/actualbudget_queries/tests/actualbudget_accounts.rs
+++ b/transformers/actualbudget_transformer/actualbudget_queries/tests/actualbudget_accounts.rs
@@ -1,6 +1,6 @@
pub mod common;
-use actualbudget_queries::actualbudget_accounts::{get_all_actualbudget_accounts, get_category_transfers_account};
+use actualbudget_queries::actualbudget_accounts::get_all_actualbudget_accounts;
use common::{account_names::ACCOUNT_NAMES, db_url::DB_URL};
use diesel::{Connection, SqliteConnection};
@@ -17,16 +17,3 @@ fn given_test_database_when_get_all_accounts_then_return_accounts() {
assert!(accounts.iter().any(|a| a.name.as_str() == *account_name));
}
}
-
-#[test]
-fn given_test_database_when_get_category_transfer_account_then_return_category_transfer_account() {
- let connection = &mut SqliteConnection::establish(DB_URL).unwrap();
-
- let account = get_category_transfers_account(connection);
-
- assert!(account.is_ok());
- let account = account.unwrap();
- assert!(account.is_some());
- let account = account.unwrap();
- assert!(account.name == "Category transfers");
-}
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/actualbudget_state.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/actualbudget_state.rs
index cda63b1..22c1eef 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/actualbudget_state.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/actualbudget_state.rs
@@ -1,12 +1,18 @@
use std::cmp::min;
use actualbudget_models::{
- ActualbudgetAccount, ActualbudgetCategory, ActualbudgetCategoryGroup, ActualbudgetDate, ActualbudgetPayee, ActualbudgetTransaction, ActualbudgetZeroBudget
+ ActualbudgetAccount, ActualbudgetCategory, ActualbudgetCategoryGroup, ActualbudgetDate,
+ ActualbudgetPayee, ActualbudgetTransaction, ActualbudgetZeroBudget,
};
use actualbudget_queries::{
- actualbudget_accounts::get_all_actualbudget_accounts, actualbudget_categories::get_all_actualbudget_categories, actualbudget_category_groups::get_all_actualbudget_category_groups, actualbudget_payees::get_all_actualbudget_payees, actualbudget_transactions::{
+ actualbudget_accounts::get_all_actualbudget_accounts,
+ actualbudget_categories::get_all_actualbudget_categories,
+ actualbudget_category_groups::get_all_actualbudget_category_groups,
+ actualbudget_payees::get_all_actualbudget_payees,
+ actualbudget_transactions::{
get_all_actualbudget_transactions, get_first_actualbudget_transaction_date,
- }, actualbudget_zero_budgets::{get_all_actualbudget_zero_budgets, get_first_zero_budget_date}
+ },
+ actualbudget_zero_budgets::{get_all_actualbudget_zero_budgets, get_first_zero_budget_date},
};
use anyhow::Result;
use diesel::SqliteConnection;
@@ -31,7 +37,9 @@ pub fn read_actualbudget_state(
),
actualbudget_accounts: get_all_actualbudget_accounts(actualbudget_connection)?,
actualbudget_categories: get_all_actualbudget_categories(actualbudget_connection)?,
- actualbudget_category_groups: get_all_actualbudget_category_groups(actualbudget_connection)?,
+ actualbudget_category_groups: get_all_actualbudget_category_groups(
+ actualbudget_connection,
+ )?,
actualbudget_payees: get_all_actualbudget_payees(actualbudget_connection)?,
actualbudget_zero_budgets: get_all_actualbudget_zero_budgets(actualbudget_connection)?,
actualbudget_transactions: get_all_actualbudget_transactions(actualbudget_connection)?,
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/schist_state.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/schist_state.rs
index ca5a15c..f42fd16 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/schist_state.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/schist_state.rs
@@ -1,7 +1,7 @@
use anyhow::Result;
use diesel::SqliteConnection;
use schist_models::{
- Account, AccountTransfer, Bucket, BucketTransfer, DateUtc, Drip, Pipe, Transaction,
+ Account, AccountTransfer, Bucket, BucketTransaction, DateUtc, Drip, Pipe, Transaction,
};
pub struct SchistState {
@@ -12,7 +12,7 @@ pub struct SchistState {
pub budget_drips: Vec<Drip>,
pub transactions: Vec<Transaction>,
pub pipes: Vec<Pipe>,
- pub bucket_transfers: Vec<BucketTransfer>,
+ pub bucket_transactions: Vec<BucketTransaction>,
}
pub fn export_schist_state(
@@ -32,8 +32,8 @@ pub fn export_schist_state(
schist_connection,
)?;
schist_queries::pipes::insert_pipes(&schist_state.pipes, schist_connection)?;
- schist_queries::bucket_transfers::insert_bucket_transfers(
- &schist_state.bucket_transfers,
+ schist_queries::bucket_transactions::insert_bucket_transactions(
+ &schist_state.bucket_transactions,
schist_connection,
)?;
Ok(())
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 a116649..75aa5bb 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
@@ -1,27 +1,30 @@
mod is_account_transfer;
-mod is_bucket_transfer;
+mod is_category_transfer;
mod transform_account_transfers;
mod transform_accounts;
+mod transform_bucket_transactions_from_budgets_not_carried_over;
+mod transform_bucket_transactions_from_category_transfers;
mod transform_categories_to_buckets;
mod transform_date;
mod transform_id;
mod transform_state;
mod transform_transactions;
-mod transform_transactions_to_bucket_transfers;
mod transform_zero_budgets_to_drips;
mod transform_zero_budgets_to_pipes;
use is_account_transfer::is_account_transfer;
use is_account_transfer::is_not_account_transfer;
-use is_bucket_transfer::is_bucket_transfer;
-use is_bucket_transfer::is_not_bucket_transfer;
+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_from_budgets_not_carried_over::transform_bucket_transactions_from_budgets_not_carried_over;
+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_transactions_to_bucket_transfers::transform_transactions_to_bucket_transfers;
+use transform_zero_budgets_to_drips::transform_zero_budgets_to_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/is_bucket_transfer.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/is_category_transfer.rs
index 188697e..75d48b7 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/is_bucket_transfer.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/is_category_transfer.rs
@@ -1,14 +1,14 @@
use actualbudget_models::ActualbudgetTransaction;
-pub fn is_bucket_transfer(
+pub fn is_category_transfer(
transaction: &ActualbudgetTransaction,
transactions: &[ActualbudgetTransaction],
) -> bool {
- is_parent_bucket_transfer(transaction, transactions)
- || is_child_bucket_transfer(transaction, transactions)
+ is_parent_category_transfer(transaction, transactions)
+ || is_child_category_transfer(transaction, transactions)
}
-fn is_parent_bucket_transfer(
+fn is_parent_category_transfer(
transaction: &ActualbudgetTransaction,
transactions: &[ActualbudgetTransaction],
) -> bool {
@@ -24,7 +24,7 @@ fn is_parent_bucket_transfer(
sum_of_children_amounts == 0
}
-fn is_child_bucket_transfer(
+fn is_child_category_transfer(
transaction: &ActualbudgetTransaction,
transactions: &[ActualbudgetTransaction],
) -> bool {
@@ -38,9 +38,9 @@ fn is_parent_and_child(parent: &ActualbudgetTransaction, child: &ActualbudgetTra
parent.is_parent && child.is_child && child.parent_id.clone().is_some_and(|id| id == parent.id)
}
-pub fn is_not_bucket_transfer(
+pub fn is_not_category_transfer(
transaction: &ActualbudgetTransaction,
transactions: &[ActualbudgetTransaction],
) -> bool {
- !is_bucket_transfer(transaction, transactions)
+ !is_category_transfer(transaction, transactions)
}
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs
index 76eaf0c..13f63c0 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_accounts.rs
@@ -1,7 +1,7 @@
use actualbudget_models::{ActualbudgetAccount, ActualbudgetTransaction};
use schist_models::{Account, DateUtc};
-use super::{is_bucket_transfer, transform_id};
+use super::{is_category_transfer, transform_id};
pub fn transform_accounts(
actualbudget_accounts: &[ActualbudgetAccount],
@@ -22,7 +22,7 @@ fn is_empty_account(
actualbudget_transactions.iter().all(|t| {
t.is_starting_balance() && t.amount == 0
|| t.account_id != actualbudget_account.id
- || is_bucket_transfer(t, actualbudget_transactions)
+ || is_category_transfer(t, actualbudget_transactions)
})
}
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_budgets_not_carried_over.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_budgets_not_carried_over.rs
new file mode 100644
index 0000000..1bdeaff
--- /dev/null
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_budgets_not_carried_over.rs
@@ -0,0 +1,350 @@
+use std::collections::HashMap;
+
+use actualbudget_models::{ActualbudgetDate, ActualbudgetTransaction, ActualbudgetZeroBudget};
+use anyhow::Result;
+use itertools::Itertools;
+use schist_models::BucketTransaction;
+use schist_traits::Dateable;
+
+use crate::transform_state::{transform_date, transform_id};
+
+pub fn transform_bucket_transactions_from_budgets_not_carried_over(
+ actualbudget_zero_budgets: &[ActualbudgetZeroBudget],
+ actualbudget_transactions: &[ActualbudgetTransaction],
+) -> Result<Vec<BucketTransaction>> {
+ let budgets_not_carried_over: Vec<&ActualbudgetZeroBudget> = actualbudget_zero_budgets
+ .iter()
+ .filter(|zb| !zb.do_carry_over)
+ .sorted_unstable_by_key(|zb| zb.month)
+ .collect();
+ let mut last_reset_to_zero_per_category_id: HashMap<String, ActualbudgetDate> =
+ HashMap::from_iter(
+ budgets_not_carried_over
+ .iter()
+ .unique_by(|zb| zb.category_id.clone())
+ .map(|zb| {
+ (
+ zb.category_id.clone(),
+ ActualbudgetDate::oldest_valid_date(),
+ )
+ }),
+ );
+
+ let mut bucket_transactions = Vec::new();
+ for budget in budgets_not_carried_over {
+ let last_not_carried_over_month_start: ActualbudgetDate =
+ last_reset_to_zero_per_category_id
+ .get(&budget.category_id)
+ .cloned()
+ .unwrap_or_else(|| ActualbudgetDate::oldest_valid_date());
+ let start_of_next_month = transform_date(&budget.month)?.first_day_in_next_month();
+ let actualbudget_start_of_next_month = ActualbudgetDate::from_ymd(
+ start_of_next_month.year(),
+ start_of_next_month.month(),
+ start_of_next_month.day(),
+ );
+
+ let balance_from_zero_budgets: i32 = actualbudget_zero_budgets
+ .iter()
+ .filter(|zb| {
+ zb.category_id == budget.category_id
+ && zb.month >= last_not_carried_over_month_start
+ && zb.month < actualbudget_start_of_next_month
+ })
+ .map(|zb| zb.amount)
+ .sum();
+ let balance_from_transactions: i32 = actualbudget_transactions
+ .iter()
+ .filter(|t| {
+ t.category_id
+ .as_ref()
+ .is_some_and(|id| *id == budget.category_id)
+ && t.date >= last_not_carried_over_month_start
+ && t.date < actualbudget_start_of_next_month
+ })
+ .map(|t| t.amount)
+ .sum();
+ let balance_at_end_of_month = balance_from_zero_budgets + balance_from_transactions;
+
+ if balance_at_end_of_month < 0 {
+ last_reset_to_zero_per_category_id
+ .insert(budget.category_id.clone(), actualbudget_start_of_next_month);
+ bucket_transactions.push(BucketTransaction {
+ id: transform_id(&budget.id),
+ description: format!("Actualbudget budget <{}> not carried over", budget.id),
+ date: start_of_next_month,
+ amount: -balance_at_end_of_month,
+ bucket_id: transform_id(&budget.category_id),
+ });
+ }
+ }
+
+ Ok(bucket_transactions)
+}
+
+#[cfg(test)]
+mod test {
+ use actualbudget_fakes::{
+ actualbudget_transaction::make_fake_actualbudget_transaction_builder,
+ actualbudget_zero_budget::make_fake_actualbudget_zero_budget_builder,
+ };
+ use actualbudget_models::ActualbudgetDate;
+ use schist_models::{BucketTransaction, DateUtc};
+
+ use crate::transform_state::{
+ transform_bucket_transactions_from_budgets_not_carried_over::transform_bucket_transactions_from_budgets_not_carried_over,
+ transform_id::transform_id,
+ };
+
+ #[test]
+ fn no_resets() {
+ let actualbudget_zero_budgets = [
+ make_fake_actualbudget_zero_budget_builder("zb0")
+ .category_id(String::from("c0"))
+ .amount(100)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202601))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_zero_budget_builder("zb1")
+ .category_id(String::from("c0"))
+ .amount(100)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202602))
+ .build()
+ .unwrap(),
+ ];
+
+ let actualbudget_transactions = [
+ make_fake_actualbudget_transaction_builder("t0")
+ .category_id(Some(String::from("c0")))
+ .amount(-50)
+ .date(ActualbudgetDate::from_ymd(2026, 1, 1))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t0")
+ .category_id(Some(String::from("c0")))
+ .amount(-50)
+ .date(ActualbudgetDate::from_ymd(2026, 1, 31))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t1")
+ .category_id(Some(String::from("c1")))
+ .amount(-101)
+ .date(ActualbudgetDate::from_ymd(2026, 1, 21))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t1")
+ .category_id(Some(String::from("c1")))
+ .amount(-101)
+ .date(ActualbudgetDate::from_ymd(2026, 2, 21))
+ .build()
+ .unwrap(),
+ ];
+
+ let result = transform_bucket_transactions_from_budgets_not_carried_over(
+ &actualbudget_zero_budgets,
+ &actualbudget_transactions,
+ );
+ assert!(result.is_ok());
+ let result = result.unwrap();
+ assert_eq!(Vec::<BucketTransaction>::new(), result);
+ }
+
+ #[test]
+ fn one_reset() {
+ let actualbudget_zero_budgets = [
+ make_fake_actualbudget_zero_budget_builder("zb0")
+ .category_id(String::from("c0"))
+ .amount(100)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202601))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_zero_budget_builder("zb1")
+ .category_id(String::from("c0"))
+ .amount(100)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202602))
+ .build()
+ .unwrap(),
+ ];
+
+ let actualbudget_transactions = [
+ make_fake_actualbudget_transaction_builder("t0")
+ .category_id(Some(String::from("c0")))
+ .amount(-50)
+ .date(ActualbudgetDate::from_ymd(2026, 1, 1))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t0")
+ .category_id(Some(String::from("c0")))
+ .amount(-51)
+ .date(ActualbudgetDate::from_ymd(2026, 1, 31))
+ .build()
+ .unwrap(),
+ ];
+
+ let result = transform_bucket_transactions_from_budgets_not_carried_over(
+ &actualbudget_zero_budgets,
+ &actualbudget_transactions,
+ );
+ assert!(result.is_ok());
+ let result = result.unwrap();
+ assert_eq!(result.len(), 1);
+ let transaction = result.first().unwrap();
+ assert_eq!(transaction.amount, 1);
+ assert_eq!(transaction.bucket_id, transform_id("c0"));
+ assert_eq!(transaction.date, DateUtc::from_ymd(2026, 2, 1).unwrap());
+ }
+
+ #[test]
+ fn two_resets() {
+ let actualbudget_zero_budgets = [
+ make_fake_actualbudget_zero_budget_builder("zb0")
+ .category_id(String::from("c0"))
+ .amount(100)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202401))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_zero_budget_builder("zb1")
+ .category_id(String::from("c0"))
+ .amount(101)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202402))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_zero_budget_builder("zb2")
+ .category_id(String::from("c0"))
+ .amount(102)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202403))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_zero_budget_builder("zb3")
+ .category_id(String::from("c0"))
+ .amount(103)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202404))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_zero_budget_builder("zb4")
+ .category_id(String::from("c0"))
+ .amount(104)
+ .do_carry_over(false)
+ .month(ActualbudgetDate::from_i32(202405))
+ .build()
+ .unwrap(),
+ ];
+
+ let actualbudget_transactions = [
+ make_fake_actualbudget_transaction_builder("t0")
+ .category_id(Some(String::from("c0")))
+ .amount(-50)
+ .date(ActualbudgetDate::from_ymd(2024, 1, 1))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t1")
+ .category_id(Some(String::from("c0")))
+ .amount(-50)
+ .date(ActualbudgetDate::from_ymd(2024, 1, 31))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t2")
+ .category_id(Some(String::from("c0")))
+ .amount(-50)
+ .date(ActualbudgetDate::from_ymd(2024, 2, 1))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t3")
+ .category_id(Some(String::from("c0")))
+ .amount(-53)
+ .date(ActualbudgetDate::from_ymd(2024, 2, 29))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t4")
+ .category_id(Some(String::from("c0")))
+ .amount(-92)
+ .date(ActualbudgetDate::from_ymd(2024, 3, 15))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t5")
+ .category_id(Some(String::from("c0")))
+ .amount(-93)
+ .date(ActualbudgetDate::from_ymd(2024, 4, 15))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t6")
+ .category_id(Some(String::from("c0")))
+ .amount(-127)
+ .date(ActualbudgetDate::from_ymd(2024, 5, 15))
+ .build()
+ .unwrap(),
+ ];
+
+ let result = transform_bucket_transactions_from_budgets_not_carried_over(
+ &actualbudget_zero_budgets,
+ &actualbudget_transactions,
+ );
+ assert!(result.is_ok());
+ let result = result.unwrap();
+ assert_eq!(result.len(), 2);
+
+ let transaction = result.iter().find(|bt| bt.amount == 2);
+ assert!(transaction.is_some());
+ let transaction = transaction.unwrap();
+ assert_eq!(transaction.amount, 2);
+ assert_eq!(transaction.bucket_id, transform_id("c0"));
+ assert_eq!(transaction.date, DateUtc::from_ymd(2024, 3, 1).unwrap());
+
+ let transaction = result.iter().find(|bt| bt.amount == 3);
+ assert!(transaction.is_some());
+ let transaction = transaction.unwrap();
+ assert_eq!(transaction.bucket_id, transform_id("c0"));
+ assert_eq!(transaction.date, DateUtc::from_ymd(2024, 6, 1).unwrap());
+ }
+
+ #[test]
+ fn carried_over() {
+ let actualbudget_zero_budgets = [
+ make_fake_actualbudget_zero_budget_builder("zb0")
+ .category_id(String::from("c0"))
+ .amount(100)
+ .do_carry_over(true)
+ .month(ActualbudgetDate::from_i32(202601))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_zero_budget_builder("zb1")
+ .category_id(String::from("c0"))
+ .amount(100)
+ .do_carry_over(true)
+ .month(ActualbudgetDate::from_i32(202602))
+ .build()
+ .unwrap(),
+ ];
+
+ let actualbudget_transactions = [
+ make_fake_actualbudget_transaction_builder("t0")
+ .category_id(Some(String::from("c0")))
+ .amount(-50)
+ .date(ActualbudgetDate::from_ymd(2026, 1, 1))
+ .build()
+ .unwrap(),
+ make_fake_actualbudget_transaction_builder("t0")
+ .category_id(Some(String::from("c0")))
+ .amount(-51)
+ .date(ActualbudgetDate::from_ymd(2026, 1, 31))
+ .build()
+ .unwrap(),
+ ];
+
+ let result = transform_bucket_transactions_from_budgets_not_carried_over(
+ &actualbudget_zero_budgets,
+ &actualbudget_transactions,
+ );
+ assert!(result.is_ok());
+ let result = result.unwrap();
+ assert_eq!(result.len(), 0);
+ }
+}
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..4dce8fa
--- /dev/null
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_bucket_transactions_from_category_transfers.rs
@@ -0,0 +1,452 @@
+use actualbudget_models::ActualbudgetTransaction;
+use anyhow::{bail, Context, Result};
+use schist_models::BucketTransaction;
+
+use crate::transform_state::transform_date;
+
+use super::{is_category_transfer, transform_id};
+
+pub fn transform_bucket_transactions_from_category_transfers(
+ actualbudget_transactions: &[ActualbudgetTransaction],
+) -> Result<Vec<BucketTransaction>> {
+ let actualbudget_category_transfers = actualbudget_transactions
+ .iter()
+ .filter(|transaction| is_category_transfer(transaction, actualbudget_transactions));
+ 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();
+
+ 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<Vec<BucketTransaction>> {
+ 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<Vec<BucketTransaction>> {
+ 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<ActualbudgetTransaction> = 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<ActualbudgetTransaction> = 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 amount: i32 = deposit.amount.try_into().unwrap();
+ let date = transform_date(&actualbudget_parent_category_transfer.date)?;
+
+ Ok(vec![
+ BucketTransaction {
+ id: transform_id(&deposit.id),
+ description: actualbudget_parent_category_transfer
+ .notes
+ .clone()
+ .unwrap_or_else(String::new),
+ amount: -amount,
+ date,
+ bucket_id: withdrawal_bucket_id,
+ },
+ BucketTransaction {
+ id: transform_id(&withdrawal.id),
+ description: actualbudget_parent_category_transfer
+ .notes
+ .clone()
+ .unwrap_or_else(String::new),
+ amount,
+ date,
+ bucket_id: deposit_id,
+ },
+ ])
+}
+
+#[cfg(test)]
+mod test {
+ use actualbudget_fakes::actualbudget_transaction::make_fake_actualbudget_transaction_builder;
+ use actualbudget_models::{
+ ActualbudgetDate, ActualbudgetTransaction, ActualbudgetTransactionBuilder,
+ };
+ use schist_models::{BucketTransaction, DateUtc};
+
+ use crate::transform_state::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_builder("0")
+ .date(ActualbudgetDate::from_ymd(2026, 01, 16))
+ .build()
+ .unwrap(),
+ 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);
+ assert_eq!(
+ DateUtc::from_ymd(2026, 1, 16).unwrap(),
+ bucket_withdrawal.date
+ );
+
+ 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);
+ assert_eq!(DateUtc::from_ymd(2026, 1, 16).unwrap(), bucket_deposit.date);
+ }
+
+ #[test]
+ fn when_many_transactions_then_transforms() {
+ let actualbudget_transactions = [
+ make_fake_parent_transaction_builder("0")
+ .date(ActualbudgetDate::from_ymd(2026, 1, 16))
+ .build()
+ .unwrap(),
+ 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_builder("3")
+ .date(ActualbudgetDate::from_ymd(2026, 1, 21))
+ .build()
+ .unwrap(),
+ 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);
+ assert_eq!(
+ DateUtc::from_ymd(2026, 1, 16).unwrap(),
+ bucket_withdrawal_0.date
+ );
+
+ 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);
+ assert_eq!(
+ DateUtc::from_ymd(2026, 1, 16).unwrap(),
+ bucket_deposit_0.date
+ );
+
+ 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);
+ assert_eq!(
+ DateUtc::from_ymd(2026, 1, 21).unwrap(),
+ bucket_withdrawal_1.date
+ );
+
+ 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);
+ assert_eq!(
+ DateUtc::from_ymd(2026, 1, 21).unwrap(),
+ bucket_deposit_1.date
+ );
+ }
+
+ #[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 835ceff..144913a 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,16 @@
use anyhow::{Context, Result};
-use schist_models::{Account, Bucket, BucketTransfer, Pipe, Transaction};
+use schist_models::{Account, Bucket, BucketTransaction, Pipe, Transaction};
-use crate::{actualbudget_state::ActualbudgetState, schist_state::SchistState};
+use crate::{
+ actualbudget_state::ActualbudgetState, schist_state::SchistState,
+ transform_state::transform_bucket_transactions_from_budgets_not_carried_over,
+};
use super::{
- transform_account_transfers, transform_accounts, transform_categories_to_buckets,
- transform_date, transform_transactions, transform_transactions_to_bucket_transfers,
- transform_zero_budgets_to_drips::transform_budget_drips, transform_zero_budgets_to_pipes,
+ transform_account_transfers, transform_accounts,
+ transform_bucket_transactions_from_category_transfers, transform_categories_to_buckets,
+ transform_date, transform_transactions, transform_zero_budgets_to_drips,
+ transform_zero_budgets_to_pipes,
};
pub fn transform_state(actualbudget_state: ActualbudgetState) -> Result<SchistState> {
@@ -27,7 +31,8 @@ fn transform_state_without_context(actualbudget_state: ActualbudgetState) -> Res
&actualbudget_state.actualbudget_transactions,
&first_activity_date,
);
- let budget_drips = transform_budget_drips(&actualbudget_state.actualbudget_zero_budgets)?;
+ let budget_drips =
+ transform_zero_budgets_to_drips(&actualbudget_state.actualbudget_zero_budgets)?;
let buckets: Vec<Bucket> = transform_categories_to_buckets(
&actualbudget_state.actualbudget_categories,
&actualbudget_state.actualbudget_category_groups,
@@ -36,8 +41,16 @@ fn transform_state_without_context(actualbudget_state: ActualbudgetState) -> Res
);
let pipes: Vec<Pipe> =
transform_zero_budgets_to_pipes(&actualbudget_state.actualbudget_zero_budgets)?;
- let bucket_transfers: Vec<BucketTransfer> =
- transform_transactions_to_bucket_transfers(&remaining_transactions)?;
+ let bucket_transactions: Vec<BucketTransaction> = vec![
+ transform_bucket_transactions_from_category_transfers(&remaining_transactions)?,
+ transform_bucket_transactions_from_budgets_not_carried_over(
+ &actualbudget_state.actualbudget_zero_budgets,
+ &actualbudget_state.actualbudget_transactions,
+ )?,
+ ]
+ .into_iter()
+ .flatten()
+ .collect();
Ok(SchistState {
first_transaction_date: first_activity_date,
@@ -47,6 +60,6 @@ fn transform_state_without_context(actualbudget_state: ActualbudgetState) -> Res
budget_drips,
pipes: pipes,
transactions,
- bucket_transfers,
+ bucket_transactions,
})
}
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs
index 11765be..dce07af 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions.rs
@@ -2,7 +2,7 @@ use actualbudget_models::{ActualbudgetPayee, ActualbudgetTransaction};
use anyhow::{Context, Result};
use schist_models::Transaction;
-use super::{is_not_bucket_transfer, transform_date, transform_id};
+use super::{is_not_category_transfer, transform_date, transform_id};
pub fn transform_transactions(
actualbudget_transactions: &[ActualbudgetTransaction],
@@ -21,7 +21,7 @@ fn transform_transactions_without_context(
let actualbudget_transactions = actualbudget_transactions
.iter()
.filter(|t| !t.is_starting_balance())
- .filter(|t| is_not_bucket_transfer(t, actualbudget_transactions));
+ .filter(|t| is_not_category_transfer(t, actualbudget_transactions));
let parent_actualbudget_transactions: Vec<&ActualbudgetTransaction> = actualbudget_transactions
.clone()
.filter(|t| t.is_parent)
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions_to_bucket_transfers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions_to_bucket_transfers.rs
deleted file mode 100644
index 51545c3..0000000
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_transactions_to_bucket_transfers.rs
+++ /dev/null
@@ -1,383 +0,0 @@
-use actualbudget_models::ActualbudgetTransaction;
-use anyhow::{bail, Context, Result};
-use itertools::Itertools;
-use schist_models::BucketTransfer;
-
-use super::transform_id;
-
-pub fn transform_transactions_to_bucket_transfers(
- actualbudget_transactions: &[ActualbudgetTransaction],
-) -> Result<Vec<BucketTransfer>> {
- let children: Vec<&ActualbudgetTransaction> = actualbudget_transactions
- .into_iter()
- .filter(|&t| t.is_child)
- .collect();
- let parent_children_pairs: Vec<(&ActualbudgetTransaction, Vec<&ActualbudgetTransaction>)> =
- actualbudget_transactions
- .iter()
- .filter(|t| t.is_parent)
- .map(|parent| {
- (
- parent,
- children
- .clone()
- .into_iter()
- .filter(|&c| c.parent_id.clone().is_some_and(|id| id == parent.id))
- .collect::<Vec<&ActualbudgetTransaction>>(),
- )
- })
- .map(|(parent, children)| {
- let children_total_amount: i32 = children.iter().map(|child| child.amount).sum();
- if parent.amount != children_total_amount {
- bail!("parent transaction {} has amount: {}, but its {} children add up to a total amount of: {}",
- parent.id,
- parent.amount,
- children.len(),
- children_total_amount);
- }
- Ok((parent, children))
- })
- .filter(|result|
- match result {
- Ok((parent, _children)) => parent.amount == 0,
- Err(_) => true,
- })
- .try_collect()?;
-
- Ok(parent_children_pairs
- .iter()
- .map(|(parent, children)| transform_category_transfer(parent, children))
- .filter_ok(Option::is_some)
- .map_ok(Option::unwrap)
- .collect::<Result<Vec<BucketTransfer>>>()
- .with_context(|| "failed to transform category transfers")?)
-}
-
-fn transform_category_transfer(
- actualbudget_parent_category_transfer: &ActualbudgetTransaction,
- actualbudget_child_category_transfers: &[&ActualbudgetTransaction],
-) -> Result<Option<BucketTransfer>> {
- 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<BucketTransfer>> {
- let from_transfers: Vec<&ActualbudgetTransaction> = actualbudget_child_category_transfers
- .into_iter()
- .cloned()
- .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
- .iter()
- .filter(|&t| t.amount > 0)
- .cloned()
- .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(BucketTransfer {
- id: transform_id(&actualbudget_parent_category_transfer.id),
- description: actualbudget_parent_category_transfer
- .notes
- .clone()
- .unwrap_or_else(String::new),
- amount: to_transfer.amount.try_into().unwrap(),
- from_bucket_id: from_category_id,
- to_bucket_id: to_category_id,
- }))
-}
-
-#[cfg(test)]
-mod test {
- use actualbudget_fakes::actualbudget_transaction::make_fake_actualbudget_transaction_builder;
- use actualbudget_models::{ActualbudgetTransaction, ActualbudgetTransactionBuilder};
- use schist_models::BucketTransfer;
-
- use crate::transform_state::transform_id::transform_id;
-
- use super::transform_transactions_to_bucket_transfers;
-
- #[test]
- fn when_one_category_transfer_then_transforms() {
- 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_transactions_to_bucket_transfers(&actualbudget_transactions);
-
- assert!(result.is_ok());
- let bucket_transfers = result.unwrap();
- assert_eq!(1, bucket_transfers.len());
- let bucket_transfer = bucket_transfers.get(0).unwrap();
- assert_eq!(100, bucket_transfer.amount);
- assert_eq!("I was a parent transaction", bucket_transfer.description);
- assert_eq!(transform_id("0"), bucket_transfer.from_bucket_id);
- assert_eq!(transform_id("1"), bucket_transfer.to_bucket_id);
- }
-
- #[test]
- fn when_many_transactions_then_transforms() {
- 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_transactions_to_bucket_transfers(&actualbudget_transactions);
-
- assert!(result.is_ok());
- let bucket_transfers = result.unwrap();
- assert_eq!(2, bucket_transfers.len());
-
- let bucket_transfer_0: Vec<&BucketTransfer> = bucket_transfers
- .iter()
- .filter(|ct| ct.amount == 100)
- .collect();
- assert_eq!(1, bucket_transfer_0.len());
- let bucket_transfer_0 = bucket_transfer_0.get(0).unwrap();
- assert_eq!(transform_id("0"), bucket_transfer_0.from_bucket_id);
- assert_eq!(transform_id("1"), bucket_transfer_0.to_bucket_id);
-
- let bucket_transfer_1: Vec<&BucketTransfer> = bucket_transfers
- .iter()
- .filter(|ct| ct.amount == 200)
- .collect();
- assert_eq!(1, bucket_transfer_1.len());
- let bucket_transfer_1 = bucket_transfer_1.get(0).unwrap();
- assert_eq!(transform_id("1"), bucket_transfer_1.from_bucket_id);
- assert_eq!(transform_id("0"), bucket_transfer_1.to_bucket_id);
- }
-
- #[test]
- fn when_multiple_child_from_transactions_then_err() {
- 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_transactions_to_bucket_transfers(&actualbudget_transactions);
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_multiple_child_to_transactions_then_err() {
- 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_transactions_to_bucket_transfers(&actualbudget_transactions);
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_no_child_from_transactions_then_err() {
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_to_child_transaction("1", "0"),
- ];
-
- let result = transform_transactions_to_bucket_transfers(&actualbudget_transactions);
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_no_child_to_transactions_then_err() {
- let actualbudget_transactions = [
- make_fake_parent_transaction("0"),
- make_fake_from_child_transaction("1", "0"),
- ];
-
- let result = transform_transactions_to_bucket_transfers(&actualbudget_transactions);
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_child_amounts_dont_add_up_to_zero_then_err() {
- 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_transactions_to_bucket_transfers(&actualbudget_transactions);
-
- assert!(result.is_err());
- }
-
- #[test]
- fn when_child_category_transfer_has_no_category_then_err() {
- 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_transactions_to_bucket_transfers(&actualbudget_transactions_0);
- let result_1 = transform_transactions_to_bucket_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_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/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_zero_budgets_to_drips.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_zero_budgets_to_drips.rs
index bfaef31..b5e6734 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_zero_budgets_to_drips.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_zero_budgets_to_drips.rs
@@ -8,7 +8,7 @@ use schist_traits::Dateable;
use super::{transform_date::transform_date, transform_id::transform_id};
-pub fn transform_budget_drips(
+pub fn transform_zero_budgets_to_drips(
actualbudget_zero_budgets: &[ActualbudgetZeroBudget],
) -> Result<Vec<Drip>> {
let non_zero_actualbudget_zero_budgets: Vec<ActualbudgetZeroBudget> = actualbudget_zero_budgets
@@ -133,7 +133,7 @@ mod test {
use crate::transform_state::transform_id::transform_id;
- use super::transform_budget_drips;
+ use super::transform_zero_budgets_to_drips;
mod map_actualbudget_zero_budget_to_budget_drips {
use actualbudget_fakes::actualbudget_zero_budget::make_fake_actualbudget_zero_budget_builder;
@@ -195,7 +195,7 @@ mod test {
#[test]
fn when_no_zero_budgets_then_no_budget_drips() {
let actualbudget_zero_budgets = [];
- let result = transform_budget_drips(&actualbudget_zero_budgets);
+ let result = transform_zero_budgets_to_drips(&actualbudget_zero_budgets);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
@@ -209,7 +209,7 @@ mod test {
.build()
.unwrap()];
- let result = transform_budget_drips(&actualbudget_zero_budgets);
+ let result = transform_zero_budgets_to_drips(&actualbudget_zero_budgets);
assert!(result.is_ok());
let mut budget_drips = result.unwrap();
@@ -233,7 +233,7 @@ mod test {
.build()
.unwrap()];
- let result = transform_budget_drips(&actualbudget_zero_budgets);
+ let result = transform_zero_budgets_to_drips(&actualbudget_zero_budgets);
assert!(result.is_ok());
let budget_drips = result.unwrap();
@@ -278,7 +278,7 @@ mod test {
.unwrap(),
];
- let result = transform_budget_drips(&actualbudget_zero_budgets);
+ let result = transform_zero_budgets_to_drips(&actualbudget_zero_budgets);
let category_0_id = transform_id("0");
let category_1_id = transform_id("1");
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_transaction_matchers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_transaction_matchers.rs
new file mode 100644
index 0000000..95bb54f
--- /dev/null
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_transaction_matchers.rs
@@ -0,0 +1,66 @@
+use schist_models::{Bucket, BucketTransaction};
+use schist_traits::Dateable;
+
+use super::bucket_names::{BUCKET_NAME_GENERAL, BUCKET_NAME_SAVINGS};
+
+pub fn get_bucket_transaction_matchers() -> Vec<Box<dyn Fn(&BucketTransaction, &[Bucket]) -> bool>>
+{
+ vec![
+ Box::new(|ct, c| matches_withdrawal_120_savings_23_oct_2024(ct, c)),
+ Box::new(|ct, c| matches_deposit_120_general_23_oct_2024(ct, c)),
+ ]
+}
+
+fn matches_withdrawal_120_savings_23_oct_2024(
+ bucket_transactions: &BucketTransaction,
+ buckets: &[Bucket],
+) -> bool {
+ let savings_bucket = buckets
+ .iter()
+ .find(|c| c.name == BUCKET_NAME_SAVINGS)
+ .expect("failed to find savings bucket");
+
+ match bucket_transactions {
+ BucketTransaction {
+ id: _id,
+ description,
+ amount: -120_00,
+ date,
+ bucket_id,
+ } => {
+ *bucket_id == savings_bucket.id
+ && description.as_str() == ""
+ && date.year() == 2024
+ && date.month() == 10
+ && date.day() == 23
+ }
+ _ => false,
+ }
+}
+
+fn matches_deposit_120_general_23_oct_2024(
+ bucket_transaction: &BucketTransaction,
+ buckets: &[Bucket],
+) -> bool {
+ let general_bucket = buckets
+ .iter()
+ .find(|c| c.name == BUCKET_NAME_GENERAL)
+ .expect("failed to find general bucket");
+
+ match bucket_transaction {
+ BucketTransaction {
+ id: _id,
+ description,
+ amount: 120_00,
+ date,
+ bucket_id,
+ } => {
+ *bucket_id == general_bucket.id
+ && description.as_str() == ""
+ && date.year() == 2024
+ && date.month() == 10
+ && date.day() == 23
+ }
+ _ => false,
+ }
+}
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_transfer_matchers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_transfer_matchers.rs
deleted file mode 100644
index c31b9bc..0000000
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_transfer_matchers.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-use schist_models::{Bucket, BucketTransfer};
-
-use super::bucket_names::{BUCKET_NAME_GENERAL, BUCKET_NAME_SAVINGS};
-
-pub fn get_bucket_transfer_matchers() -> Vec<Box<dyn Fn(&BucketTransfer, &[Bucket]) -> bool>> {
- vec![Box::new(|ct, c| {
- matches_120_savings_to_general_23_oct_2024(ct, c)
- })]
-}
-
-fn matches_120_savings_to_general_23_oct_2024(
- bucket_transfer: &BucketTransfer,
- buckets: &[Bucket],
-) -> bool {
- let savings_bucket = buckets
- .iter()
- .find(|c| c.name == BUCKET_NAME_SAVINGS)
- .expect("failed to find savings bucket");
-
- let general_bucket = buckets
- .iter()
- .find(|c| c.name == BUCKET_NAME_GENERAL)
- .expect("failed to find general bucket");
-
- match bucket_transfer {
- BucketTransfer {
- id: _id,
- description,
- amount: 120_00,
- from_bucket_id,
- to_bucket_id,
- } => {
- *from_bucket_id == savings_bucket.id
- && *to_bucket_id == general_bucket.id
- && description.as_str() == ""
- }
- _ => false,
- }
-}
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/drip_matchers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/drip_matchers.rs
index 629a04b..ccb1715 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/drip_matchers.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/drip_matchers.rs
@@ -33,7 +33,7 @@ pub fn assert_drip_matches(drip: &Drip, buckets: &[Bucket]) {
.as_str(),
);
- let expected_quantity = match (bucket_name.as_str(), drip.date.month(), drip.date.day()) {
+ let expected_amount = match (bucket_name.as_str(), drip.date.month(), drip.date.day()) {
(BUCKET_NAME_FOOD, 8 | 10, 1) => 6_50,
(BUCKET_NAME_FOOD, 8 | 10, _) => 6_45,
(BUCKET_NAME_FOOD, 9 | 11, 1) => 6_86,
@@ -70,8 +70,8 @@ pub fn assert_drip_matches(drip: &Drip, buckets: &[Bucket]) {
};
assert_eq!(
- expected_quantity, drip.amount,
- "drip {:?} had unexpected quantity",
+ expected_amount, drip.amount,
+ "drip {:?} had unexpected amount",
drip,
);
}
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/mod.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/mod.rs
index 82d7870..1dd718c 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/mod.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/mod.rs
@@ -3,7 +3,7 @@ mod account_names;
mod account_transfer_matchers;
mod bucket_matchers;
mod bucket_names;
-mod bucket_transfer_matchers;
+mod bucket_transaction_matchers;
mod db_url;
mod drip_matchers;
mod pipe_matchers;
@@ -15,7 +15,7 @@ pub use account_names::ACCOUNT_NAMES_DICT;
pub use account_transfer_matchers::get_account_transfer_matchers;
pub use bucket_matchers::get_bucket_matchers;
pub use bucket_names::BUCKET_NAMES_DICT;
-pub use bucket_transfer_matchers::get_bucket_transfer_matchers;
+pub use bucket_transaction_matchers::get_bucket_transaction_matchers;
pub use db_url::TEST_IN_DB_URL;
pub use db_url::TEST_OUT_DB_URL;
pub use drip_matchers::assert_drip_matches;
diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/run.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/run.rs
index 04c21ca..1232016 100644
--- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/run.rs
+++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/run.rs
@@ -3,15 +3,15 @@ pub mod common;
use actualbudget_to_schist_transformer::run::run;
use common::{
assert_drip_matches, get_account_matchers, get_account_transfer_matchers, get_bucket_matchers,
- get_bucket_transfer_matchers, get_num_expected_drips, get_transaction_matchers, TestContext,
+ get_bucket_transaction_matchers, get_num_expected_drips, get_transaction_matchers, TestContext,
TEST_IN_DB_URL, TEST_OUT_DB_URL,
};
use diesel::{Connection, SqliteConnection};
use schist_queries::{
account_transfers::get_all_account_transfers, accounts::get_all_accounts,
- bucket_transfers::get_all_bucket_transfers, buckets::get_all_buckets, drips::get_all_drips,
- pipes::get_all_pipes, transactions::get_all_transactions,
+ bucket_transactions::get_all_bucket_transactions, buckets::get_all_buckets,
+ drips::get_all_drips, pipes::get_all_pipes, transactions::get_all_transactions,
};
use crate::common::get_pipe_matchers;
@@ -25,7 +25,7 @@ fn given_test_database_when_run_then_exports_expected_schist_state() {
let account_transfers = get_all_account_transfers(connection).unwrap();
let drips = get_all_drips(connection).unwrap();
let buckets = get_all_buckets(connection).unwrap();
- let bucket_transfers = get_all_bucket_transfers(connection).unwrap();
+ let bucket_transactions = get_all_bucket_transactions(connection).unwrap();
let pipes = get_all_pipes(connection).unwrap();
let transactions = get_all_transactions(connection).unwrap();
@@ -66,15 +66,15 @@ fn given_test_database_when_run_then_exports_expected_schist_state() {
);
}
- let bucket_transfer_matchers = get_bucket_transfer_matchers();
- assert_eq!(bucket_transfer_matchers.len(), bucket_transfers.len());
- for bucket_transfer in bucket_transfers {
+ let bucket_transaction_matchers = get_bucket_transaction_matchers();
+ assert_eq!(bucket_transaction_matchers.len(), bucket_transactions.len());
+ for bucket_transaction in bucket_transactions {
assert!(
- bucket_transfer_matchers
+ bucket_transaction_matchers
.iter()
- .any(|m| m(&bucket_transfer, &buckets)),
- "no match found for bucket_transfer {:?}",
- bucket_transfer,
+ .any(|m| m(&bucket_transaction, &buckets)),
+ "no match found for bucket_transaction {:?}",
+ bucket_transaction,
);
}