diff options
| author | Joe Carstairs <me@joeac.net> | 2025-08-15 16:02:40 +0000 |
|---|---|---|
| committer | joeac <me@joeac.net> | 2025-08-15 16:02:40 +0000 |
| commit | 703496a49315f7b67442abf0e0df3c41d3605d3b (patch) | |
| tree | 8e92f3842710162f3b26e910eb931950c1049f07 | |
| parent | 8d714f760bd0349560d464583a151a04a19de1c9 (diff) | |
task-012 (#2)
Co-authored-by: Joe Carstairs <jcarstairs@scottlogic.com>
Reviewed-on: https://git.joeac.net/joeac/schist/pulls/2
Co-authored-by: Joe Carstairs <me@joeac.net>
Co-committed-by: Joe Carstairs <me@joeac.net>
73 files changed, 1618 insertions, 1494 deletions
@@ -3,3 +3,4 @@ target *.keep .zed .vscode +**/*.sqlite @@ -132,7 +132,7 @@ dependencies = [ "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -483,7 +483,7 @@ dependencies = [ "polling", "rustix 0.38.44", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -583,7 +583,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c" dependencies = [ - "thiserror", + "thiserror 1.0.69", "x11rb", ] @@ -834,7 +834,7 @@ checksum = "2a76fa97167fa740dcdbfe18e8895601e1bc36525f09b044e00916e717c03a3c" dependencies = [ "dconf_rs", "detect-desktop-environment", - "dirs", + "dirs 4.0.0", "objc", "rust-ini", "web-sys", @@ -989,7 +989,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", ] [[package]] @@ -999,11 +1008,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] [[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.60.2", +] + +[[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1497,7 +1518,7 @@ checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" dependencies = [ "log", "presser", - "thiserror", + "thiserror 1.0.69", "winapi", "windows", ] @@ -1577,7 +1598,7 @@ dependencies = [ "com", "libc", "libloading 0.8.8", - "thiserror", + "thiserror 1.0.69", "widestring", "winapi", ] @@ -1641,7 +1662,7 @@ dependencies = [ "iced_renderer", "iced_widget", "iced_winit", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1660,7 +1681,7 @@ dependencies = [ "palette", "rustc-hash 2.1.1", "smol_str", - "thiserror", + "thiserror 1.0.69", "web-time", ] @@ -1707,7 +1728,7 @@ dependencies = [ "once_cell", "raw-window-handle", "rustc-hash 2.1.1", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", ] @@ -1721,7 +1742,7 @@ dependencies = [ "iced_tiny_skia", "iced_wgpu", "log", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1734,7 +1755,7 @@ dependencies = [ "iced_core", "iced_futures", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1769,7 +1790,7 @@ dependencies = [ "log", "once_cell", "rustc-hash 2.1.1", - "thiserror", + "thiserror 1.0.69", "wgpu", ] @@ -1784,7 +1805,7 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 2.1.1", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", ] @@ -1799,7 +1820,7 @@ dependencies = [ "iced_runtime", "log", "rustc-hash 2.1.1", - "thiserror", + "thiserror 1.0.69", "tracing", "wasm-bindgen-futures", "web-sys", @@ -1859,7 +1880,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -2106,7 +2127,7 @@ dependencies = [ "rustc-hash 1.1.0", "spirv", "termcolor", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -2122,7 +2143,7 @@ dependencies = [ "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2447,6 +2468,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] name = "orbclient" version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2874,7 +2901,18 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.14", ] [[package]] @@ -2981,11 +3019,15 @@ version = "0.1.0" dependencies = [ "anyhow", "diesel", + "diesel_migrations", + "dirs 6.0.0", "iced", "itertools", + "libsqlite3-sys", "schist_fakes", "schist_models", "schist_queries", + "schist_schema", ] [[package]] @@ -3191,7 +3233,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -3357,7 +3399,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +dependencies = [ + "thiserror-impl 2.0.14", ] [[package]] @@ -3372,6 +3423,17 @@ dependencies = [ ] [[package]] +name = "thiserror-impl" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3935,7 +3997,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror", + "thiserror 1.0.69", "web-sys", "wgpu-hal", "wgpu-types", @@ -3979,7 +4041,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", "wgpu-types", @@ -4045,7 +4107,7 @@ dependencies = [ "clipboard_wayland", "clipboard_x11", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -20,6 +20,7 @@ chrono = { version = "0.4.38" } derive_builder = { version = "0.20.2" } diesel = { version = "2.2.0" } diesel_migrations = "2.2.0" +dirs = "6.0" dotenvy = "0.15.7" iced = "0.13.1" itertools = "0.13.0" diff --git a/schist_core/schist_models/diesel.toml b/diesel.toml index 2b8ebd8..07f4552 100644 --- a/schist_core/schist_models/diesel.toml +++ b/diesel.toml @@ -2,8 +2,8 @@ # see https://diesel.rs/guides/configuring-diesel-cli [print_schema] -file = "src/schema.rs" +file = "schist_core/schist_models/src/schema.rs" custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] [migrations_directory] -dir = "/home/joeac/src/schist/rust/schist_models/migrations" +dir = "schist_core/schist_models/migrations" diff --git a/requirements/kanban.md b/requirements/kanban.md index 4f0ebde..95a1994 100644 --- a/requirements/kanban.md +++ b/requirements/kanban.md @@ -4,7 +4,6 @@ | -------- | -------- | -------- | | | | epic-000 | | ======== | ======== | ======== | -| | task-012 | | | | task-001 | | | | task-064 | | | | task-072 | | diff --git a/requirements/tasks/epic-000.md b/requirements/tasks/epic-000.md index 7b6e21c..239c612 100644 --- a/requirements/tasks/epic-000.md +++ b/requirements/tasks/epic-000.md @@ -3,13 +3,13 @@ For this Epic, there should be a desktop GUI with basic functionality to meet the most important user needs. -Status: not ready +Status: in progress ## Ordered backlog - [x] task-010 (hello, world) - [x] task-073 (UI layout & nav) -- [ ] task-012 (SQLite) +- [x] task-012 (SQLite) - [ ] task-001 (inserting transactions) - [ ] task-064 (deleting transactions) - [ ] task-072 (import from Actualbudget) diff --git a/requirements/tasks/task-012.md b/requirements/tasks/task-012.md index e45c74b..13e3b88 100644 --- a/requirements/tasks/task-012.md +++ b/requirements/tasks/task-012.md @@ -7,6 +7,8 @@ the pattern is ready to be used in other tasks. As this blocks using actual domain entities, then, a good place to start would be storing a database schema version number. +Status: done + Source: ard-001 Epic: epic-000 diff --git a/schist_core/schist_fakes/src/account_transfer.rs b/schist_core/schist_fakes/src/account_transfer.rs index f762c2f..703d7ce 100644 --- a/schist_core/schist_fakes/src/account_transfer.rs +++ b/schist_core/schist_fakes/src/account_transfer.rs @@ -1,17 +1,20 @@ -use schist_models::{date_utc::DateUtc, account_transfer::{AccountTransfer, AccountTransferBuilder}}; +use schist_models::{ + account_transfer::{AccountTransfer, AccountTransferBuilder}, + date_utc::DateUtc, +}; use schist_traits::nowlike::Nowlike; pub fn make_fake_account_transfer(id: i32) -> AccountTransfer { make_fake_account_transfer_builder(id).build().unwrap() } -pub fn make_fake_account_transfer_builder(id: i32) -> AccountTransferBuilder { +pub fn make_fake_account_transfer_builder(id: i32) -> AccountTransferBuilder { let mut builder = AccountTransferBuilder::default(); builder .id(id) .date(DateUtc::now()) .description(String::from("Fake account transfer")) - .quantity(0) + .amount(0) .from_account_id(0) .to_account_id(1); builder diff --git a/schist_core/schist_fakes/src/bucket.rs b/schist_core/schist_fakes/src/bucket.rs index 1b447b2..fe03a83 100644 --- a/schist_core/schist_fakes/src/bucket.rs +++ b/schist_core/schist_fakes/src/bucket.rs @@ -1,9 +1,4 @@ -use schist_models::{ - bucket::{Bucket, BucketBuilder}, - budget_period_unit::BudgetPeriodUnit, - date_utc::DateUtc, -}; -use schist_traits::nowlike::Nowlike; +use schist_models::bucket::{Bucket, BucketBuilder}; pub fn make_fake_bucket(id: i32) -> Bucket { make_fake_bucket_builder(id).build().unwrap() @@ -14,11 +9,8 @@ pub fn make_fake_bucket_builder(id: i32) -> BucketBuilder { builder .id(id) .name(String::from("Fake bucket")) - .balance(0) - .balance_date(DateUtc::now()) - .budget_period(0) - .budget_period_unit(BudgetPeriodUnit::Day) - .budget_quantity(0) + .balance(None) + .balance_cache_key(None) .group(String::from("Fake group")); builder } diff --git a/schist_core/schist_fakes/src/bucket_transfer.rs b/schist_core/schist_fakes/src/bucket_transfer.rs index 9bc1f80..bf5e71b 100644 --- a/schist_core/schist_fakes/src/bucket_transfer.rs +++ b/schist_core/schist_fakes/src/bucket_transfer.rs @@ -9,7 +9,7 @@ pub fn make_fake_bucket_transfer_builder(id: i32) -> BucketTransferBuilder { builder .id(id) .description(String::from("Fake bucket transfer")) - .quantity(0) + .amount(0) .from_bucket_id(0) .to_bucket_id(1); builder diff --git a/schist_core/schist_fakes/src/budget_drip.rs b/schist_core/schist_fakes/src/budget_drip.rs deleted file mode 100644 index 498dd76..0000000 --- a/schist_core/schist_fakes/src/budget_drip.rs +++ /dev/null @@ -1,16 +0,0 @@ -use schist_models::{budget_drip::{BudgetDrip, BudgetDripBuilder}, date_utc::DateUtc}; -use schist_traits::nowlike::Nowlike; - -pub fn make_fake_budget_drip(id: i32) -> BudgetDrip { - make_fake_budget_drip_builder(id).build().unwrap() -} - -pub fn make_fake_budget_drip_builder(id: i32) -> BudgetDripBuilder { - let mut builder = BudgetDripBuilder::default(); - builder - .id(id) - .bucket_id(0) - .date(DateUtc::now()) - .quantity(0); - builder -} diff --git a/schist_core/schist_fakes/src/drip.rs b/schist_core/schist_fakes/src/drip.rs new file mode 100644 index 0000000..88f0b05 --- /dev/null +++ b/schist_core/schist_fakes/src/drip.rs @@ -0,0 +1,15 @@ +use schist_models::{ + date_utc::DateUtc, + drip::{Drip, DripBuilder}, +}; +use schist_traits::nowlike::Nowlike; + +pub fn make_fake_drip(id: i32) -> Drip { + make_fake_drip_builder(id).build().unwrap() +} + +pub fn make_fake_drip_builder(id: i32) -> DripBuilder { + let mut builder = DripBuilder::default(); + builder.id(id).amount(0).bucket_id(0).date(DateUtc::now()); + builder +} diff --git a/schist_core/schist_fakes/src/lib.rs b/schist_core/schist_fakes/src/lib.rs index ba88fb5..0e4cbb4 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 budget_drip; pub mod bucket_transfer; +pub mod drip; +pub mod pipe; pub mod transaction; -pub mod transaction_categorisation; diff --git a/schist_core/schist_fakes/src/pipe.rs b/schist_core/schist_fakes/src/pipe.rs new file mode 100644 index 0000000..6e96401 --- /dev/null +++ b/schist_core/schist_fakes/src/pipe.rs @@ -0,0 +1,21 @@ +use schist_models::{ + budget_period_unit::BudgetPeriodUnit, + date_utc::DateUtc, + pipe::{Pipe, PipeBuilder}, +}; +use schist_traits::nowlike::Nowlike; + +pub fn make_fake_pipe(id: i32) -> Pipe { + make_fake_pipe_builder(id).build().unwrap() +} + +pub fn make_fake_pipe_builder(id: i32) -> PipeBuilder { + let mut builder = PipeBuilder::default(); + builder + .id(id) + .amount(0) + .bucket_id(0) + .period(BudgetPeriodUnit::Day) + .start_date(DateUtc::now()); + builder +} diff --git a/schist_core/schist_fakes/src/transaction.rs b/schist_core/schist_fakes/src/transaction.rs index cee7e00..bce02e5 100644 --- a/schist_core/schist_fakes/src/transaction.rs +++ b/schist_core/schist_fakes/src/transaction.rs @@ -1,18 +1,23 @@ -use schist_models::{date_utc::DateUtc, transaction::{Transaction, TransactionBuilder}}; +use schist_models::{ + date_utc::DateUtc, + transaction::{Transaction, TransactionBuilder}, +}; use schist_traits::nowlike::Nowlike; pub fn make_fake_transaction(id: i32) -> Transaction { make_fake_transaction_builder(id).build().unwrap() } -pub fn make_fake_transaction_builder(id: i32) -> TransactionBuilder { +pub fn make_fake_transaction_builder(id: i32) -> TransactionBuilder { let mut builder = TransactionBuilder::default(); builder .id(id) - .description(String::from("Fake bucket transfer")) - .payee(String::from("Fake payee")) - .quantity(0) + .account_id(0) + .amount(0) + .bucket_id(None) + .counterparty(String::from("Fake payee")) .date(DateUtc::now()) + .description(String::from("Fake transaction")) .account_id(0); builder } diff --git a/schist_core/schist_fakes/src/transaction_categorisation.rs b/schist_core/schist_fakes/src/transaction_categorisation.rs deleted file mode 100644 index 11b50c2..0000000 --- a/schist_core/schist_fakes/src/transaction_categorisation.rs +++ /dev/null @@ -1,16 +0,0 @@ -use schist_models::transaction_categorisation::{TransactionCategorisation, TransactionCategorisationBuilder}; - -pub fn make_fake_transaction_categorisation(id: i32) -> TransactionCategorisation { - make_fake_transaction_categorisation_builder(id).build().unwrap() -} - -pub fn make_fake_transaction_categorisation_builder(id: i32) -> TransactionCategorisationBuilder { - let mut builder = TransactionCategorisationBuilder::default(); - builder - .id(id) - .description(String::from("Fake bucket transfer")) - .quantity(0) - .transaction_id(0) - .bucket_id(0); - builder -} 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 ba65012..3b2c73e 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 budget_drips; -DROP TABLE categories; -DROP TABLE transaction_categorisations; -DROP TABLE category_transfers; +DROP TABLE buckets; +DROP TABLE bucket_transfers; +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 a09bf7b..275f280 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 @@ -9,9 +9,9 @@ CREATE TABLE accounts( CREATE TABLE account_transfers( id INTEGER NOT NULL PRIMARY KEY, + amount INTEGER NOT NULL, date TEXT NOT NULL, description TEXT NOT NULL, - quantity INTEGER NOT NULL, from_account_id INTEGER NOT NULL, to_account_id INTEGER NOT NULL, FOREIGN KEY (from_account_id) @@ -24,70 +24,66 @@ CREATE TABLE account_transfers( ON DELETE RESTRICT ); - -CREATE TABLE budget_drips( +CREATE TABLE buckets( id INTEGER NOT NULL PRIMARY KEY, - category_id INTEGER NOT NULL, - date TEXT NOT NULL, - quantity INTEGER NOT NULL, - FOREIGN KEY (category_id) - REFERENCES categories (id) - ON UPDATE CASCADE - ON DELETE RESTRICT, - UNIQUE(category_id, date) + balance INTEGER, + balance_cache_key TEXT, + bucket_group TEXT NOT NULL, + name TEXT NOT NULL ); -CREATE TABLE categories( - id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - balance INTEGER NOT NULL, - balance_date TEXT NOT NULL, - budget_period INTEGER NOT NULL, - budget_period_unit TEXT NOT NULL, - budget_quantity INTEGER NOT NULL -); - -CREATE TABLE category_transfers( +CREATE TABLE bucket_transfers( id INTEGER NOT NULL PRIMARY KEY, + amount INTEGER NOT NULL, description TEXT NOT NULL, - quantity INTEGER NOT NULL, - from_category_id INTEGER NOT NULL, - to_category_id INTEGER NOT NULL, - FOREIGN KEY (from_category_id) - REFERENCES categories (id) + 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_category_id) - REFERENCES categories (id) + FOREIGN KEY (to_bucket_id) + REFERENCES buckets (id) ON UPDATE CASCADE ON DELETE RESTRICT ); -CREATE TABLE transactions( +CREATE TABLE drips( id INTEGER NOT NULL PRIMARY KEY, - description TEXT NOT NULL, - payee TEXT NOT NULL, - quantity INTEGER NOT NULL, + amount INTEGER NOT NULL, + bucket_id INTEGER NOT NULL, date TEXT NOT NULL, - account_id INTEGER NOT NULL, - FOREIGN KEY (account_id) - REFERENCES accounts (id) + inserted_at_unix_seconds INTEGER NOT NULL, + pipe_id INTEGER NOT NULL, + FOREIGN KEY (bucket_id) + REFERENCES buckets (id) + ON UPDATE CASCADE + ON DELETE RESTRICT, + UNIQUE(bucket_id, date) +); + +CREATE TABLE pipes( + id INTEGER NOT NULL PRIMARY KEY, + amount INTEGER NOT NULL, + bucket_id INTEGER NOT NULL, + period TEXT NOT NULL, + start_date TEXT NOT NULL, + FOREIGN KEY (bucket_id) + REFERENCES buckets (id) ON UPDATE CASCADE ON DELETE RESTRICT ); -CREATE TABLE transaction_categorisations( +CREATE TABLE transactions( id INTEGER NOT NULL PRIMARY KEY, + account_id INTEGER NOT NULL, + amount INTEGER NOT NULL, + bucket_id INTEGER, + counterparty TEXT NOT NULL, + date TEXT NOT NULL, description TEXT NOT NULL, - quantity INTEGER NOT NULL, - transaction_id INTEGER NOT NULL, - category_id INTEGER NOT NULL, - FOREIGN KEY (transaction_id) - REFERENCES transactions (id) - ON UPDATE CASCADE - ON DELETE RESTRICT, - FOREIGN KEY (category_id) - REFERENCES categories (id) + FOREIGN KEY (account_id) + REFERENCES accounts (id) ON UPDATE CASCADE ON DELETE RESTRICT ); diff --git a/schist_core/schist_models/migrations/2025-08-15-090859_dummy_data/down.sql b/schist_core/schist_models/migrations/2025-08-15-090859_dummy_data/down.sql new file mode 100644 index 0000000..8050572 --- /dev/null +++ b/schist_core/schist_models/migrations/2025-08-15-090859_dummy_data/down.sql @@ -0,0 +1,39 @@ +DELETE FROM accounts + WHERE id=0 + AND name="Nationwide bank account" + AND opening_balance=0 + AND opening_date="2024-01-01"; + +DELETE FROM accounts + WHERE id=1 + AND name="cash account" + AND opening_balance=0 + AND opening_date="2024-01-01"; + +DELETE FROM accounts + WHERE id=2 + AND name="YBS savings account" + AND opening_balance=0 + AND opening_date="2024-01-01"; + +DELETE FROM buckets + WHERE id=0 + AND balance IS NULL + AND balance_cache_key IS NULL + AND bucket_group="normal expenses" + AND name="groceries"; + +DELETE FROM buckets + WHERE id=1 + AND balance IS NULL + AND balance_cache_key IS NULL + AND bucket_group="luxuries" + AND name="bevvy"; + +DELETE FROM buckets + WHERE id=2 + AND balance IS NULL + AND balance_cache_key IS NULL + AND bucket_group="income" + AND name="salary"; + diff --git a/schist_core/schist_models/migrations/2025-08-15-090859_dummy_data/up.sql b/schist_core/schist_models/migrations/2025-08-15-090859_dummy_data/up.sql new file mode 100644 index 0000000..f5c97d0 --- /dev/null +++ b/schist_core/schist_models/migrations/2025-08-15-090859_dummy_data/up.sql @@ -0,0 +1,14 @@ +INSERT INTO accounts + (id, name, opening_balance, opening_date) + VALUES + (0, "Nationwide bank account", 0, "2024-01-01"), + (1, "cash account", 0, "2024-01-01"), + (2, "YBS savings account", 0, "2024-01-01"); + +INSERT INTO buckets + (id, balance, balance_cache_key, bucket_group, name) + VALUES + (0, NULL, NULL, "normal expenses", "groceries"), + (1, NULL, NULL, "luxuries", "bevvy"), + (2, NULL, NULL, "income", "salary"); + diff --git a/schist_core/schist_models/src/account_transfer.rs b/schist_core/schist_models/src/account_transfer.rs index 06c51ac..ba33dee 100644 --- a/schist_core/schist_models/src/account_transfer.rs +++ b/schist_core/schist_models/src/account_transfer.rs @@ -7,9 +7,9 @@ use crate::date_utc::DateUtc; #[diesel(table_name = crate::schema::account_transfers)] pub struct AccountTransfer { pub id: i32, + pub amount: i32, pub date: DateUtc, pub description: String, - pub quantity: i32, pub from_account_id: i32, pub to_account_id: i32, } diff --git a/schist_core/schist_models/src/bucket.rs b/schist_core/schist_models/src/bucket.rs index 1ee6a13..1a78628 100644 --- a/schist_core/schist_models/src/bucket.rs +++ b/schist_core/schist_models/src/bucket.rs @@ -2,8 +2,6 @@ use derive_builder::Builder; use diesel::prelude::*; use serde::{Deserialize, Serialize}; -use crate::{budget_period_unit::BudgetPeriodUnit, date_utc::DateUtc}; - #[derive( Builder, Clone, @@ -19,11 +17,9 @@ use crate::{budget_period_unit::BudgetPeriodUnit, date_utc::DateUtc}; #[diesel(table_name = crate::schema::buckets)] pub struct Bucket { pub id: i32, - pub name: String, - pub balance: i32, - pub balance_date: DateUtc, - pub budget_period: i32, - pub budget_period_unit: BudgetPeriodUnit, - pub budget_quantity: i32, + pub balance: Option<i32>, + pub balance_cache_key: Option<String>, + #[column_name = "bucket_group"] pub group: String, + pub name: String, } diff --git a/schist_core/schist_models/src/bucket_transfer.rs b/schist_core/schist_models/src/bucket_transfer.rs index bdf95d9..7c3a9d2 100644 --- a/schist_core/schist_models/src/bucket_transfer.rs +++ b/schist_core/schist_models/src/bucket_transfer.rs @@ -5,8 +5,8 @@ use diesel::prelude::*; #[diesel(table_name = crate::schema::bucket_transfers)] pub struct BucketTransfer { pub id: i32, + pub amount: i32, pub description: String, - pub quantity: i32, pub from_bucket_id: i32, pub to_bucket_id: i32, } diff --git a/schist_core/schist_models/src/budget_drip.rs b/schist_core/schist_models/src/budget_drip.rs deleted file mode 100644 index 419c8f9..0000000 --- a/schist_core/schist_models/src/budget_drip.rs +++ /dev/null @@ -1,14 +0,0 @@ -use derive_builder::Builder; -use diesel::prelude::{Identifiable, Insertable, Queryable, Selectable}; -use serde::{Deserialize, Serialize}; - -use crate::date_utc::DateUtc; - -#[derive(Builder, Clone, Copy, Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize, Insertable)] -#[diesel(table_name = crate::schema::budget_drips)] -pub struct BudgetDrip { - pub id: i32, - pub bucket_id: i32, - pub date: DateUtc, - pub quantity: i32, -} diff --git a/schist_core/schist_models/src/drip.rs b/schist_core/schist_models/src/drip.rs new file mode 100644 index 0000000..f092ae9 --- /dev/null +++ b/schist_core/schist_models/src/drip.rs @@ -0,0 +1,28 @@ +use derive_builder::Builder; +use diesel::prelude::{Identifiable, Insertable, Queryable, Selectable}; +use serde::{Deserialize, Serialize}; + +use crate::date_utc::DateUtc; + +#[derive( + Builder, + Clone, + Copy, + Queryable, + Identifiable, + Insertable, + Selectable, + Debug, + PartialEq, + Serialize, + Deserialize, +)] +#[diesel(table_name = crate::schema::drips)] +pub struct Drip { + pub id: i32, + pub amount: i32, + pub bucket_id: i32, + pub date: DateUtc, + pub inserted_at_unix_seconds: i32, + pub pipe_id: i32, +} diff --git a/schist_core/schist_models/src/lib.rs b/schist_core/schist_models/src/lib.rs index db5c593..503db17 100644 --- a/schist_core/schist_models/src/lib.rs +++ b/schist_core/schist_models/src/lib.rs @@ -2,11 +2,11 @@ pub mod account; pub mod account_transfer; pub mod bucket; pub mod bucket_transfer; -pub mod budget_drip; pub mod budget_period_unit; pub mod date_utc; pub mod datetime_utc; +pub mod drip; pub mod migrations; +pub mod pipe; pub mod schema; pub mod transaction; -pub mod transaction_categorisation; diff --git a/schist_core/schist_models/src/pipe.rs b/schist_core/schist_models/src/pipe.rs new file mode 100644 index 0000000..fb66a20 --- /dev/null +++ b/schist_core/schist_models/src/pipe.rs @@ -0,0 +1,27 @@ +use derive_builder::Builder; +use diesel::prelude::*; +use serde::{Deserialize, Serialize}; + +use crate::{budget_period_unit::BudgetPeriodUnit, date_utc::DateUtc}; + +#[derive( + Builder, + Insertable, + Queryable, + Identifiable, + Selectable, + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, +)] +#[diesel(table_name = crate::schema::pipes)] +#[diesel(belongs_to(Bucket))] +pub struct Pipe { + pub id: i32, + pub amount: i32, + pub bucket_id: i32, + pub period: BudgetPeriodUnit, + pub start_date: DateUtc, +} diff --git a/schist_core/schist_models/src/schema.rs b/schist_core/schist_models/src/schema.rs index 1c9fc62..3799479 100644 --- a/schist_core/schist_models/src/schema.rs +++ b/schist_core/schist_models/src/schema.rs @@ -3,9 +3,9 @@ diesel::table! { account_transfers (id) { id -> Integer, + amount -> Integer, date -> Text, description -> Text, - quantity -> Integer, from_account_id -> Integer, to_account_id -> Integer, } @@ -21,69 +21,68 @@ diesel::table! { } diesel::table! { - budget_drips (id) { + bucket_transfers (id) { id -> Integer, - bucket_id -> Integer, - date -> Text, - quantity -> Integer, + amount -> Integer, + description -> Text, + from_bucket_id -> Integer, + to_bucket_id -> Integer, } } diesel::table! { buckets (id) { id -> Integer, + balance -> Nullable<Integer>, + balance_cache_key -> Nullable<Text>, + bucket_group -> Text, name -> Text, - balance -> Integer, - balance_date -> Text, - budget_period -> Integer, - budget_period_unit -> Text, - budget_quantity -> Integer, - group -> Text, } } diesel::table! { - bucket_transfers (id) { + drips (id) { id -> Integer, - description -> Text, - quantity -> Integer, - from_bucket_id -> Integer, - to_bucket_id -> Integer, + amount -> Integer, + bucket_id -> Integer, + date -> Text, + inserted_at_unix_seconds -> Integer, + pipe_id -> Integer, } } diesel::table! { - transaction_categorisations (id) { + pipes (id) { id -> Integer, - description -> Text, - quantity -> Integer, - transaction_id -> Integer, + amount -> Integer, bucket_id -> Integer, + period -> Text, + start_date -> Text, } } diesel::table! { transactions (id) { id -> Integer, - description -> Text, - payee -> Text, - quantity -> Integer, - date -> Text, account_id -> Integer, + amount -> Integer, + bucket_id -> Nullable<Integer>, + counterparty -> Text, + date -> Text, + description -> Text, } } -diesel::joinable!(budget_drips -> buckets (bucket_id)); -diesel::joinable!(transaction_categorisations -> buckets (bucket_id)); -diesel::joinable!(transaction_categorisations -> transactions (transaction_id)); +diesel::joinable!(drips -> buckets (bucket_id)); +diesel::joinable!(pipes -> buckets (bucket_id)); diesel::joinable!(transactions -> accounts (account_id)); diesel::allow_tables_to_appear_in_same_query!( account_transfers, accounts, - budget_drips, - buckets, bucket_transfers, - transaction_categorisations, + buckets, + drips, + pipes, transactions, ); diff --git a/schist_core/schist_models/src/transaction.rs b/schist_core/schist_models/src/transaction.rs index 9e983f5..7e754be 100644 --- a/schist_core/schist_models/src/transaction.rs +++ b/schist_core/schist_models/src/transaction.rs @@ -3,14 +3,17 @@ use diesel::prelude::{Associations, Identifiable, Insertable, Queryable, Selecta use crate::{account::Account, date_utc::DateUtc}; -#[derive(Builder, Clone, Queryable, Identifiable, Selectable, Insertable, Associations, Debug, PartialEq)] +#[derive( + Builder, Clone, Queryable, Identifiable, Selectable, Insertable, Associations, Debug, PartialEq, +)] #[diesel(table_name = crate::schema::transactions)] #[diesel(belongs_to(Account))] pub struct Transaction { pub id: i32, - pub description: String, - pub payee: String, - pub quantity: i32, - pub date: DateUtc, pub account_id: i32, + pub amount: i32, + pub bucket_id: Option<i32>, + pub counterparty: String, + pub date: DateUtc, + pub description: String, } diff --git a/schist_core/schist_models/src/transaction_categorisation.rs b/schist_core/schist_models/src/transaction_categorisation.rs deleted file mode 100644 index 36752ee..0000000 --- a/schist_core/schist_models/src/transaction_categorisation.rs +++ /dev/null @@ -1,12 +0,0 @@ -use derive_builder::Builder; -use diesel::prelude::*; - -#[derive(Builder, Queryable, Identifiable, Selectable, Insertable, Debug, PartialEq)] -#[diesel(table_name = crate::schema::transaction_categorisations)] -pub struct TransactionCategorisation { - pub id: i32, - pub description: String, - pub quantity: i32, - pub transaction_id: i32, - pub bucket_id: i32, -} diff --git a/schist_core/schist_models/user_data.sqlite b/schist_core/schist_models/user_data.sqlite Binary files differindex a428ff0..175ce60 100644 --- a/schist_core/schist_models/user_data.sqlite +++ b/schist_core/schist_models/user_data.sqlite diff --git a/schist_core/schist_queries/src/accounts.rs b/schist_core/schist_queries/src/accounts.rs index ead281a..289faf6 100644 --- a/schist_core/schist_queries/src/accounts.rs +++ b/schist_core/schist_queries/src/accounts.rs @@ -11,16 +11,14 @@ pub fn delete_all_accounts(connection: &mut SqliteConnection) -> Result<usize> { } pub fn get_all_accounts(connection: &mut SqliteConnection) -> Result<Vec<Account>> { - let all_accounts = accounts_table.select(Account::as_select()) + let all_accounts = accounts_table + .select(Account::as_select()) .load(connection) .with_context(|| "failed to get all accounts")?; Ok(all_accounts) } -pub fn insert_accounts( - accounts: &[Account], - connection: &mut SqliteConnection, -) -> Result<usize> { +pub fn insert_accounts(accounts: &[Account], connection: &mut SqliteConnection) -> Result<usize> { let num_accounts_inserted = diesel::insert_into(accounts_table) .values(accounts) .execute(connection) diff --git a/schist_core/schist_queries/src/bucket_transfers.rs b/schist_core/schist_queries/src/bucket_transfers.rs index 2145944..923867f 100644 --- a/schist_core/schist_queries/src/bucket_transfers.rs +++ b/schist_core/schist_queries/src/bucket_transfers.rs @@ -1,10 +1,9 @@ use anyhow::{Context, Result}; use diesel::{dsl::sum, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; +use schist_models::bucket_transfer::BucketTransfer; use schist_schema::schema::bucket_transfers::{ - self as bucket_transfers_schema, - dsl::bucket_transfers as bucket_transfers_table, + self as bucket_transfers_schema, dsl::bucket_transfers as bucket_transfers_table, }; -use schist_models::bucket_transfer::BucketTransfer; pub fn delete_all_bucket_transfers(connection: &mut SqliteConnection) -> Result<usize> { let num_rows_deleted = diesel::delete(bucket_transfers_table) @@ -50,10 +49,15 @@ pub fn sum_bucket_transfer_quantity_per_from_bucket_id( .group_by(bucket_transfers_schema::from_bucket_id) .select(( bucket_transfers_schema::from_bucket_id, - sum(bucket_transfers_schema::quantity), + sum(bucket_transfers_schema::amount), )) .load::<(i32, Option<i64>)>(connection) - .map(|result| result.iter().map(|sum| (sum.0, sum.1.unwrap_or(0))).collect()) + .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) } @@ -65,10 +69,14 @@ pub fn sum_bucket_transfer_quantity_per_to_bucket_id( .group_by(bucket_transfers_schema::to_bucket_id) .select(( bucket_transfers_schema::to_bucket_id, - sum(bucket_transfers_schema::quantity), + sum(bucket_transfers_schema::amount), )) .load::<(i32, Option<i64>)>(connection) - .map(|vec| vec.iter().map(|(id, sum)| (*id, sum.unwrap_or(0))).collect()) + .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/buckets.rs b/schist_core/schist_queries/src/buckets.rs index c7cf213..29270af 100644 --- a/schist_core/schist_queries/src/buckets.rs +++ b/schist_core/schist_queries/src/buckets.rs @@ -26,10 +26,7 @@ pub fn get_all_bucket_ids(connection: &mut SqliteConnection) -> Result<Vec<i32>> Ok(all_bucket_ids) } -pub fn insert_buckets( - buckets: &[Bucket], - connection: &mut SqliteConnection, -) -> Result<usize> { +pub fn insert_buckets(buckets: &[Bucket], connection: &mut SqliteConnection) -> Result<usize> { let num_buckets_inserted = diesel::insert_into(buckets_table) .values(buckets) .execute(connection) diff --git a/schist_core/schist_queries/src/budget_drips.rs b/schist_core/schist_queries/src/budget_drips.rs deleted file mode 100644 index b6f9372..0000000 --- a/schist_core/schist_queries/src/budget_drips.rs +++ /dev/null @@ -1,40 +0,0 @@ -use anyhow::{Context, Result}; -use diesel::{QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; -use schist_models::budget_drip::BudgetDrip; -use schist_schema::schema::budget_drips::dsl::budget_drips as budget_drips_table; - -pub fn delete_all_budget_drips(connection: &mut SqliteConnection) -> Result<usize> { - let num_rows_deleted = diesel::delete(budget_drips_table) - .execute(connection) - .with_context(|| "failed to delete all budget drips")?; - Ok(num_rows_deleted) -} - -pub fn get_all_budget_drips(connection: &mut SqliteConnection) -> Result<Vec<BudgetDrip>> { - let all_budget_drips = budget_drips_table.select(BudgetDrip::as_select()) - .load(connection) - .with_context(|| "failed to get all budget drips")?; - Ok(all_budget_drips) -} - -pub fn insert_budget_drips( - budget_drips: &[BudgetDrip], - connection: &mut SqliteConnection, -) -> Result<usize> { - let num_budget_drips_inserted = diesel::insert_into(budget_drips_table) - .values(budget_drips) - .execute(connection) - .with_context(|| insert_err_msg(budget_drips))?; - Ok(num_budget_drips_inserted) -} - -fn insert_err_msg(budget_drips: &[BudgetDrip]) -> String { - format!( - "failed to insert budget drips: [{}]", - budget_drips - .iter() - .map(|a| a.id.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) -} diff --git a/schist_core/schist_queries/src/clear.rs b/schist_core/schist_queries/src/clear.rs index 6d2894d..04d22d9 100644 --- a/schist_core/schist_queries/src/clear.rs +++ b/schist_core/schist_queries/src/clear.rs @@ -2,15 +2,17 @@ use anyhow::Result; use diesel::SqliteConnection; use crate::{ - accounts::delete_all_accounts, budget_drips::delete_all_budget_drips, buckets::delete_all_buckets, bucket_transfers::delete_all_bucket_transfers, transaction_categorisations::delete_all_transaction_categorisations, transactions::delete_all_transactions + 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, }; pub fn clear(connection: &mut SqliteConnection) -> Result<()> { delete_all_accounts(connection)?; - delete_all_budget_drips(connection)?; + delete_all_drips(connection)?; delete_all_buckets(connection)?; delete_all_bucket_transfers(connection)?; delete_all_transactions(connection)?; - delete_all_transaction_categorisations(connection)?; + delete_all_pipes(connection)?; Ok(()) } diff --git a/schist_core/schist_queries/src/drips.rs b/schist_core/schist_queries/src/drips.rs new file mode 100644 index 0000000..d9e33a8 --- /dev/null +++ b/schist_core/schist_queries/src/drips.rs @@ -0,0 +1,38 @@ +use anyhow::{Context, Result}; +use diesel::{QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; +use schist_models::drip::Drip; +use schist_schema::schema::drips::dsl::drips as drips_table; + +pub fn delete_all_drips(connection: &mut SqliteConnection) -> Result<usize> { + let num_rows_deleted = diesel::delete(drips_table) + .execute(connection) + .with_context(|| "failed to delete all drips")?; + Ok(num_rows_deleted) +} + +pub fn get_all_drips(connection: &mut SqliteConnection) -> Result<Vec<Drip>> { + let all_drips = drips_table + .select(Drip::as_select()) + .load(connection) + .with_context(|| "failed to get all drips")?; + Ok(all_drips) +} + +pub fn insert_drips(drips: &[Drip], connection: &mut SqliteConnection) -> Result<usize> { + let num_drips_inserted = diesel::insert_into(drips_table) + .values(drips) + .execute(connection) + .with_context(|| insert_err_msg(drips))?; + Ok(num_drips_inserted) +} + +fn insert_err_msg(drips: &[Drip]) -> String { + format!( + "failed to insert drips: [{}]", + drips + .iter() + .map(|a| a.id.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) +} diff --git a/schist_core/schist_queries/src/lib.rs b/schist_core/schist_queries/src/lib.rs index e3e5488..ccd558d 100644 --- a/schist_core/schist_queries/src/lib.rs +++ b/schist_core/schist_queries/src/lib.rs @@ -1,10 +1,10 @@ -pub mod accounts; pub mod account_transfers; -pub mod budget_drips; -pub mod buckets; +pub mod accounts; pub mod bucket_transfers; +pub mod buckets; pub mod clear; +pub mod drips; +pub mod pipes; pub mod transactions; -pub mod transaction_categorisations; mod utils; diff --git a/schist_core/schist_queries/src/pipes.rs b/schist_core/schist_queries/src/pipes.rs new file mode 100644 index 0000000..188d193 --- /dev/null +++ b/schist_core/schist_queries/src/pipes.rs @@ -0,0 +1,55 @@ +use anyhow::{Context, Result}; +use diesel::{dsl::sum, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; +use schist_models::pipe::Pipe; +use schist_schema::schema::pipes::{self as pipes_schema, dsl::pipes as pipes_table}; + +pub fn delete_all_pipes(connection: &mut SqliteConnection) -> Result<usize> { + let num_rows_deleted = diesel::delete(pipes_table) + .execute(connection) + .with_context(|| "failed to delete all pipes")?; + Ok(num_rows_deleted) +} + +pub fn get_all_pipes(connection: &mut SqliteConnection) -> Result<Vec<Pipe>> { + let all_pipes = pipes_table + .select(Pipe::as_select()) + .load(connection) + .with_context(|| "failed to get all pipes")?; + Ok(all_pipes) +} + +pub fn insert_pipes( + pipes: &[Pipe], + connection: &mut SqliteConnection, +) -> Result<usize> { + let num_rows_inserted = diesel::insert_into(pipes_table) + .values(pipes) + .execute(connection) + .with_context(|| insert_err_msg(&pipes))?; + Ok(num_rows_inserted) +} + +fn insert_err_msg(pipes: &[Pipe]) -> String { + format!( + "failed to insert pipes: [{}]", + pipes + .iter() + .map(|tc| tc.id.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) +} + +pub fn sum_pipes_flow_per_bucket_id(connection: &mut SqliteConnection) -> Result<Vec<(i32, i64)>> { + let sum = pipes_table + .group_by(pipes_schema::bucket_id) + .select((pipes_schema::bucket_id, sum(pipes_schema::amount))) + .load::<(i32, Option<i64>)>(connection) + .map(|vec| { + vec.iter() + .map(|(bucket_id, sum)| (*bucket_id, sum.unwrap_or(0))) + .collect() + }) + .with_context(|| "failed to sum pipes flow per bucket ID")?; + Ok(sum) +} diff --git a/schist_core/schist_queries/src/transaction_categorisations.rs b/schist_core/schist_queries/src/transaction_categorisations.rs deleted file mode 100644 index 95aaf8d..0000000 --- a/schist_core/schist_queries/src/transaction_categorisations.rs +++ /dev/null @@ -1,59 +0,0 @@ -use anyhow::{Context, Result}; -use diesel::{dsl::sum, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; -use schist_models::transaction_categorisation::TransactionCategorisation; -use schist_schema::schema::transaction_categorisations::{ - self as transaction_categorisations_schema, - dsl::transaction_categorisations as transaction_categorisations_table, -}; - -pub fn delete_all_transaction_categorisations(connection: &mut SqliteConnection) -> Result<usize> { - let num_rows_deleted = diesel::delete(transaction_categorisations_table) - .execute(connection) - .with_context(|| "failed to delete all transaction categorisations")?; - Ok(num_rows_deleted) -} - -pub fn get_all_transaction_categorisations(connection: &mut SqliteConnection) -> Result<Vec<TransactionCategorisation>> { - let all_transaction_categorisations = transaction_categorisations_table - .select(TransactionCategorisation::as_select()) - .load(connection) - .with_context(|| "failed to get all transaction categorisations")?; - Ok(all_transaction_categorisations) -} - -pub fn insert_transaction_categorisations( - transaction_categorisations: &[TransactionCategorisation], - connection: &mut SqliteConnection, -) -> Result<usize> { - let num_rows_inserted = diesel::insert_into(transaction_categorisations_table) - .values(transaction_categorisations) - .execute(connection) - .with_context(|| insert_err_msg(&transaction_categorisations))?; - Ok(num_rows_inserted) -} - -fn insert_err_msg(transaction_categorisations: &[TransactionCategorisation]) -> String { - format!( - "failed to insert transaction categorisations: [{}]", - transaction_categorisations - .iter() - .map(|tc| tc.id.to_string()) - .collect::<Vec<String>>() - .join(", ") - ) -} - -pub fn sum_transaction_categorisation_quantity_per_bucket_id( - connection: &mut SqliteConnection, -) -> Result<Vec<(i32, i64)>> { - let sum = transaction_categorisations_table - .group_by(transaction_categorisations_schema::bucket_id) - .select(( - transaction_categorisations_schema::bucket_id, - sum(transaction_categorisations_schema::quantity), - )) - .load::<(i32, Option<i64>)>(connection) - .map(|vec| vec.iter().map(|(bucket_id, sum)| (*bucket_id, sum.unwrap_or(0))).collect()) - .with_context(|| "failed to sum transaction categorisation quantity per bucket ID")?; - Ok(sum) -} diff --git a/schist_core/schist_queries/src/transactions.rs b/schist_core/schist_queries/src/transactions.rs index 5941ebc..3abacfb 100644 --- a/schist_core/schist_queries/src/transactions.rs +++ b/schist_core/schist_queries/src/transactions.rs @@ -14,7 +14,7 @@ pub fn get_all_transactions(connection: &mut SqliteConnection) -> Result<Vec<Tra let all_transactions = transactions_table .select(Transaction::as_select()) .load(connection) - .with_context(|| "failed to get all bucket transfers")?; + .with_context(|| "failed to get all transactions")?; Ok(all_transactions) } diff --git a/schist_core/schist_queries/src/utils/calculate_bucket_balance.rs b/schist_core/schist_queries/src/utils/calculate_bucket_balance.rs index 5194084..e238613 100644 --- a/schist_core/schist_queries/src/utils/calculate_bucket_balance.rs +++ b/schist_core/schist_queries/src/utils/calculate_bucket_balance.rs @@ -1,31 +1,26 @@ use anyhow::Result; -use schist_models::{bucket::Bucket, budget_drip::BudgetDrip}; +use schist_models::{bucket::Bucket, drip::Drip}; -use super::{calculate_budgets_accrual, find_by_id_or}; +use super::{find_by_id_or, sum_drips_for_bucket}; pub fn calculate_bucket_balance( bucket: &Bucket, transaction_sums: &[(i32, i64)], bucket_transfer_from_sums: &[(i32, i64)], bucket_transfer_to_sums: &[(i32, i64)], - budget_drips: &[BudgetDrip], + budget_drips: &[Drip], ) -> Result<i64> { let transaction_sum = find_by_id_or(transaction_sums, bucket.id, 0); let bucket_transfer_from_sum = find_by_id_or(bucket_transfer_from_sums, bucket.id, 0); let bucket_transfer_to_sum = find_by_id_or(bucket_transfer_to_sums, bucket.id, 0); - let budget_accruals_sum = calculate_budgets_accrual(bucket, budget_drips)?; - - Ok( - transaction_sum - + budget_accruals_sum - + bucket_transfer_to_sum - - bucket_transfer_from_sum - ) + let budget_accruals_sum = sum_drips_for_bucket(bucket, budget_drips)?; + + Ok(transaction_sum + budget_accruals_sum + bucket_transfer_to_sum - bucket_transfer_from_sum) } #[cfg(test)] mod test { - use schist_fakes::{budget_drip::make_fake_budget_drip_builder, bucket::make_fake_bucket}; + use schist_fakes::{bucket::make_fake_bucket, drip::make_fake_drip_builder}; use super::calculate_bucket_balance; @@ -45,9 +40,11 @@ mod test { let transaction_sums = [(1, 100)]; let bucket_transfer_from_sums = [(1, 91)]; let bucket_transfer_to_sums = [(2, 79)]; - let budget_drips = [ - make_fake_budget_drip_builder(0).quantity(100).bucket_id(1).build().unwrap(), - ]; + let budget_drips = [make_fake_drip_builder(0) + .amount(100) + .bucket_id(1) + .build() + .unwrap()]; let result = calculate_bucket_balance( &bucket, @@ -108,11 +105,31 @@ mod test { let bucket_transfer_from_sums = [(0, 91)]; let bucket_transfer_to_sums = [(0, 79)]; let budget_drips = [ - make_fake_budget_drip_builder(0).quantity(77).bucket_id(0).build().unwrap(), - make_fake_budget_drip_builder(0).quantity(77).bucket_id(0).build().unwrap(), - make_fake_budget_drip_builder(0).quantity(77).bucket_id(0).build().unwrap(), - make_fake_budget_drip_builder(0).quantity(77).bucket_id(0).build().unwrap(), - make_fake_budget_drip_builder(0).quantity(77).bucket_id(0).build().unwrap(), + make_fake_drip_builder(0) + .amount(77) + .bucket_id(0) + .build() + .unwrap(), + make_fake_drip_builder(0) + .amount(77) + .bucket_id(0) + .build() + .unwrap(), + make_fake_drip_builder(0) + .amount(77) + .bucket_id(0) + .build() + .unwrap(), + make_fake_drip_builder(0) + .amount(77) + .bucket_id(0) + .build() + .unwrap(), + make_fake_drip_builder(0) + .amount(77) + .bucket_id(0) + .build() + .unwrap(), ]; let result = calculate_bucket_balance( diff --git a/schist_core/schist_queries/src/utils/calculate_budgets_accrual.rs b/schist_core/schist_queries/src/utils/calculate_budgets_accrual.rs deleted file mode 100644 index cbf7180..0000000 --- a/schist_core/schist_queries/src/utils/calculate_budgets_accrual.rs +++ /dev/null @@ -1,72 +0,0 @@ -use anyhow::Result; -use schist_models::{ - budget_drip::BudgetDrip, - bucket::Bucket, -}; - -pub fn calculate_budgets_accrual(bucket: &Bucket, budget_drips: &[BudgetDrip]) -> Result<i64> { - Ok(budget_drips - .iter() - .filter(|bd| bd.bucket_id == bucket.id) - .map(|bd| Into::<i64>::into(bd.quantity)) - .sum() - ) -} - -#[cfg(test)] -mod test { - use schist_fakes::{budget_drip::make_fake_budget_drip_builder, bucket::make_fake_bucket}; - - use super::calculate_budgets_accrual; - - #[test] - fn when_no_budget_drips_then_return_zero() { - let bucket = make_fake_bucket(0); - let budget_drips = []; - - let result = calculate_budgets_accrual(&bucket, &budget_drips); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 0_i64); - } - - #[test] - fn when_one_budget_drip_for_another_bucket_then_return_zero() { - let bucket = make_fake_bucket(0); - let budget_drips = [ - make_fake_budget_drip_builder(0).quantity(100).bucket_id(1).build().unwrap(), - ]; - - let result = calculate_budgets_accrual(&bucket, &budget_drips); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 0_i64); - } - - #[test] - fn when_one_budget_drip_then_return_quantity() { - let bucket = make_fake_bucket(0); - let budget_drips = [ - make_fake_budget_drip_builder(0).quantity(100).bucket_id(0).build().unwrap(), - ]; - - let result = calculate_budgets_accrual(&bucket, &budget_drips); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 100); - } - - #[test] - fn when_two_budget_drips_then_return_sum() { - let bucket = make_fake_bucket(0); - let budget_drips = [ - make_fake_budget_drip_builder(0).quantity(100).bucket_id(0).build().unwrap(), - make_fake_budget_drip_builder(1).quantity(120).bucket_id(0).build().unwrap(), - ]; - - let result = calculate_budgets_accrual(&bucket, &budget_drips); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 220); - } -} diff --git a/schist_core/schist_queries/src/utils/mod.rs b/schist_core/schist_queries/src/utils/mod.rs index c006a9b..6b47b48 100644 --- a/schist_core/schist_queries/src/utils/mod.rs +++ b/schist_core/schist_queries/src/utils/mod.rs @@ -1,8 +1,8 @@ -mod calculate_budgets_accrual; mod calculate_bucket_balance; mod find_by_id_or; +mod sum_drips_for_bucket; -use calculate_budgets_accrual::calculate_budgets_accrual; +use sum_drips_for_bucket::sum_drips_for_bucket; pub use calculate_bucket_balance::calculate_bucket_balance; pub use find_by_id_or::find_by_id_or; 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 new file mode 100644 index 0000000..76f7988 --- /dev/null +++ b/schist_core/schist_queries/src/utils/sum_drips_for_bucket.rs @@ -0,0 +1,80 @@ +use anyhow::Result; +use schist_models::{bucket::Bucket, drip::Drip}; + +pub fn sum_drips_for_bucket(bucket: &Bucket, budget_drips: &[Drip]) -> Result<i64> { + Ok(budget_drips + .iter() + .filter(|bd| bd.bucket_id == bucket.id) + .map(|bd| Into::<i64>::into(bd.amount)) + .sum()) +} + +#[cfg(test)] +mod test { + use schist_fakes::{bucket::make_fake_bucket, drip::make_fake_drip_builder}; + + use super::sum_drips_for_bucket; + + #[test] + fn when_no_budget_drips_then_return_zero() { + let bucket = make_fake_bucket(0); + let budget_drips = []; + + let result = sum_drips_for_bucket(&bucket, &budget_drips); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 0_i64); + } + + #[test] + fn when_one_budget_drip_for_another_bucket_then_return_zero() { + let bucket = make_fake_bucket(0); + let budget_drips = [make_fake_drip_builder(0) + .amount(100) + .bucket_id(1) + .build() + .unwrap()]; + + let result = sum_drips_for_bucket(&bucket, &budget_drips); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 0_i64); + } + + #[test] + fn when_one_budget_drip_then_return_quantity() { + let bucket = make_fake_bucket(0); + let budget_drips = [make_fake_drip_builder(0) + .amount(100) + .bucket_id(0) + .build() + .unwrap()]; + + let result = sum_drips_for_bucket(&bucket, &budget_drips); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 100); + } + + #[test] + fn when_two_budget_drips_then_return_sum() { + let bucket = make_fake_bucket(0); + let budget_drips = [ + make_fake_drip_builder(0) + .amount(100) + .bucket_id(0) + .build() + .unwrap(), + make_fake_drip_builder(1) + .amount(120) + .bucket_id(0) + .build() + .unwrap(), + ]; + + let result = sum_drips_for_bucket(&bucket, &budget_drips); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 220); + } +} diff --git a/schist_core/schist_queries/tests/category_transfers.rs b/schist_core/schist_queries/tests/category_transfers.rs index e913dcb..13b9866 100644 --- a/schist_core/schist_queries/tests/category_transfers.rs +++ b/schist_core/schist_queries/tests/category_transfers.rs @@ -11,12 +11,21 @@ fn when_insert_bucket_transfers_then_returns_num_bucket_transfers() { 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(), + 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(); + let num_bucket_transfers_inserted = + queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap(); assert_eq!(num_bucket_transfers_inserted, 2); } @@ -27,20 +36,34 @@ fn given_bucket_transfers_when_get_all_bucket_transfers_then_return_bucket_trans 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(), + 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::insert_bucket_transfers(&bucket_transfers, connection).unwrap(); - let bucket_transfers_in_db = queries::bucket_transfers::get_all_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))); + 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))); + assert!(bucket_transfers + .iter() + .any(|bucket_transfer| bucket_transfer.eq(&bucket_transfer_in_db))); } } @@ -50,13 +73,23 @@ fn given_bucket_transfers_when_delete_all_bucket_transfers_then_return_num_of_bu 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(), + 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::insert_bucket_transfers(&bucket_transfers, connection).unwrap(); - let num_bucket_transfers_deleted = queries::bucket_transfers::delete_all_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()); } @@ -67,32 +100,60 @@ fn given_deleted_bucket_transfers_when_get_all_bucket_transfers_then_return_empt 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(), + 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::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(); + 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() { +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).quantity(1).build().unwrap(), - make_fake_bucket_transfer_builder(1).from_bucket_id(0).to_bucket_id(1).quantity(2).build().unwrap(), - make_fake_bucket_transfer_builder(2).from_bucket_id(1).to_bucket_id(0).quantity(4).build().unwrap(), + 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 _ = + queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap(); - let sums = queries::bucket_transfers::sum_bucket_transfer_quantity_per_from_bucket_id(connection); + let sums = + queries::bucket_transfers::sum_bucket_transfer_quantity_per_from_bucket_id(connection); assert!(sums.is_ok()); let mut sums = sums.unwrap(); @@ -108,12 +169,27 @@ fn given_bucket_transfers_when_sum_quantity_per_to_bucket_id_then_returns_sum_pe 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).quantity(1).build().unwrap(), - make_fake_bucket_transfer_builder(1).from_bucket_id(0).to_bucket_id(1).quantity(2).build().unwrap(), - make_fake_bucket_transfer_builder(2).from_bucket_id(1).to_bucket_id(0).quantity(4).build().unwrap(), + 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) + .build() + .unwrap(), ]; let _ = queries::buckets::insert_buckets(&buckets, connection); - let _ = queries::bucket_transfers::insert_bucket_transfers(&bucket_transfers, connection).unwrap(); + 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); diff --git a/schist_core/schist_queries/tests/budget_drips.rs b/schist_core/schist_queries/tests/drips.rs index 7c5bd4d..dccdbf4 100644 --- a/schist_core/schist_queries/tests/budget_drips.rs +++ b/schist_core/schist_queries/tests/drips.rs @@ -2,7 +2,7 @@ mod common; use common::test_context::TestContext; use diesel::{Connection, SqliteConnection}; -use schist_fakes::{budget_drip::make_fake_budget_drip_builder, bucket::make_fake_bucket}; +use schist_fakes::{bucket::make_fake_bucket, drip::make_fake_drip_builder}; use schist_models::{bucket::Bucket, date_utc::DateUtc}; use schist_queries as queries; @@ -18,17 +18,20 @@ fn when_insert_budget_drips_then_returns_num_budget_drips() { let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); let _buckets = given_buckets(connection); let budget_drips = [ - make_fake_budget_drip_builder(0) + make_fake_drip_builder(0) .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) .bucket_id(0) - .build().unwrap(), - make_fake_budget_drip_builder(1) + .build() + .unwrap(), + make_fake_drip_builder(1) .date(DateUtc::from_ymd(2020, 12, 26).unwrap()) .bucket_id(0) - .build().unwrap(), + .build() + .unwrap(), ]; - let num_budget_drips_inserted = queries::budget_drips::insert_budget_drips(&budget_drips, connection).unwrap(); + let num_budget_drips_inserted = + queries::drips::insert_drips(&budget_drips, connection).unwrap(); assert_eq!(num_budget_drips_inserted, 2); } @@ -39,72 +42,68 @@ fn given_budget_drip_when_insert_budget_drip_same_bucket_and_same_date_then_retu let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); let _buckets = given_buckets(connection); - let budget_drips_1 = [ - make_fake_budget_drip_builder(0) - .bucket_id(0) - .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) - .build().unwrap() - ]; - let _ = queries::budget_drips::insert_budget_drips(&budget_drips_1, connection); + let budget_drips_1 = [make_fake_drip_builder(0) + .bucket_id(0) + .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) + .build() + .unwrap()]; + let _ = queries::drips::insert_drips(&budget_drips_1, connection); - let budget_drips_2 = [ - make_fake_budget_drip_builder(1) - .bucket_id(0) - .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) - .build().unwrap() - ]; - let result = queries::budget_drips::insert_budget_drips(&budget_drips_2, connection); + let budget_drips_2 = [make_fake_drip_builder(1) + .bucket_id(0) + .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) + .build() + .unwrap()]; + let result = queries::drips::insert_drips(&budget_drips_2, connection); assert!(result.is_err()); } #[test] -fn given_budget_drip_when_insert_budget_drip_different_bucket_and_same_date_then_return_num_budget_drips_inserted() { +fn given_budget_drip_when_insert_budget_drip_different_bucket_and_same_date_then_return_num_budget_drips_inserted( +) { let context = TestContext::new(); let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); let _buckets = given_buckets(connection); - let budget_drips_1 = [ - make_fake_budget_drip_builder(0) - .bucket_id(0) - .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) - .build().unwrap() - ]; - let _ = queries::budget_drips::insert_budget_drips(&budget_drips_1, connection); + let budget_drips_1 = [make_fake_drip_builder(0) + .bucket_id(0) + .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) + .build() + .unwrap()]; + let _ = queries::drips::insert_drips(&budget_drips_1, connection); - let budget_drips_2 = [ - make_fake_budget_drip_builder(1) - .bucket_id(1) - .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) - .build().unwrap() - ]; - let result = queries::budget_drips::insert_budget_drips(&budget_drips_2, connection); + let budget_drips_2 = [make_fake_drip_builder(1) + .bucket_id(1) + .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) + .build() + .unwrap()]; + let result = queries::drips::insert_drips(&budget_drips_2, connection); assert!(result.is_ok()); assert_eq!(result.unwrap(), 1); } #[test] -fn given_budget_drip_when_insert_budget_drip_same_bucket_and_different_date_then_return_num_budget_drips_inserted() { +fn given_budget_drip_when_insert_budget_drip_same_bucket_and_different_date_then_return_num_budget_drips_inserted( +) { let context = TestContext::new(); let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); let _buckets = given_buckets(connection); - let budget_drips_1 = [ - make_fake_budget_drip_builder(0) - .bucket_id(0) - .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) - .build().unwrap() - ]; - let _ = queries::budget_drips::insert_budget_drips(&budget_drips_1, connection); + let budget_drips_1 = [make_fake_drip_builder(0) + .bucket_id(0) + .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) + .build() + .unwrap()]; + let _ = queries::drips::insert_drips(&budget_drips_1, connection); - let budget_drips_2 = [ - make_fake_budget_drip_builder(1) - .bucket_id(0) - .date(DateUtc::from_ymd(2020, 12, 26).unwrap()) - .build().unwrap() - ]; - let result = queries::budget_drips::insert_budget_drips(&budget_drips_2, connection); + let budget_drips_2 = [make_fake_drip_builder(1) + .bucket_id(0) + .date(DateUtc::from_ymd(2020, 12, 26).unwrap()) + .build() + .unwrap()]; + let result = queries::drips::insert_drips(&budget_drips_2, connection); assert!(result.is_ok()); assert_eq!(result.unwrap(), 1); @@ -116,27 +115,33 @@ fn given_budget_drips_when_get_all_budget_drips_then_return_budget_drips() { let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); let _buckets = given_buckets(connection); let budget_drips = [ - make_fake_budget_drip_builder(0) + make_fake_drip_builder(0) .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) .bucket_id(0) - .build().unwrap(), - make_fake_budget_drip_builder(1) + .build() + .unwrap(), + make_fake_drip_builder(1) .date(DateUtc::from_ymd(2020, 12, 26).unwrap()) .bucket_id(0) - .build().unwrap(), + .build() + .unwrap(), ]; - let _ = queries::budget_drips::insert_budget_drips(&budget_drips, connection).unwrap(); + let _ = queries::drips::insert_drips(&budget_drips, connection).unwrap(); - let budget_drips_in_db = queries::budget_drips::get_all_budget_drips(connection).unwrap(); + let budget_drips_in_db = queries::drips::get_all_drips(connection).unwrap(); assert_eq!(budget_drips_in_db.len(), budget_drips.len()); assert_eq!(budget_drips_in_db[0], budget_drips[0]); assert_eq!(budget_drips_in_db[1], budget_drips[1]); for budget_drip in &budget_drips { - assert!(budget_drips_in_db.iter().any(|budget_drip_in_db| budget_drip_in_db.eq(&budget_drip))); + assert!(budget_drips_in_db + .iter() + .any(|budget_drip_in_db| budget_drip_in_db.eq(&budget_drip))); } for budget_drip_in_db in budget_drips_in_db { - assert!(budget_drips.iter().any(|budget_drip| budget_drip.eq(&budget_drip_in_db))); + assert!(budget_drips + .iter() + .any(|budget_drip| budget_drip.eq(&budget_drip_in_db))); } } @@ -146,18 +151,20 @@ fn given_budget_drips_when_delete_all_budget_drips_then_return_num_of_budget_dri let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); let _buckets = given_buckets(connection); let budget_drips = [ - make_fake_budget_drip_builder(0) + make_fake_drip_builder(0) .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) .bucket_id(0) - .build().unwrap(), - make_fake_budget_drip_builder(1) + .build() + .unwrap(), + make_fake_drip_builder(1) .date(DateUtc::from_ymd(2020, 12, 26).unwrap()) .bucket_id(0) - .build().unwrap(), + .build() + .unwrap(), ]; - let _ = queries::budget_drips::insert_budget_drips(&budget_drips, connection).unwrap(); + let _ = queries::drips::insert_drips(&budget_drips, connection).unwrap(); - let num_budget_drips_deleted = queries::budget_drips::delete_all_budget_drips(connection).unwrap(); + let num_budget_drips_deleted = queries::drips::delete_all_drips(connection).unwrap(); assert_eq!(num_budget_drips_deleted, budget_drips.len()); } @@ -168,19 +175,21 @@ fn given_deleted_budget_drips_when_get_all_budget_drips_then_return_empty() { let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); let _buckets = given_buckets(connection); let budget_drips = [ - make_fake_budget_drip_builder(0) + make_fake_drip_builder(0) .date(DateUtc::from_ymd(2020, 12, 25).unwrap()) .bucket_id(0) - .build().unwrap(), - make_fake_budget_drip_builder(1) + .build() + .unwrap(), + make_fake_drip_builder(1) .date(DateUtc::from_ymd(2020, 12, 26).unwrap()) .bucket_id(0) - .build().unwrap(), + .build() + .unwrap(), ]; - let _ = queries::budget_drips::insert_budget_drips(&budget_drips, connection).unwrap(); - let _ = queries::budget_drips::delete_all_budget_drips(connection).unwrap(); + let _ = queries::drips::insert_drips(&budget_drips, connection).unwrap(); + let _ = queries::drips::delete_all_drips(connection).unwrap(); - let budget_drips_returned = queries::budget_drips::get_all_budget_drips(connection).unwrap(); + let budget_drips_returned = queries::drips::get_all_drips(connection).unwrap(); assert_eq!(budget_drips_returned.len(), 0); } diff --git a/schist_core/schist_queries/tests/pipes.rs b/schist_core/schist_queries/tests/pipes.rs new file mode 100644 index 0000000..abd5a5e --- /dev/null +++ b/schist_core/schist_queries/tests/pipes.rs @@ -0,0 +1,151 @@ +mod common; + +use common::test_context::TestContext; +use diesel::{Connection, SqliteConnection}; +use schist_fakes::{ + account::make_fake_account, bucket::make_fake_bucket, pipe::make_fake_pipe_builder, + transaction::make_fake_transaction_builder, +}; +use schist_models::{account::Account, bucket::Bucket, transaction::Transaction}; +use schist_queries as queries; + +fn given_accounts(connection: &mut SqliteConnection) -> Vec<Account> { + let accounts = vec![make_fake_account(0)]; + let _ = queries::accounts::insert_accounts(&accounts, connection); + accounts +} + +fn given_buckets(connection: &mut SqliteConnection) -> Vec<Bucket> { + let buckets = vec![make_fake_bucket(0), make_fake_bucket(1)]; + let _ = queries::buckets::insert_buckets(&buckets, connection); + buckets +} + +fn given_transactions(connection: &mut SqliteConnection) -> Vec<Transaction> { + let transactions = vec![ + make_fake_transaction_builder(0) + .account_id(0) + .build() + .unwrap(), + make_fake_transaction_builder(1) + .account_id(0) + .build() + .unwrap(), + ]; + let _ = queries::transactions::insert_transactions(&transactions, connection); + transactions +} + +#[test] +fn when_insert_pipes_then_returns_num_pipes() { + let context = TestContext::new(); + let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); + given_accounts(connection); + given_buckets(connection); + given_transactions(connection); + let pipes = [ + make_fake_pipe_builder(0).bucket_id(0).build().unwrap(), + make_fake_pipe_builder(1).bucket_id(0).build().unwrap(), + ]; + + let num_pipes_inserted = queries::pipes::insert_pipes(&pipes, connection).unwrap(); + + assert_eq!(num_pipes_inserted, 2); +} + +#[test] +fn given_pipes_when_get_all_pipes_then_return_pipes() { + let context = TestContext::new(); + let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); + given_accounts(connection); + given_buckets(connection); + given_transactions(connection); + let pipes = [ + make_fake_pipe_builder(0).bucket_id(0).build().unwrap(), + make_fake_pipe_builder(1).bucket_id(0).build().unwrap(), + ]; + let _ = queries::pipes::insert_pipes(&pipes, connection); + + let pipes_in_db = queries::pipes::get_all_pipes(connection).unwrap(); + + assert_eq!(pipes_in_db.len(), pipes.len()); + for pipe in &pipes { + assert!(pipes_in_db.iter().any(|pipe_in_db| pipe_in_db.eq(&pipe))); + } + for pipe_in_db in pipes_in_db { + assert!(pipes.iter().any(|pipe| pipe.eq(&pipe_in_db))); + } +} + +#[test] +fn given_pipes_when_delete_all_pipes_then_return_num_of_pipes() { + let context = TestContext::new(); + let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); + given_accounts(connection); + given_buckets(connection); + given_transactions(connection); + let pipes = [ + make_fake_pipe_builder(0).bucket_id(0).build().unwrap(), + make_fake_pipe_builder(1).bucket_id(0).build().unwrap(), + ]; + let _ = queries::pipes::insert_pipes(&pipes, connection); + + let num_pipes_deleted = queries::pipes::delete_all_pipes(connection).unwrap(); + + assert_eq!(num_pipes_deleted, pipes.len()); +} + +#[test] +fn given_deleted_pipes_when_get_all_pipes_then_return_empty() { + let context = TestContext::new(); + let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); + given_accounts(connection); + given_buckets(connection); + given_transactions(connection); + let pipes = [ + make_fake_pipe_builder(0).bucket_id(0).build().unwrap(), + make_fake_pipe_builder(1).bucket_id(0).build().unwrap(), + ]; + let _ = queries::pipes::insert_pipes(&pipes, connection); + let _ = queries::pipes::delete_all_pipes(connection); + + let pipes_returned = queries::pipes::get_all_pipes(connection).unwrap(); + + assert_eq!(pipes_returned.len(), 0); +} + +#[test] +fn given_pipes_when_sum_quantity_per_bucket_id_then_returns_sum_quantity_per_bucket_id() { + let context = TestContext::new(); + let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); + given_accounts(connection); + let buckets = given_buckets(connection); + given_transactions(connection); + let pipes = [ + make_fake_pipe_builder(0) + .bucket_id(0) + .amount(1) + .build() + .unwrap(), + make_fake_pipe_builder(1) + .bucket_id(0) + .amount(2) + .build() + .unwrap(), + make_fake_pipe_builder(2) + .bucket_id(1) + .amount(4) + .build() + .unwrap(), + ]; + let _ = queries::pipes::insert_pipes(&pipes, connection); + + let sums = queries::pipes::sum_pipes_flow_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/transaction_categorisations.rs b/schist_core/schist_queries/tests/transaction_categorisations.rs deleted file mode 100644 index bb88896..0000000 --- a/schist_core/schist_queries/tests/transaction_categorisations.rs +++ /dev/null @@ -1,130 +0,0 @@ -mod common; - -use common::test_context::TestContext; -use diesel::{Connection, SqliteConnection}; -use schist_fakes::{account::make_fake_account, bucket::make_fake_bucket, transaction::make_fake_transaction_builder, transaction_categorisation::make_fake_transaction_categorisation_builder}; -use schist_models::{account::Account, bucket::Bucket, transaction::Transaction}; -use schist_queries as queries; - -fn given_accounts(connection: &mut SqliteConnection) -> Vec<Account> { - let accounts = vec![make_fake_account(0)]; - let _ = queries::accounts::insert_accounts(&accounts, connection); - accounts -} - -fn given_buckets(connection: &mut SqliteConnection) -> Vec<Bucket> { - let buckets = vec![make_fake_bucket(0), make_fake_bucket(1)]; - let _ = queries::buckets::insert_buckets(&buckets, connection); - buckets -} - -fn given_transactions(connection: &mut SqliteConnection) -> Vec<Transaction> { - let transactions = vec![ - make_fake_transaction_builder(0).account_id(0).build().unwrap(), - make_fake_transaction_builder(1).account_id(0).build().unwrap(), - ]; - let _ = queries::transactions::insert_transactions(&transactions, connection); - transactions -} - -#[test] -fn when_insert_transaction_categorisations_then_returns_num_transaction_categorisations() { - let context = TestContext::new(); - let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); - given_accounts(connection); - given_buckets(connection); - given_transactions(connection); - let transaction_categorisations = [ - make_fake_transaction_categorisation_builder(0).bucket_id(0).build().unwrap(), - make_fake_transaction_categorisation_builder(1).bucket_id(0).build().unwrap(), - ]; - - let num_transaction_categorisations_inserted = queries::transaction_categorisations::insert_transaction_categorisations(&transaction_categorisations, connection).unwrap(); - - assert_eq!(num_transaction_categorisations_inserted, 2); -} - -#[test] -fn given_transaction_categorisations_when_get_all_transaction_categorisations_then_return_transaction_categorisations() { - let context = TestContext::new(); - let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); - given_accounts(connection); - given_buckets(connection); - given_transactions(connection); - let transaction_categorisations = [ - make_fake_transaction_categorisation_builder(0).bucket_id(0).build().unwrap(), - make_fake_transaction_categorisation_builder(1).bucket_id(0).build().unwrap(), - ]; - let _ = queries::transaction_categorisations::insert_transaction_categorisations(&transaction_categorisations, connection); - - let transaction_categorisations_in_db = queries::transaction_categorisations::get_all_transaction_categorisations(connection).unwrap(); - - assert_eq!(transaction_categorisations_in_db.len(), transaction_categorisations.len()); - for transaction_categorisation in &transaction_categorisations { - assert!(transaction_categorisations_in_db.iter().any(|transaction_categorisation_in_db| transaction_categorisation_in_db.eq(&transaction_categorisation))); - } - for transaction_categorisation_in_db in transaction_categorisations_in_db { - assert!(transaction_categorisations.iter().any(|transaction_categorisation| transaction_categorisation.eq(&transaction_categorisation_in_db))); - } -} - -#[test] -fn given_transaction_categorisations_when_delete_all_transaction_categorisations_then_return_num_of_transaction_categorisations() { - let context = TestContext::new(); - let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); - given_accounts(connection); - given_buckets(connection); - given_transactions(connection); - let transaction_categorisations = [ - make_fake_transaction_categorisation_builder(0).bucket_id(0).build().unwrap(), - make_fake_transaction_categorisation_builder(1).bucket_id(0).build().unwrap(), - ]; - let _ = queries::transaction_categorisations::insert_transaction_categorisations(&transaction_categorisations, connection); - - let num_transaction_categorisations_deleted = queries::transaction_categorisations::delete_all_transaction_categorisations(connection).unwrap(); - - assert_eq!(num_transaction_categorisations_deleted, transaction_categorisations.len()); -} - -#[test] -fn given_deleted_transaction_categorisations_when_get_all_transaction_categorisations_then_return_empty() { - let context = TestContext::new(); - let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); - given_accounts(connection); - given_buckets(connection); - given_transactions(connection); - let transaction_categorisations = [ - make_fake_transaction_categorisation_builder(0).bucket_id(0).build().unwrap(), - make_fake_transaction_categorisation_builder(1).bucket_id(0).build().unwrap(), - ]; - let _ = queries::transaction_categorisations::insert_transaction_categorisations(&transaction_categorisations, connection); - let _ = queries::transaction_categorisations::delete_all_transaction_categorisations(connection); - - let transaction_categorisations_returned = queries::transaction_categorisations::get_all_transaction_categorisations(connection).unwrap(); - - assert_eq!(transaction_categorisations_returned.len(), 0); -} - -#[test] -fn given_transaction_categorisations_when_sum_quantity_per_bucket_id_then_returns_sum_quantity_per_bucket_id() { - let context = TestContext::new(); - let connection = &mut SqliteConnection::establish(&context.db_url).unwrap(); - given_accounts(connection); - let buckets = given_buckets(connection); - given_transactions(connection); - let transaction_categorisations = [ - make_fake_transaction_categorisation_builder(0).bucket_id(0).quantity(1).build().unwrap(), - make_fake_transaction_categorisation_builder(1).bucket_id(0).quantity(2).build().unwrap(), - make_fake_transaction_categorisation_builder(2).bucket_id(1).quantity(4).build().unwrap(), - ]; - let _ = queries::transaction_categorisations::insert_transaction_categorisations(&transaction_categorisations, connection); - - let sums = queries::transaction_categorisations::sum_transaction_categorisation_quantity_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_desktop_gui/Cargo.toml b/schist_desktop_gui/Cargo.toml index b7b80da..35e6521 100644 --- a/schist_desktop_gui/Cargo.toml +++ b/schist_desktop_gui/Cargo.toml @@ -5,7 +5,13 @@ edition = "2021" [dependencies] anyhow = { workspace = true } +diesel = { workspace = true } +diesel_migrations = { workspace = true } +dirs = { workspace = true } iced = { workspace = true } itertools = { workspace = true } +libsqlite3-sys = { workspace = true } schist_fakes = { workspace = true } schist_models = { workspace = true } +schist_queries = { workspace = true } +schist_schema = { workspace = true } diff --git a/schist_desktop_gui/src/connection.rs b/schist_desktop_gui/src/connection.rs new file mode 100644 index 0000000..5a88083 --- /dev/null +++ b/schist_desktop_gui/src/connection.rs @@ -0,0 +1,20 @@ +use std::{fs::create_dir_all, path::Path}; + +use anyhow::Context; +use diesel::{Connection, SqliteConnection}; + +const SCHIST_DATA_DIR: &'static str = "schist"; +const SCHIST_DATABASE_FILENAME: &'static str = "schist.sqlite"; + +pub fn establish_connection() -> anyhow::Result<SqliteConnection> { + let data_dir = dirs::data_dir().context("Failed to get local app data directory")?; + let schist_local_dir = data_dir.join(Path::new(SCHIST_DATA_DIR)); + create_dir_all(&schist_local_dir) + .context(format!("Failed to create local files directory at {}", schist_local_dir.display()))?; + let db_path = schist_local_dir.join(Path::new(SCHIST_DATABASE_FILENAME)); + let db_path = db_path + .to_str() + .context("Failed to construct database path")?; + Ok(SqliteConnection::establish(db_path) + .with_context(|| format!("Failed to connect to database at {}", db_path))?) +} diff --git a/schist_desktop_gui/src/dummy_data.rs b/schist_desktop_gui/src/dummy_data.rs deleted file mode 100644 index 079b687..0000000 --- a/schist_desktop_gui/src/dummy_data.rs +++ /dev/null @@ -1,70 +0,0 @@ -use schist_models::{ - account::Account, bucket::Bucket, budget_period_unit::BudgetPeriodUnit, date_utc::DateUtc, -}; - -#[derive(Clone, Debug)] -pub struct Error {} - -impl From<anyhow::Error> for Error { - fn from(_value: anyhow::Error) -> Self { - Error {} - } -} - -pub async fn fetch_accounts() -> Result<Vec<Account>, Error> { - Ok(vec![ - Account { - id: 0, - name: String::from("Nationwide current account"), - opening_balance: 0, - opening_date: DateUtc::from_ymd(2024, 06, 12)?, - }, - Account { - id: 1, - name: String::from("cash account"), - opening_balance: 0, - opening_date: DateUtc::from_ymd(2024, 06, 12)?, - }, - Account { - id: 2, - name: String::from("YBS savings account"), - opening_balance: 0, - opening_date: DateUtc::from_ymd(2024, 06, 12)?, - }, - ]) -} - -pub async fn fetch_buckets() -> Result<Vec<Bucket>, Error> { - Ok(vec![ - Bucket { - id: 0, - name: String::from("groceries"), - balance: 123, - balance_date: DateUtc::from_ymd(2025, 8, 10)?, - budget_period: 1, - budget_period_unit: BudgetPeriodUnit::Month, - budget_quantity: 300, - group: String::from("normal expenses"), - }, - Bucket { - id: 1, - name: String::from("outdoor trips"), - balance: -11, - balance_date: DateUtc::from_ymd(2025, 8, 10)?, - budget_period: 1, - budget_period_unit: BudgetPeriodUnit::Month, - budget_quantity: 110, - group: String::from("outdoors"), - }, - Bucket { - id: 2, - name: String::from("salary"), - balance: 1018, - balance_date: DateUtc::from_ymd(2025, 8, 10)?, - budget_period: 1, - budget_period_unit: BudgetPeriodUnit::Month, - budget_quantity: -2196, - group: String::from("income"), - }, - ]) -} diff --git a/schist_desktop_gui/src/gui.rs b/schist_desktop_gui/src/gui.rs index 0f84f41..d22e999 100644 --- a/schist_desktop_gui/src/gui.rs +++ b/schist_desktop_gui/src/gui.rs @@ -1,12 +1,13 @@ pub mod components; pub mod screens; +use diesel::SqliteConnection; use iced::Element; use schist_models::{account::Account, bucket::Bucket}; +use schist_queries::{accounts::get_all_accounts, buckets::get_all_buckets}; use crate::{ config::Config, - dummy_data::{self, fetch_accounts, fetch_buckets}, gui::screens::{main_screen, MainScreen, Screen}, shortcut::KeyBind, traits::{Component, Viewable}, @@ -16,8 +17,23 @@ use crate::{ pub enum Message { Event(iced::Event), MainScreenMessage(main_screen::Message), - RefetchedAccounts(Result<Vec<Account>, dummy_data::Error>), - RefetchedBuckets(Result<Vec<Bucket>, dummy_data::Error>), + RefetchedAccounts(FetchResult<Vec<Account>>), + RefetchedBuckets(FetchResult<Vec<Bucket>>), +} + +pub type FetchResult<T> = std::result::Result<T, FetchError>; + +#[derive(Clone, Debug)] +pub struct FetchError { + pub message: String, +} + +impl FetchError { + pub fn new(err: anyhow::Error) -> Self { + Self { + message: format!("{}", err), + } + } } pub struct Gui { @@ -28,25 +44,30 @@ pub struct Gui { } impl Gui { - pub fn new() -> (Self, iced::Task<Message>) { - let tasks = vec![ - iced::Task::perform(fetch_buckets(), Message::RefetchedBuckets), - iced::Task::perform(fetch_accounts(), Message::RefetchedAccounts), - ]; + pub fn new(connection: &mut SqliteConnection) -> (Self, iced::Task<Message>) { let buckets = Vec::new(); let accounts = Vec::new(); - let gui = Self { + let mut gui = Self { active_screen: Screen::Main, buckets: buckets.clone(), main_screen: MainScreen::new(buckets, accounts), config: Config::default(), }; - (gui, iced::Task::batch(tasks)) + + let buckets = get_all_buckets(connection).map_err(FetchError::new); + let accounts = get_all_accounts(connection).map_err(FetchError::new); + + let tasks = iced::Task::batch(vec![ + gui.update(Message::RefetchedBuckets(buckets)), + gui.update(Message::RefetchedAccounts(accounts)), + ]); + + (gui, tasks) } } impl Gui { - pub fn view(&self) -> Element<Message> { + pub fn view<'a>(&'a self) -> Element<'a, Message> { match self.active_screen { Screen::Main => self.main_screen.view().map(Message::MainScreenMessage), } @@ -109,7 +130,7 @@ impl Gui { self.main_screen .update(main_screen::Message::SetBuckets(buckets)); } - Err(err) => println!("Failed to get buckets: {:?}", err), + Err(err) => println!("Failed to get buckets: {:?}", err.message), }; iced::Task::none() } @@ -119,7 +140,7 @@ impl Gui { self.main_screen .update(main_screen::Message::SetAccounts(accounts)); } - Err(err) => println!("Failed to get accounts: {:?}", err), + Err(err) => println!("Failed to get accounts: {:?}", err.message), } iced::Task::none() } 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 fa2dcac..b31e0e2 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 @@ -19,6 +19,10 @@ impl BucketNameAndBalance { impl<'a> Into<Element<'a, navigation::Message<BucketNameAndBalance>>> for BucketNameAndBalance { fn into(self) -> Element<'a, navigation::Message<BucketNameAndBalance>> { - row![text(self.bucket.name.clone()), text(self.bucket.balance)].into() + row![ + text(self.bucket.name.clone()), + text(self.bucket.balance.unwrap_or(0)) + ] + .into() } } diff --git a/schist_desktop_gui/src/gui/components/transactions_view.rs b/schist_desktop_gui/src/gui/components/transactions_view.rs index 6181a14..3a8333c 100644 --- a/schist_desktop_gui/src/gui/components/transactions_view.rs +++ b/schist_desktop_gui/src/gui/components/transactions_view.rs @@ -43,7 +43,7 @@ impl TransactionsView { } impl<'a> Viewable<'a, Message> for TransactionsView { - fn view(&self) -> Element<Message> { + fn view(&'a self) -> Element<'a, Message> { let navigation = self.navigation.view().map(Message::NavigationMessage); let main_content = column![iced::widget::text(self.greeting.clone())]; row![ diff --git a/schist_desktop_gui/src/main.rs b/schist_desktop_gui/src/main.rs index 7e54ddb..24aedc0 100644 --- a/schist_desktop_gui/src/main.rs +++ b/schist_desktop_gui/src/main.rs @@ -1,5 +1,5 @@ mod config; -mod dummy_data; +mod connection; mod gui; mod settings; mod shortcut; @@ -8,17 +8,31 @@ mod theme; mod traits; mod window_settings; +use anyhow::Context; +use diesel_migrations::MigrationHarness; +use schist_schema::migrations::MIGRATIONS; + use crate::{ - config::Config, gui::Gui, settings::make_settings, theme::make_theme, + config::Config, + connection::establish_connection, + gui::Gui, + settings::make_settings, + theme::make_theme, window_settings::make_window_settings, }; -fn main() -> iced::Result { +fn main() -> anyhow::Result<()> { let config = Config::default(); - iced::application("Schist", Gui::update, Gui::view) - .settings(make_settings(&config.clone())) - .subscription(gui::subscription) - .theme(make_theme) - .window(make_window_settings(&config.clone())) - .run_with(Gui::new) + let mut connection = establish_connection() + .context("Failed to establish database connection")?; + connection.run_pending_migrations(MIGRATIONS) + .expect("Failed to run pending database migrations"); + +iced::application("Schist", Gui::update, Gui::view) + .settings(make_settings(&config.clone())) + .subscription(gui::subscription) + .theme(make_theme) + .window(make_window_settings(&config.clone())) + .run_with(move || Gui::new(&mut connection)) + .context("Failed to run Schist GUI") } 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 a6c850e..d5f7e6f 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,9 @@ use anyhow::Result; use diesel::SqliteConnection; use schist_models::{ - account::Account, account_transfer::AccountTransfer, budget_drip::BudgetDrip, bucket::Bucket, bucket_transfer::BucketTransfer, date_utc::DateUtc, transaction::Transaction, transaction_categorisation::TransactionCategorisation + account::Account, account_transfer::AccountTransfer, bucket::Bucket, + bucket_transfer::BucketTransfer, date_utc::DateUtc, drip::Drip, pipe::Pipe, + transaction::Transaction, }; pub struct SchistState { @@ -9,20 +11,32 @@ pub struct SchistState { pub accounts: Vec<Account>, pub account_transfers: Vec<AccountTransfer>, pub buckets: Vec<Bucket>, - pub budget_drips: Vec<BudgetDrip>, + pub budget_drips: Vec<Drip>, pub transactions: Vec<Transaction>, - pub transaction_categorisations: Vec<TransactionCategorisation>, + pub pipes: Vec<Pipe>, pub bucket_transfers: Vec<BucketTransfer>, } -pub fn export_schist_state(schist_state: &SchistState, schist_connection: &mut SqliteConnection) -> Result<()> { +pub fn export_schist_state( + schist_state: &SchistState, + schist_connection: &mut SqliteConnection, +) -> Result<()> { schist_queries::clear::clear(schist_connection)?; schist_queries::accounts::insert_accounts(&schist_state.accounts, schist_connection)?; - schist_queries::account_transfers::insert_account_transfers(&schist_state.account_transfers, schist_connection)?; + schist_queries::account_transfers::insert_account_transfers( + &schist_state.account_transfers, + schist_connection, + )?; schist_queries::buckets::insert_buckets(&schist_state.buckets, schist_connection)?; - schist_queries::budget_drips::insert_budget_drips(&schist_state.budget_drips, schist_connection)?; - schist_queries::transactions::insert_transactions(&schist_state.transactions, schist_connection)?; - schist_queries::transaction_categorisations::insert_transaction_categorisations(&schist_state.transaction_categorisations, schist_connection)?; - schist_queries::bucket_transfers::insert_bucket_transfers(&schist_state.bucket_transfers, schist_connection)?; + schist_queries::drips::insert_drips(&schist_state.budget_drips, schist_connection)?; + schist_queries::transactions::insert_transactions( + &schist_state.transactions, + schist_connection, + )?; + schist_queries::pipes::insert_pipes(&schist_state.pipes, schist_connection)?; + schist_queries::bucket_transfers::insert_bucket_transfers( + &schist_state.bucket_transfers, + schist_connection, + )?; Ok(()) } diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/mod.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/mod.rs index 8787699..5fadc2c 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/mod.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/mod.rs @@ -9,17 +9,19 @@ mod transform_date; mod transform_id; mod transform_state; mod transform_transactions; +mod transform_zero_budgets; use is_account_transfer::is_account_transfer; use is_account_transfer::is_not_account_transfer; use is_category_transfer::is_category_transfer; use is_category_transfer::is_not_category_transfer; -use transform_accounts::transform_accounts; use transform_account_transfers::transform_account_transfers; +use transform_accounts::transform_accounts; use transform_categories::transform_categories; use transform_category_transfers::transform_category_transfers; use transform_date::transform_date; use transform_id::transform_id; use transform_transactions::transform_transactions; +use transform_zero_budgets::transform_zero_budgets; pub use transform_state::transform_state; diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_account_transfers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_account_transfers.rs index a0c426d..3a9d4b2 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_account_transfers.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_account_transfers.rs @@ -3,7 +3,10 @@ use anyhow::{Context, Result}; use itertools::Itertools; use schist_models::account_transfer::AccountTransfer; -use super::{is_account_transfer, is_not_account_transfer, transform_date::transform_date, transform_id::transform_id}; +use super::{ + is_account_transfer, is_not_account_transfer, transform_date::transform_date, + transform_id::transform_id, +}; pub fn transform_account_transfers( actualbudget_transactions: &[ActualbudgetTransaction], @@ -15,7 +18,10 @@ pub fn transform_account_transfers( pub fn transform_account_transfers_without_context( actualbudget_transactions: &[ActualbudgetTransaction], ) -> Result<(Vec<ActualbudgetTransaction>, Vec<AccountTransfer>)> { - let mut account_transfer_transaction_pairs: Vec<(ActualbudgetTransaction, ActualbudgetTransaction)> = Vec::new(); + let mut account_transfer_transaction_pairs: Vec<( + ActualbudgetTransaction, + ActualbudgetTransaction, + )> = Vec::new(); let mut transactions_not_transformed: Vec<ActualbudgetTransaction> = Vec::new(); let account_transfer_transactions = actualbudget_transactions .iter() @@ -28,9 +34,11 @@ pub fn transform_account_transfers_without_context( continue; } - let is_already_transformed = |t: &ActualbudgetTransaction| + let is_already_transformed = |t: &ActualbudgetTransaction| { account_transfer_transaction_pairs - .iter().any(|(t1, t2)| *t1 == *t || *t2 == *t); + .iter() + .any(|(t1, t2)| *t1 == *t || *t2 == *t) + }; if is_already_transformed(actualbudget_transaction) { continue; @@ -38,10 +46,12 @@ pub fn transform_account_transfers_without_context( let companion = account_transfer_transactions .clone() - .filter(|t| !is_already_transformed(t) - && t.date == actualbudget_transaction.date - && t.amount == -actualbudget_transaction.amount - ).last(); + .filter(|t| { + !is_already_transformed(t) + && t.date == actualbudget_transaction.date + && t.amount == -actualbudget_transaction.amount + }) + .last(); if companion.is_none() { transactions_not_transformed.push(actualbudget_transaction.clone()); @@ -68,23 +78,31 @@ pub fn transform_account_transfers_without_context( } fn transform_account_transfer( - (actualbudget_transaction_from, actualbudget_transaction_to): - &(ActualbudgetTransaction, ActualbudgetTransaction), + (actualbudget_transaction_from, actualbudget_transaction_to): &( + ActualbudgetTransaction, + ActualbudgetTransaction, + ), ) -> Result<AccountTransfer> { - let context = || format!( - "failed to transform account transfer from transactions ({:?}, {:?})", - actualbudget_transaction_from, - actualbudget_transaction_to, - ); + let context = || { + format!( + "failed to transform account transfer from transactions ({:?}, {:?})", + actualbudget_transaction_from, actualbudget_transaction_to, + ) + }; Ok(AccountTransfer { id: transform_id(&actualbudget_transaction_from.id), - date: transform_date(&actualbudget_transaction_from.date) - .with_context(context)?, - description: actualbudget_transaction_from.notes.clone().unwrap_or_else(|| - actualbudget_transaction_to.notes.clone().unwrap_or_else(String::new) - ), - quantity: actualbudget_transaction_to.amount, + date: transform_date(&actualbudget_transaction_from.date).with_context(context)?, + description: actualbudget_transaction_from + .notes + .clone() + .unwrap_or_else(|| { + actualbudget_transaction_to + .notes + .clone() + .unwrap_or_else(String::new) + }), + amount: actualbudget_transaction_to.amount, from_account_id: transform_id(actualbudget_transaction_from.account_id.as_str()), to_account_id: transform_id(actualbudget_transaction_to.account_id.as_str()), }) @@ -129,9 +147,12 @@ mod test { assert_eq!(0, remaining_transactions.len()); assert_eq!(1, account_transfers.len()); let account_transfer = account_transfers.get(0).unwrap(); - assert_eq!("2024-08-01".parse::<DateUtc>().unwrap(), account_transfer.date); + assert_eq!( + "2024-08-01".parse::<DateUtc>().unwrap(), + account_transfer.date + ); assert_eq!("My notes", account_transfer.description); - assert_eq!(100, account_transfer.quantity); + assert_eq!(100, account_transfer.amount); assert_eq!(transform_id("0"), account_transfer.from_account_id); assert_eq!(transform_id("1"), account_transfer.to_account_id); } @@ -165,9 +186,12 @@ mod test { assert_eq!(0, remaining_transactions.len()); assert_eq!(1, account_transfers.len()); let account_transfer = account_transfers.get(0).unwrap(); - assert_eq!("2024-08-01".parse::<DateUtc>().unwrap(), account_transfer.date); + assert_eq!( + "2024-08-01".parse::<DateUtc>().unwrap(), + account_transfer.date + ); assert_eq!("My notes", account_transfer.description); - assert_eq!(100, account_transfer.quantity); + assert_eq!(100, account_transfer.amount); assert_eq!(transform_id("0"), account_transfer.from_account_id); assert_eq!(transform_id("1"), account_transfer.to_account_id); } @@ -219,20 +243,26 @@ mod test { let account_transfer_0 = account_transfers .iter() - .filter(|at| at.quantity == 100) + .filter(|at| at.amount == 100) .last() .unwrap(); - assert_eq!("2024-08-01".parse::<DateUtc>().unwrap(), account_transfer_0.date); + assert_eq!( + "2024-08-01".parse::<DateUtc>().unwrap(), + account_transfer_0.date + ); assert_eq!(transform_id("0"), account_transfer_0.from_account_id); assert_eq!(transform_id("1"), account_transfer_0.to_account_id); assert_eq!("My notes 1", account_transfer_0.description); let account_transfer_1 = account_transfers .iter() - .filter(|at| at.quantity == 200) + .filter(|at| at.amount == 200) .last() .unwrap(); - assert_eq!("2024-08-01".parse::<DateUtc>().unwrap(), account_transfer_1.date); + assert_eq!( + "2024-08-01".parse::<DateUtc>().unwrap(), + account_transfer_1.date + ); assert_eq!(transform_id("0"), account_transfer_1.from_account_id); assert_eq!(transform_id("1"), account_transfer_1.to_account_id); assert_eq!("My notes 2", account_transfer_1.description); @@ -288,7 +318,7 @@ mod test { .filter(|at| at.date == "2024-08-01".parse().unwrap()) .last() .unwrap(); - assert_eq!(100, account_transfer_0.quantity); + assert_eq!(100, account_transfer_0.amount); assert_eq!(transform_id("0"), account_transfer_0.from_account_id); assert_eq!(transform_id("1"), account_transfer_0.to_account_id); assert_eq!("My notes 1", account_transfer_0.description); @@ -298,7 +328,7 @@ mod test { .filter(|at| at.date == "2024-08-02".parse().unwrap()) .last() .unwrap(); - assert_eq!(100, account_transfer_1.quantity); + assert_eq!(100, account_transfer_1.amount); assert_eq!(transform_id("0"), account_transfer_1.from_account_id); assert_eq!(transform_id("1"), account_transfer_1.to_account_id); assert_eq!("My notes 2", account_transfer_1.description); diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_budget_drips.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_budget_drips.rs index 01289a0..6fd7610 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_budget_drips.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_budget_drips.rs @@ -3,40 +3,46 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use actualbudget_models::ActualbudgetZeroBudget; use anyhow::{Context, Result}; use itertools::Itertools; -use schist_models::{budget_drip::BudgetDrip, date_utc::DateUtc}; +use schist_models::{date_utc::DateUtc, drip::Drip}; use schist_traits::dateable::Dateable; use super::{transform_date::transform_date, transform_id::transform_id}; -pub fn transform_budget_drips(actualbudget_zero_budgets: &[ActualbudgetZeroBudget]) -> Result<Vec<BudgetDrip>> { +pub fn transform_budget_drips( + actualbudget_zero_budgets: &[ActualbudgetZeroBudget], +) -> Result<Vec<Drip>> { let non_zero_actualbudget_zero_budgets: Vec<ActualbudgetZeroBudget> = actualbudget_zero_budgets .iter() .filter(|zb| zb.amount != 0) .cloned() .collect(); - let budget_drips = group_actualbudget_zero_budgets_by_category_id( - &non_zero_actualbudget_zero_budgets, - ).iter() - .map(sort_actualbudget_zero_budgets_by_month) - .flat_map(|sorted_actualbudget_zero_budgets| sorted_actualbudget_zero_budgets + let budget_drips = + group_actualbudget_zero_budgets_by_category_id(&non_zero_actualbudget_zero_budgets) .iter() - .map(map_actualbudget_zero_budget_to_budget_drips) - .collect::<Result<Vec<Vec<BudgetDrip>>>>() - ).flatten() - .flatten() - .filter(|bd| bd.quantity != 0) - .collect(); + .map(sort_actualbudget_zero_budgets_by_month) + .flat_map(|sorted_actualbudget_zero_budgets| { + sorted_actualbudget_zero_budgets + .iter() + .map(map_actualbudget_zero_budget_to_budget_drips) + .collect::<Result<Vec<Vec<Drip>>>>() + }) + .flatten() + .flatten() + .filter(|bd| bd.amount != 0) + .collect(); Ok(budget_drips) } fn map_actualbudget_zero_budget_to_budget_drips( actualbudget_zero_budget: &ActualbudgetZeroBudget, -) -> Result<Vec<BudgetDrip>> { +) -> Result<Vec<Drip>> { let context = || "failed to map actualbudget zero budgets to budget drips"; - let first_day_of_month: DateUtc = Into::<DateUtc>::into(transform_date(&actualbudget_zero_budget.month).with_context(context)?); + let first_day_of_month: DateUtc = Into::<DateUtc>::into( + transform_date(&actualbudget_zero_budget.month).with_context(context)?, + ); let month = first_day_of_month.month(); let days_in_month: Vec<DateUtc> = (0..31) .map(|i| first_day_of_month.add_days(i)) @@ -52,21 +58,29 @@ fn map_actualbudget_zero_budget_to_budget_drips( // 31 elements, so we should be grand let num_days_in_month: i32 = days_in_month.len().try_into().unwrap(); - let quantity = actualbudget_zero_budget.amount / num_days_in_month; - let remainder_quantity = actualbudget_zero_budget.amount - (quantity * num_days_in_month); + let amount = actualbudget_zero_budget.amount / num_days_in_month; + let remainder_amount = actualbudget_zero_budget.amount - (amount * num_days_in_month); - let budget_drips: Vec<BudgetDrip> = days_in_month.iter().map(|&date| - BudgetDrip { + let budget_drips: Vec<Drip> = days_in_month + .iter() + .map(|&date| Drip { id: transform_budget_drip_id(&actualbudget_zero_budget, &date), bucket_id: transform_id(&actualbudget_zero_budget.category_id), date, - quantity: if date == first_day_of_month { - quantity + remainder_quantity + amount: if date == first_day_of_month { + amount + remainder_amount } else { - quantity - } - } - ).collect(); + amount + }, + inserted_at_unix_seconds: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("The current time should be after the Unix epoch") + .as_secs() + .try_into() + .expect("The current time's timestamp in seconds should fit into a 32-bit signed integer"), + pipe_id: 0, + }) + .collect(); Ok(budget_drips) } @@ -88,12 +102,14 @@ fn group_actualbudget_zero_budgets_by_category_id( .unique(); category_ids - .map(|category_id| actualbudget_zero_budgets - .iter() - .filter(|zb| zb.category_id == category_id) - .map(ActualbudgetZeroBudget::clone) - .collect() - ).collect() + .map(|category_id| { + actualbudget_zero_budgets + .iter() + .filter(|zb| zb.category_id == category_id) + .map(ActualbudgetZeroBudget::clone) + .collect() + }) + .collect() } fn transform_budget_drip_id( @@ -112,7 +128,7 @@ fn transform_budget_drip_id( mod test { use actualbudget_fakes::actualbudget_zero_budget::make_fake_actualbudget_zero_budget_builder; use actualbudget_models::ActualbudgetDate; - use schist_models::budget_drip::BudgetDrip; + use schist_models::drip::Drip; use schist_traits::dateable::Dateable; use crate::transform_state::transform_id::transform_id; @@ -122,7 +138,7 @@ mod test { mod map_actualbudget_zero_budget_to_budget_drips { use actualbudget_fakes::actualbudget_zero_budget::make_fake_actualbudget_zero_budget_builder; use actualbudget_models::ActualbudgetDate; - use schist_models::budget_drip::BudgetDrip; + use schist_models::drip::Drip; use schist_traits::dateable::Dateable; use crate::transform_state::transform_budget_drips::map_actualbudget_zero_budget_to_budget_drips; @@ -141,10 +157,13 @@ mod test { assert!(result.is_ok()); let mut budget_drips = result.unwrap(); assert_eq!(31, budget_drips.len()); - assert!(budget_drips.iter().all(|bd| bd.quantity == 100)); + assert!(budget_drips.iter().all(|bd| bd.amount == 100)); budget_drips.sort_by_key(|bd| bd.date); for i in 1..31 { - assert_eq!(Ok(i), budget_drips.get(i - 1).unwrap().date.day().try_into()); + assert_eq!( + Ok(i), + budget_drips.get(i - 1).unwrap().date.day().try_into() + ); } } @@ -163,16 +182,12 @@ mod test { let budget_drips = result.unwrap(); assert_eq!(31, budget_drips.len()); - let budget_drips_quantity_not_floored: Vec<&BudgetDrip> = budget_drips - .iter() - .filter(|bd| bd.quantity != 100) - .collect(); - assert_eq!(1, budget_drips_quantity_not_floored.len()); + let budget_drips_amount_not_floored: Vec<&Drip> = + budget_drips.iter().filter(|bd| bd.amount != 100).collect(); + assert_eq!(1, budget_drips_amount_not_floored.len()); - let budget_drips_floored: Vec<&BudgetDrip> = budget_drips - .iter() - .filter(|bd| bd.quantity == 100) - .collect(); + let budget_drips_floored: Vec<&Drip> = + budget_drips.iter().filter(|bd| bd.amount == 100).collect(); assert_eq!(30, budget_drips_floored.len()); } } @@ -187,37 +202,36 @@ mod test { #[test] fn when_one_zero_budget_with_integer_budget_per_day_then_evenly_divided_budget_drips() { - let actualbudget_zero_budgets = [ - make_fake_actualbudget_zero_budget_builder("0") - .amount(31 * 100) - .category_id(String::from("0")) - .month(ActualbudgetDate::from_i32(202401)) - .build() - .unwrap() - ]; + let actualbudget_zero_budgets = [make_fake_actualbudget_zero_budget_builder("0") + .amount(31 * 100) + .category_id(String::from("0")) + .month(ActualbudgetDate::from_i32(202401)) + .build() + .unwrap()]; let result = transform_budget_drips(&actualbudget_zero_budgets); assert!(result.is_ok()); let mut budget_drips = result.unwrap(); assert_eq!(31, budget_drips.len()); - assert!(budget_drips.iter().all(|bd| bd.quantity == 100)); + assert!(budget_drips.iter().all(|bd| bd.amount == 100)); budget_drips.sort_by_key(|bd| bd.date); for i in 1..31 { - assert_eq!(Ok(i), budget_drips.get(i - 1).unwrap().date.day().try_into()); + assert_eq!( + Ok(i), + budget_drips.get(i - 1).unwrap().date.day().try_into() + ); } } #[test] fn when_one_zero_budgets_with_non_integer_budget_per_day_then_remainder_on_one_day() { - let actualbudget_zero_budgets = [ - make_fake_actualbudget_zero_budget_builder("0") - .amount(31 * 100 + 30) - .category_id(String::from("0")) - .month(ActualbudgetDate::from_i32(202401)) - .build() - .unwrap() - ]; + let actualbudget_zero_budgets = [make_fake_actualbudget_zero_budget_builder("0") + .amount(31 * 100 + 30) + .category_id(String::from("0")) + .month(ActualbudgetDate::from_i32(202401)) + .build() + .unwrap()]; let result = transform_budget_drips(&actualbudget_zero_budgets); @@ -225,21 +239,18 @@ mod test { let budget_drips = result.unwrap(); assert_eq!(31, budget_drips.len()); - let budget_drips_quantity_not_floored: Vec<&BudgetDrip> = budget_drips - .iter() - .filter(|bd| bd.quantity != 100) - .collect(); - assert_eq!(1, budget_drips_quantity_not_floored.len()); + let budget_drips_amount_not_floored: Vec<&Drip> = + budget_drips.iter().filter(|bd| bd.amount != 100).collect(); + assert_eq!(1, budget_drips_amount_not_floored.len()); - let budget_drips_floored: Vec<&BudgetDrip> = budget_drips - .iter() - .filter(|bd| bd.quantity == 100) - .collect(); + let budget_drips_floored: Vec<&Drip> = + budget_drips.iter().filter(|bd| bd.amount == 100).collect(); assert_eq!(30, budget_drips_floored.len()); } #[test] - fn when_multiple_zero_budgets_with_integer_budget_per_day_across_multiple_categories_then_evenly_divided_budget_drips() { + fn when_multiple_zero_budgets_with_integer_budget_per_day_across_multiple_categories_then_evenly_divided_budget_drips( + ) { let actualbudget_zero_budgets = [ make_fake_actualbudget_zero_budget_builder("0") .amount(31 * 100) @@ -275,32 +286,40 @@ mod test { let budget_drips = result.unwrap(); assert_eq!(31 + 31 + 29 + 29, budget_drips.len()); - let budget_drips_jan_category_0: Vec<&BudgetDrip> = budget_drips + let budget_drips_jan_category_0: Vec<&Drip> = budget_drips .iter() .filter(|bd| bd.bucket_id == category_0_id && bd.date.month() == 1) .collect(); assert_eq!(31, budget_drips_jan_category_0.len()); - assert!(budget_drips_jan_category_0.iter().all(|bd| bd.quantity == 100)); + assert!(budget_drips_jan_category_0 + .iter() + .all(|bd| bd.amount == 100)); - let budget_drips_jan_category_1: Vec<&BudgetDrip> = budget_drips + let budget_drips_jan_category_1: Vec<&Drip> = budget_drips .iter() .filter(|bd| bd.bucket_id == category_1_id && bd.date.month() == 1) .collect(); assert_eq!(31, budget_drips_jan_category_1.len()); - assert!(budget_drips_jan_category_1.iter().all(|bd| bd.quantity == 200)); + assert!(budget_drips_jan_category_1 + .iter() + .all(|bd| bd.amount == 200)); - let budget_drips_feb_category_0: Vec<&BudgetDrip> = budget_drips + let budget_drips_feb_category_0: Vec<&Drip> = budget_drips .iter() .filter(|bd| bd.bucket_id == category_0_id && bd.date.month() == 2) .collect(); assert_eq!(29, budget_drips_feb_category_0.len()); - assert!(budget_drips_feb_category_0.iter().all(|bd| bd.quantity == 110)); + assert!(budget_drips_feb_category_0 + .iter() + .all(|bd| bd.amount == 110)); - let budget_drips_feb_category_1: Vec<&BudgetDrip> = budget_drips + let budget_drips_feb_category_1: Vec<&Drip> = budget_drips .iter() .filter(|bd| bd.bucket_id == category_1_id && bd.date.month() == 2) .collect(); assert_eq!(29, budget_drips_feb_category_1.len()); - assert!(budget_drips_feb_category_1.iter().all(|bd| bd.quantity == 210)); + assert!(budget_drips_feb_category_1 + .iter() + .all(|bd| bd.amount == 210)); } } diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_categories.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_categories.rs index 2a21860..ccc8639 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_categories.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_categories.rs @@ -1,48 +1,24 @@ use actualbudget_models::{ActualbudgetCategory, ActualbudgetZeroBudget}; -use itertools::Itertools; -use schist_models::{budget_period_unit::BudgetPeriodUnit, bucket::Bucket, date_utc::DateUtc}; +use schist_models::bucket::Bucket; use super::transform_id; -pub fn transform_categories( - actualbudget_categories: &[ActualbudgetCategory], - actualbudget_zero_budgets: &[ActualbudgetZeroBudget], - before_first_activity: &DateUtc, -) -> Vec<Bucket> { - let last_non_zero_month = actualbudget_zero_budgets +pub fn transform_categories(actualbudget_categories: &[ActualbudgetCategory]) -> Vec<Bucket> { + actualbudget_categories .iter() - .map(|zb| zb.month.clone()) - .unique() - .filter(|month| actualbudget_zero_budgets - .iter() - .any(|zb| zb.month == *month && zb.amount != 0) - ).max(); - - let actualbudget_zero_budgets = if let Some(last_non_zero_month) = last_non_zero_month { - &actualbudget_zero_budgets - .iter() - .filter(|zb| zb.month == last_non_zero_month) - .cloned() - .collect::<Vec<_>>() - } else { - actualbudget_zero_budgets - }; - - actualbudget_categories.iter().map(|actualbudget_category| Bucket { - id: transform_id(&actualbudget_category.id.clone()), - name: actualbudget_category.name.clone(), - balance: 0, - balance_date: before_first_activity.clone(), - budget_period: 1, - budget_period_unit: BudgetPeriodUnit::Month, - budget_quantity: get_budget_quantity(actualbudget_category, actualbudget_zero_budgets), - group: String::from("un-named group"), - }).collect() + .map(|actualbudget_category| Bucket { + id: transform_id(&actualbudget_category.id.clone()), + balance: None, + balance_cache_key: None, + group: String::from("un-named group"), + name: actualbudget_category.name.clone(), + }) + .collect() } fn get_budget_quantity( actualbudget_category: &ActualbudgetCategory, - actualbudget_zero_budgets: &[ActualbudgetZeroBudget] + actualbudget_zero_budgets: &[ActualbudgetZeroBudget], ) -> i32 { let latest_zero_budget_for_category = actualbudget_zero_budgets .iter() @@ -57,10 +33,7 @@ fn get_budget_quantity( #[cfg(test)] mod test { - use actualbudget_fakes::{actualbudget_category::make_fake_actualbudget_category_builder, actualbudget_zero_budget::make_fake_actualbudget_zero_budget_builder}; - use actualbudget_models::ActualbudgetDate; - use schist_models::{budget_period_unit::BudgetPeriodUnit, date_utc::DateUtc}; - use schist_traits::nowlike::Nowlike; + use actualbudget_fakes::actualbudget_category::make_fake_actualbudget_category_builder; use crate::transform_state::transform_id::transform_id; @@ -68,138 +41,103 @@ mod test { #[test] fn when_one_category_one_zero_budget_then_transforms_category() { - let actualbudget_categories = [ - make_fake_actualbudget_category_builder("0").name(String::from("Food")).build().unwrap(), - ]; - let actualbudget_zero_budgets = [ - make_fake_actualbudget_zero_budget_builder("0").amount(100).build().unwrap(), - ]; - let before_first_transaction = DateUtc::now(); + let actualbudget_categories = [make_fake_actualbudget_category_builder("0") + .name(String::from("Food")) + .build() + .unwrap()]; - let buckets = transform_categories( - &actualbudget_categories, - &actualbudget_zero_budgets, - &before_first_transaction, - ); + let buckets = transform_categories(&actualbudget_categories); assert_eq!(1, buckets.len()); let bucket = buckets.get(0).unwrap(); + assert_eq!(None, bucket.balance); + assert_eq!(None, bucket.balance_cache_key); assert_eq!("Food", bucket.name); - assert_eq!(0, bucket.balance); - assert_eq!(before_first_transaction, bucket.balance_date); - assert_eq!(1, bucket.budget_period); - assert_eq!(BudgetPeriodUnit::Month, bucket.budget_period_unit); - assert_eq!(100, bucket.budget_quantity); } #[test] fn when_one_category_no_zero_budget_then_transforms_category() { - let actualbudget_categories = [ - make_fake_actualbudget_category_builder("0").name(String::from("Food")).build().unwrap(), - ]; - let actualbudget_zero_budgets = []; - let before_first_transaction = DateUtc::now(); + let actualbudget_categories = [make_fake_actualbudget_category_builder("0") + .name(String::from("Food")) + .build() + .unwrap()]; - let buckets = transform_categories( - &actualbudget_categories, - &actualbudget_zero_budgets, - &before_first_transaction, - ); + let buckets = transform_categories(&actualbudget_categories); assert_eq!(1, buckets.len()); let bucket = buckets.get(0).unwrap(); assert_eq!("Food", bucket.name); - assert_eq!(0, bucket.balance); - assert_eq!(before_first_transaction, bucket.balance_date); - assert_eq!(1, bucket.budget_period); - assert_eq!(BudgetPeriodUnit::Month, bucket.budget_period_unit); - assert_eq!(0, bucket.budget_quantity); + assert_eq!(None, bucket.balance); + assert_eq!(None, bucket.balance_cache_key); + assert_eq!("un-named group", bucket.group); } #[test] fn when_one_category_multiple_zero_budgets_then_transforms_category() { - let actualbudget_categories = [ - make_fake_actualbudget_category_builder("0").name(String::from("Food")).build().unwrap(), - ]; - let jan = ActualbudgetDate::from_i32(202401); - let feb = ActualbudgetDate::from_i32(202402); - let mar = ActualbudgetDate::from_i32(202403); - let actualbudget_zero_budgets = [ - make_fake_actualbudget_zero_budget_builder("0").amount(100).month(jan).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("1").amount(200).month(feb).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("2").amount(300).month(mar).build().unwrap(), - ]; - let before_first_transaction = DateUtc::now(); + let actualbudget_categories = [make_fake_actualbudget_category_builder("0") + .name(String::from("Food")) + .build() + .unwrap()]; - let buckets = transform_categories( - &actualbudget_categories, - &actualbudget_zero_budgets, - &before_first_transaction, - ); + let buckets = transform_categories(&actualbudget_categories); assert_eq!(1, buckets.len()); let bucket = buckets.get(0).unwrap(); assert_eq!("Food", bucket.name); - assert_eq!(0, bucket.balance); - assert_eq!(before_first_transaction, bucket.balance_date); - assert_eq!(1, bucket.budget_period); - assert_eq!(BudgetPeriodUnit::Month, bucket.budget_period_unit); - assert_eq!(300, bucket.budget_quantity); + assert_eq!(None, bucket.balance); + assert_eq!(None, bucket.balance_cache_key); + assert_eq!("un-named group", bucket.group); } #[test] fn when_multiple_categories_multiple_zero_budgets_then_transforms_category() { let actualbudget_categories = [ - make_fake_actualbudget_category_builder("0").name(String::from("Food")).build().unwrap(), - make_fake_actualbudget_category_builder("1").name(String::from("Clothes")).build().unwrap(), - make_fake_actualbudget_category_builder("2").name(String::from("Rent")).build().unwrap(), + make_fake_actualbudget_category_builder("0") + .name(String::from("Food")) + .build() + .unwrap(), + make_fake_actualbudget_category_builder("1") + .name(String::from("Clothes")) + .build() + .unwrap(), + make_fake_actualbudget_category_builder("2") + .name(String::from("Rent")) + .build() + .unwrap(), ]; - let jan = ActualbudgetDate::from_i32(202401); - let feb = ActualbudgetDate::from_i32(202402); - let mar = ActualbudgetDate::from_i32(202403); - let actualbudget_zero_budgets = [ - make_fake_actualbudget_zero_budget_builder("0").amount(100).month(jan.clone()).category_id(String::from("0")).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("1").amount(200).month(feb.clone()).category_id(String::from("0")).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("2").amount(300).month(mar.clone()).category_id(String::from("0")).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("3").amount(110).month(jan.clone()).category_id(String::from("1")).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("4").amount(210).month(feb.clone()).category_id(String::from("1")).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("5").amount(310).month(mar.clone()).category_id(String::from("1")).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("6").amount(120).month(jan.clone()).category_id(String::from("2")).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("7").amount(220).month(feb.clone()).category_id(String::from("2")).build().unwrap(), - make_fake_actualbudget_zero_budget_builder("8").amount(320).month(mar.clone()).category_id(String::from("2")).build().unwrap(), - ]; - let before_first_transaction = DateUtc::now(); - let buckets = transform_categories( - &actualbudget_categories, - &actualbudget_zero_budgets, - &before_first_transaction, - ); + let buckets = transform_categories(&actualbudget_categories); assert_eq!(3, buckets.len()); - let bucket_0 = buckets.iter().filter(|c| c.id == transform_id("0")).last().unwrap(); + let bucket_0 = buckets + .iter() + .filter(|c| c.id == transform_id("0")) + .last() + .unwrap(); assert_eq!("Food", bucket_0.name); - assert_eq!(0, bucket_0.balance); - assert_eq!(before_first_transaction, bucket_0.balance_date); - assert_eq!(1, bucket_0.budget_period); - assert_eq!(BudgetPeriodUnit::Month, bucket_0.budget_period_unit); - assert_eq!(300, bucket_0.budget_quantity); + assert_eq!(None, bucket_0.balance); + assert_eq!(None, bucket_0.balance_cache_key); + assert_eq!("un-named group", bucket_0.group); - let bucket_1 = buckets.iter().filter(|c| c.id == transform_id("1")).last().unwrap(); + let bucket_1 = buckets + .iter() + .filter(|c| c.id == transform_id("1")) + .last() + .unwrap(); assert_eq!("Clothes", bucket_1.name); - assert_eq!(0, bucket_1.balance); - assert_eq!(before_first_transaction, bucket_1.balance_date); - assert_eq!(1, bucket_1.budget_period); - assert_eq!(BudgetPeriodUnit::Month, bucket_1.budget_period_unit); - assert_eq!(310, bucket_1.budget_quantity); + assert_eq!(None, bucket_1.balance); + assert_eq!(None, bucket_1.balance_cache_key); + assert_eq!("un-named group", bucket_1.group); - let bucket_2 = buckets.iter().filter(|c| c.id == transform_id("2")).last().unwrap(); + let bucket_2 = buckets + .iter() + .filter(|c| c.id == transform_id("2")) + .last() + .unwrap(); assert_eq!("Rent", bucket_2.name); - assert_eq!(0, bucket_2.balance); - assert_eq!(before_first_transaction, bucket_2.balance_date); - assert_eq!(1, bucket_2.budget_period); - assert_eq!(BudgetPeriodUnit::Month, bucket_2.budget_period_unit); - assert_eq!(320, bucket_2.budget_quantity); + assert_eq!(None, bucket_2.balance); + assert_eq!(None, bucket_2.balance_cache_key); + assert_eq!("un-named group", bucket_2.group); } } diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_category_transfers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_category_transfers.rs index 11ed2e3..bfbf7ac 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_category_transfers.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_category_transfers.rs @@ -1,7 +1,4 @@ -use actualbudget_models::{ - ActualbudgetAccount, - ActualbudgetTransaction, -}; +use actualbudget_models::{ActualbudgetAccount, ActualbudgetTransaction}; use anyhow::{bail, Context, Result}; use itertools::Itertools; use schist_models::bucket_transfer::BucketTransfer; @@ -12,20 +9,20 @@ pub fn transform_category_transfers( actualbudget_transactions: &[ActualbudgetTransaction], actualbudget_category_transfers_account: &Option<ActualbudgetAccount>, ) -> Result<Vec<BucketTransfer>> { - let actualbudget_category_transfers = actualbudget_transactions - .iter() - .filter(|transaction| - is_category_transfer(transaction, actualbudget_category_transfers_account) - ); - let actualbudget_parent_category_transfers: Vec<ActualbudgetTransaction> = actualbudget_category_transfers - .clone() - .filter(|t| t.is_parent) - .map(ActualbudgetTransaction::clone) - .collect(); - let actualbudget_child_category_transfers: Vec<ActualbudgetTransaction> = actualbudget_category_transfers - .filter(|t| t.is_child) - .map(ActualbudgetTransaction::clone) - .collect(); + let actualbudget_category_transfers = actualbudget_transactions.iter().filter(|transaction| { + is_category_transfer(transaction, actualbudget_category_transfers_account) + }); + let actualbudget_parent_category_transfers: Vec<ActualbudgetTransaction> = + actualbudget_category_transfers + .clone() + .filter(|t| t.is_parent) + .map(ActualbudgetTransaction::clone) + .collect(); + let actualbudget_child_category_transfers: Vec<ActualbudgetTransaction> = + actualbudget_category_transfers + .filter(|t| t.is_child) + .map(ActualbudgetTransaction::clone) + .collect(); Ok(actualbudget_parent_category_transfers .iter() @@ -33,8 +30,7 @@ pub fn transform_category_transfers( .filter_ok(Option::is_some) .map_ok(Option::unwrap) .collect::<Result<Vec<BucketTransfer>>>() - .with_context(|| "failed to transform category transfers")? - ) + .with_context(|| "failed to transform category transfers")?) } fn transform_category_transfer( @@ -44,7 +40,8 @@ fn transform_category_transfer( transform_category_transfer_without_context( actualbudget_parent_category_transfer, actualbudget_child_category_transfers, - ).with_context(|| "failed to transform category transfer") + ) + .with_context(|| "failed to transform category transfer") } fn transform_category_transfer_without_context( @@ -53,11 +50,12 @@ fn transform_category_transfer_without_context( ) -> Result<Option<BucketTransfer>> { let actualbudget_child_category_transfers = actualbudget_child_category_transfers .iter() - .filter(|t| + .filter(|t| { t.parent_id .clone() .is_some_and(|parent_id| parent_id == actualbudget_parent_category_transfer.id) - ).map(ActualbudgetTransaction::clone); + }) + .map(ActualbudgetTransaction::clone); let from_transfers: Vec<ActualbudgetTransaction> = actualbudget_child_category_transfers .clone() @@ -73,7 +71,10 @@ fn transform_category_transfer_without_context( 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); + 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()); @@ -91,12 +92,14 @@ fn transform_category_transfer_without_context( 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); + 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 - { + if from_transfer.amount != -to_transfer.amount { bail!("actualbudget category transfer amounts did not sum to zero"); } @@ -106,7 +109,7 @@ fn transform_category_transfer_without_context( .notes .clone() .unwrap_or_else(String::new), - quantity: to_transfer.amount.try_into().unwrap(), + amount: to_transfer.amount.try_into().unwrap(), from_bucket_id: from_category_id, to_bucket_id: to_category_id, })) @@ -114,7 +117,10 @@ fn transform_category_transfer_without_context( #[cfg(test)] mod test { - use actualbudget_fakes::{actualbudget_account::make_fake_actualbudget_account, actualbudget_transaction::make_fake_actualbudget_transaction_builder}; + use actualbudget_fakes::{ + actualbudget_account::make_fake_actualbudget_account, + actualbudget_transaction::make_fake_actualbudget_transaction_builder, + }; use actualbudget_models::{ActualbudgetTransaction, ActualbudgetTransactionBuilder}; use schist_models::bucket_transfer::BucketTransfer; @@ -140,7 +146,7 @@ mod test { let bucket_transfers = result.unwrap(); assert_eq!(1, bucket_transfers.len()); let bucket_transfer = bucket_transfers.get(0).unwrap(); - assert_eq!(100, bucket_transfer.quantity); + 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); @@ -151,13 +157,31 @@ mod test { let actualbudget_category_transfers_account = make_fake_actualbudget_account("0"); let actualbudget_transactions = [ make_fake_parent_transaction("0"), - make_fake_from_child_transaction_builder("1", 100, "0").category_id(Some(String::from("0"))).build().unwrap(), - make_fake_to_child_transaction_builder("2", 100, "0").category_id(Some(String::from("1"))).build().unwrap(), + make_fake_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(), + make_fake_from_child_transaction_builder("4", 200, "3") + .category_id(Some(String::from("1"))) + .build() + .unwrap(), + make_fake_to_child_transaction_builder("5", 200, "3") + .category_id(Some(String::from("0"))) + .build() + .unwrap(), + make_fake_actualbudget_transaction_builder("6") + .amount(300) + .build() + .unwrap(), + make_fake_actualbudget_transaction_builder("7") + .amount(-400) + .build() + .unwrap(), ]; let result = transform_category_transfers( @@ -171,7 +195,7 @@ mod test { let bucket_transfer_0: Vec<&BucketTransfer> = bucket_transfers .iter() - .filter(|ct| ct.quantity == 100) + .filter(|ct| ct.amount == 100) .collect(); assert_eq!(1, bucket_transfer_0.len()); let bucket_transfer_0 = bucket_transfer_0.get(0).unwrap(); @@ -180,7 +204,7 @@ mod test { let bucket_transfer_1: Vec<&BucketTransfer> = bucket_transfers .iter() - .filter(|ct| ct.quantity == 200) + .filter(|ct| ct.amount == 200) .collect(); assert_eq!(1, bucket_transfer_1.len()); let bucket_transfer_1 = bucket_transfer_1.get(0).unwrap(); @@ -193,9 +217,15 @@ mod test { let actualbudget_category_transfers_account = make_fake_actualbudget_account("0"); let actualbudget_transactions = [ make_fake_parent_transaction("0"), - make_fake_from_child_transaction_builder("1", 50, "0").build().unwrap(), - make_fake_from_child_transaction_builder("2", 50, "0").build().unwrap(), - make_fake_to_child_transaction_builder("3", 100, "0").build().unwrap(), + make_fake_from_child_transaction_builder("1", 50, "0") + .build() + .unwrap(), + make_fake_from_child_transaction_builder("2", 50, "0") + .build() + .unwrap(), + make_fake_to_child_transaction_builder("3", 100, "0") + .build() + .unwrap(), ]; let result = transform_category_transfers( @@ -211,9 +241,15 @@ mod test { let actualbudget_category_transfers_account = make_fake_actualbudget_account("0"); let actualbudget_transactions = [ make_fake_parent_transaction("0"), - make_fake_from_child_transaction_builder("1", 100, "0").build().unwrap(), - make_fake_to_child_transaction_builder("2", 50, "0").build().unwrap(), - make_fake_to_child_transaction_builder("3", 50, "0").build().unwrap(), + make_fake_from_child_transaction_builder("1", 100, "0") + .build() + .unwrap(), + make_fake_to_child_transaction_builder("2", 50, "0") + .build() + .unwrap(), + make_fake_to_child_transaction_builder("3", 50, "0") + .build() + .unwrap(), ]; let result = transform_category_transfers( @@ -261,8 +297,12 @@ mod test { let actualbudget_category_transfers_account = make_fake_actualbudget_account("0"); let actualbudget_transactions = [ make_fake_parent_transaction("0"), - make_fake_from_child_transaction_builder("1", 50, "0").build().unwrap(), - make_fake_to_child_transaction_builder("2", 100, "0").build().unwrap(), + make_fake_from_child_transaction_builder("1", 50, "0") + .build() + .unwrap(), + make_fake_to_child_transaction_builder("2", 100, "0") + .build() + .unwrap(), ]; let result = transform_category_transfers( @@ -278,13 +318,23 @@ mod test { let actualbudget_category_transfers_account = make_fake_actualbudget_account("0"); let actualbudget_transactions_0 = [ make_fake_parent_transaction("0"), - make_fake_from_child_transaction_builder("1", 100, "0").category_id(None).build().unwrap(), - make_fake_to_child_transaction_builder("2", 100, "0").build().unwrap(), + 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(), + make_fake_from_child_transaction_builder("1", 100, "0") + .build() + .unwrap(), + make_fake_to_child_transaction_builder("2", 100, "0") + .category_id(None) + .build() + .unwrap(), ]; let result_0 = transform_category_transfers( @@ -314,25 +364,37 @@ mod test { } fn make_fake_from_child_transaction(id: &str, parent_id: &str) -> ActualbudgetTransaction { - make_fake_from_child_transaction_builder(id, 100, parent_id).build().unwrap() + 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_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() + 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 { + 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")) 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 575e624..7d7f90b 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,9 +1,16 @@ use anyhow::{bail, Context, Result}; -use schist_models::{account::Account, bucket::Bucket, bucket_transfer::BucketTransfer, date_utc::DateUtc, transaction::Transaction, transaction_categorisation::TransactionCategorisation}; +use schist_models::{ + account::Account, bucket::Bucket, bucket_transfer::BucketTransfer, date_utc::DateUtc, + pipe::Pipe, transaction::Transaction, +}; use crate::{actualbudget_state::ActualbudgetState, schist_state::SchistState}; -use super::{transform_account_transfers, transform_accounts, transform_budget_drips::transform_budget_drips, transform_categories, transform_category_transfers, transform_date, transform_transactions}; +use super::{ + transform_account_transfers, transform_accounts, + transform_budget_drips::transform_budget_drips, transform_categories, + transform_category_transfers, transform_date, transform_transactions, transform_zero_budgets, +}; pub fn transform_state(actualbudget_state: ActualbudgetState) -> Result<SchistState> { transform_state_without_context(actualbudget_state) @@ -11,30 +18,23 @@ pub fn transform_state(actualbudget_state: ActualbudgetState) -> Result<SchistSt } fn transform_state_without_context(actualbudget_state: ActualbudgetState) -> Result<SchistState> { - let (remaining_transactions, account_transfers) = transform_account_transfers( - &actualbudget_state.actualbudget_transactions, - )?; - let first_activity_date = - transform_date(&actualbudget_state.actualbudget_first_activity_date)?; + let (remaining_transactions, account_transfers) = + transform_account_transfers(&actualbudget_state.actualbudget_transactions)?; + let first_activity_date = transform_date(&actualbudget_state.actualbudget_first_activity_date)?; let before_first_activity_date = find_date_before(&first_activity_date)?; - let accounts: Vec<Account> = - transform_accounts( - &actualbudget_state.actualbudget_accounts, - &actualbudget_state.actualbudget_category_transfers_account, - &first_activity_date, - ); - let budget_drips = transform_budget_drips(&actualbudget_state.actualbudget_zero_budgets)?; - let buckets: Vec<Bucket> = transform_categories( - &actualbudget_state.actualbudget_categories, - &actualbudget_state.actualbudget_zero_budgets, - &before_first_activity_date, + let accounts: Vec<Account> = transform_accounts( + &actualbudget_state.actualbudget_accounts, + &actualbudget_state.actualbudget_category_transfers_account, + &first_activity_date, ); - let (transactions, transaction_categorisations): (Vec<Transaction>, Vec<TransactionCategorisation>) = - transform_transactions( - &remaining_transactions, - &actualbudget_state.actualbudget_category_transfers_account, - &actualbudget_state.actualbudget_payees, - )?; + let budget_drips = transform_budget_drips(&actualbudget_state.actualbudget_zero_budgets)?; + let buckets: Vec<Bucket> = transform_categories(&actualbudget_state.actualbudget_categories); + let pipes: Vec<Pipe> = transform_zero_budgets(&actualbudget_state.actualbudget_zero_budgets); + let transactions: Vec<Transaction> = transform_transactions( + &remaining_transactions, + &actualbudget_state.actualbudget_category_transfers_account, + &actualbudget_state.actualbudget_payees, + )?; let bucket_transfers: Vec<BucketTransfer> = transform_category_transfers( &remaining_transactions, &actualbudget_state.actualbudget_category_transfers_account, @@ -46,8 +46,8 @@ fn transform_state_without_context(actualbudget_state: ActualbudgetState) -> Res account_transfers, buckets, budget_drips, + pipes: pipes, transactions, - transaction_categorisations, bucket_transfers, }) } 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 f24e8b6..a1b7c4d 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 @@ -1,6 +1,6 @@ -use anyhow::{Context, Result}; use actualbudget_models::{ActualbudgetAccount, ActualbudgetPayee, ActualbudgetTransaction}; -use schist_models::{transaction::Transaction, transaction_categorisation::TransactionCategorisation}; +use anyhow::{Context, Result}; +use schist_models::transaction::Transaction; use super::{is_not_category_transfer, transform_date, transform_id}; @@ -8,22 +8,21 @@ pub fn transform_transactions( actualbudget_transactions: &[ActualbudgetTransaction], actualbudget_category_transfers_account: &Option<ActualbudgetAccount>, actualbudget_payees: &[ActualbudgetPayee], -) -> Result<(Vec<Transaction>, Vec<TransactionCategorisation>)> { +) -> Result<Vec<Transaction>> { transform_transactions_without_context( actualbudget_transactions, actualbudget_category_transfers_account, actualbudget_payees, ) - .with_context(|| "failed to transform actualbudget transactions to schist transactions") + .with_context(|| "failed to transform actualbudget transactions to schist transactions") } fn transform_transactions_without_context( actualbudget_transactions: &[ActualbudgetTransaction], actualbudget_category_transfers_account: &Option<ActualbudgetAccount>, actualbudget_payees: &[ActualbudgetPayee], -) -> Result<(Vec<Transaction>, Vec<TransactionCategorisation>)> { +) -> Result<Vec<Transaction>> { let mut transactions = Vec::<Transaction>::new(); - let mut transaction_categorisations = Vec::<TransactionCategorisation>::new(); let actualbudget_transactions = actualbudget_transactions .iter() @@ -32,10 +31,6 @@ fn transform_transactions_without_context( .clone() .filter(|t| t.is_parent) .collect(); - let child_actualbudget_transactions: Vec<&ActualbudgetTransaction> = actualbudget_transactions - .clone() - .filter(|t| t.is_child) - .collect(); let atomic_actualbudget_transactions: Vec<&ActualbudgetTransaction> = actualbudget_transactions .filter(|t| !t.is_parent && !t.is_child) .collect(); @@ -46,84 +41,59 @@ fn transform_transactions_without_context( let payee_name = atomic_actualbudget_transaction .payee .clone() - .map(|p| actualbudget_payees - .iter() - .find(|pp| pp.id == p) - .map(|pp| pp.name.clone()) - ).unwrap_or(None) + .map(|p| { + actualbudget_payees + .iter() + .find(|pp| pp.id == p) + .map(|pp| pp.name.clone()) + }) + .unwrap_or(None) .unwrap_or_else(String::new); transactions.push(Transaction { id: transaction_id, - description: atomic_actualbudget_transaction.notes.clone().unwrap_or_else(String::new), - payee: payee_name, - quantity: atomic_actualbudget_transaction.amount.try_into().unwrap(), + description: atomic_actualbudget_transaction + .notes + .clone() + .unwrap_or_else(String::new), + counterparty: payee_name, + amount: atomic_actualbudget_transaction.amount.try_into().unwrap(), date: transform_date(&atomic_actualbudget_transaction.date)?, account_id: transform_id(&atomic_actualbudget_transaction.account_id), - }); - - let actualbudget_category_id = atomic_actualbudget_transaction - .category_id - .clone() - .with_context(|| format!( - "atomic actualbudget transaction {} did not have a category ID", - atomic_actualbudget_transaction.id, - ))?; - - transaction_categorisations.push(TransactionCategorisation { - id: transaction_id, - description: String::new(), - quantity: atomic_actualbudget_transaction.amount.try_into().unwrap(), - transaction_id, - bucket_id: transform_id(&actualbudget_category_id), + bucket_id: None, }); } for parent_actualbudget_transaction in parent_actualbudget_transactions { - let child_actualbudget_transactions = child_actualbudget_transactions - .iter() - .filter(|t| t - .parent_id - .clone() - .is_some_and(|parent_id| parent_id == parent_actualbudget_transaction.id.clone()) - ); - let transaction_id = transform_id(&parent_actualbudget_transaction.id); transactions.push(Transaction { id: transaction_id, - description: parent_actualbudget_transaction.notes.clone().unwrap_or_else(String::new), - payee: parent_actualbudget_transaction.payee.clone().unwrap_or_else(String::new), - quantity: parent_actualbudget_transaction.amount.try_into().unwrap(), + bucket_id: None, + description: parent_actualbudget_transaction + .notes + .clone() + .unwrap_or_else(String::new), + counterparty: parent_actualbudget_transaction + .payee + .clone() + .unwrap_or_else(String::new), + amount: parent_actualbudget_transaction.amount.try_into().unwrap(), date: transform_date(&parent_actualbudget_transaction.date)?, account_id: transform_id(&parent_actualbudget_transaction.account_id), }); - - for child_actualbudget_transaction in child_actualbudget_transactions { - let actualbudget_category_id = child_actualbudget_transaction - .category_id - .clone() - .with_context(|| format!( - "actualbudget child transaction {} had no category ID", - child_actualbudget_transaction.id, - ))?; - - transaction_categorisations.push(TransactionCategorisation { - id: transform_id(&child_actualbudget_transaction.id), - description: child_actualbudget_transaction.notes.clone().unwrap_or_else(String::new), - quantity: child_actualbudget_transaction.amount.try_into().unwrap(), - transaction_id, - bucket_id: transform_id(&actualbudget_category_id), - }); - } } - Ok((transactions, transaction_categorisations)) + Ok(transactions) } #[cfg(test)] mod test { - use actualbudget_fakes::{actualbudget_account::make_fake_actualbudget_account, actualbudget_payee::{make_fake_actualbudget_payee, make_fake_actualbudget_payee_builder}, actualbudget_transaction::make_fake_actualbudget_transaction_builder}; + use actualbudget_fakes::{ + actualbudget_account::make_fake_actualbudget_account, + actualbudget_payee::{make_fake_actualbudget_payee, make_fake_actualbudget_payee_builder}, + actualbudget_transaction::make_fake_actualbudget_transaction_builder, + }; use actualbudget_models::ActualbudgetDate; use schist_models::date_utc::DateUtc; @@ -133,65 +103,50 @@ mod test { #[test] fn when_one_category_transfer_transaction_then_returns_empty() { - let actualbudget_transactions = [ - make_fake_actualbudget_transaction_builder("0") - .category_id(Some(String::from("0"))) - .build() - .unwrap(), - ]; + let actualbudget_transactions = [make_fake_actualbudget_transaction_builder("0") + .category_id(Some(String::from("0"))) + .build() + .unwrap()]; let actualbudget_category_transfers_account = make_fake_actualbudget_account("0"); let actualbudget_payees = [make_fake_actualbudget_payee("0")]; - let (transactions, transaction_categorisations) = transform_transactions( + let transactions = transform_transactions( &actualbudget_transactions, &Some(actualbudget_category_transfers_account), &actualbudget_payees, - ).unwrap(); + ) + .unwrap(); assert_eq!(0, transactions.len()); - assert_eq!(0, transaction_categorisations.len()); } #[test] fn when_one_atomic_transaction_then_returns_transformed_transaction() { - let actualbudget_transactions = [ - make_fake_actualbudget_transaction_builder("0") - .account_id(String::from("0")) - .amount(100) - .category_id(Some(String::from("0"))) - .payee(Some(String::from("0"))) - .notes(Some(String::from("Savings"))) - .date(ActualbudgetDate::from_i32(19700101)) - .build() - .unwrap(), - ]; - let actualbudget_payees = [ - make_fake_actualbudget_payee_builder("0") - .name(String::from("Gringotts")) - .build() - .unwrap(), - ]; - - let (transactions, transaction_categorisations) = transform_transactions( - &actualbudget_transactions, - &None, - &actualbudget_payees, - ).unwrap(); + let actualbudget_transactions = [make_fake_actualbudget_transaction_builder("0") + .account_id(String::from("0")) + .amount(100) + .category_id(Some(String::from("0"))) + .payee(Some(String::from("0"))) + .notes(Some(String::from("Savings"))) + .date(ActualbudgetDate::from_i32(19700101)) + .build() + .unwrap()]; + let actualbudget_payees = [make_fake_actualbudget_payee_builder("0") + .name(String::from("Gringotts")) + .build() + .unwrap()]; + + let transactions = + transform_transactions(&actualbudget_transactions, &None, &actualbudget_payees) + .unwrap(); assert_eq!(1, transactions.len()); let transaction = transactions.get(0).unwrap(); assert_eq!("Savings", transaction.description); - assert_eq!("Gringotts", transaction.payee); - assert_eq!(100, transaction.quantity); + assert_eq!("Gringotts", transaction.counterparty); + assert_eq!(100, transaction.amount); assert_eq!(DateUtc::from_ymd(1970, 1, 1).unwrap(), transaction.date); assert_eq!(transform_id("0"), transaction.account_id); - - assert_eq!(1, transaction_categorisations.len()); - let transaction_categorisation = transaction_categorisations.get(0).unwrap(); - assert_eq!("", transaction_categorisation.description); - assert_eq!(100, transaction_categorisation.quantity); - assert_eq!(transaction.id, transaction_categorisation.transaction_id); - assert_eq!(transform_id("0"), transaction_categorisation.bucket_id); } #[test] @@ -220,34 +175,11 @@ mod test { .unwrap(), ]; - let (transactions, transaction_categorisations) = transform_transactions( - &actualbudget_transactions, - &None, - &[], - ).unwrap(); + let transactions = transform_transactions(&actualbudget_transactions, &None, &[]).unwrap(); assert_eq!(1, transactions.len()); - assert_eq!(2, transaction_categorisations.len()); let transaction = transactions.get(0).unwrap(); - assert_eq!(100, transaction.quantity); - - let transaction_categorisation_0 = transaction_categorisations - .iter() - .filter(|tc| tc.quantity == 200) - .last() - .unwrap(); - assert_eq!(transaction.id, transaction_categorisation_0.transaction_id); - assert_eq!(transform_id("0"), transaction_categorisation_0.bucket_id); - assert_eq!("notes A", transaction_categorisation_0.description); - - let transaction_categorisation_1 = transaction_categorisations - .iter() - .filter(|tc| tc.quantity == 300) - .last() - .unwrap(); - assert_eq!(transaction.id, transaction_categorisation_1.transaction_id); - assert_eq!(transform_id("0"), transaction_categorisation_1.bucket_id); - assert_eq!("notes B", transaction_categorisation_1.description); + assert_eq!(100, transaction.amount); } } diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_zero_budgets.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_zero_budgets.rs new file mode 100644 index 0000000..07ecd56 --- /dev/null +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/src/transform_state/transform_zero_budgets.rs @@ -0,0 +1,6 @@ +use actualbudget_models::ActualbudgetZeroBudget; +use schist_models::pipe::Pipe; + +pub fn transform_zero_budgets(_actualbudget_zero_budgets: &[ActualbudgetZeroBudget]) -> Vec<Pipe> { + todo!() +} diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/account_transfer_matchers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/account_transfer_matchers.rs index ea3180e..f3e3037 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/account_transfer_matchers.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/account_transfer_matchers.rs @@ -3,11 +3,10 @@ use schist_models::{account::Account, account_transfer::AccountTransfer}; use super::ACCOUNT_NAMES_DICT; -pub fn get_account_transfer_matchers( -) -> Vec<Box<dyn Fn(&AccountTransfer, &[Account]) -> bool>> { - vec![ - Box::new(|at, a| matches_cash_withdrawal_10_sept_2024(at, a)), - ] +pub fn get_account_transfer_matchers() -> Vec<Box<dyn Fn(&AccountTransfer, &[Account]) -> bool>> { + vec![Box::new(|at, a| { + matches_cash_withdrawal_10_sept_2024(at, a) + })] } fn matches_cash_withdrawal_10_sept_2024( @@ -33,13 +32,15 @@ fn matches_cash_withdrawal_10_sept_2024( id: _id, date, description, - quantity: 80_00, + amount: 80_00, from_account_id, to_account_id, - } => *date == "2024-09-10".parse().unwrap() - && *description == "" - && *from_account_id == bank_account.id - && *to_account_id == cash_account.id, + } => { + *date == "2024-09-10".parse().unwrap() + && *description == "" + && *from_account_id == bank_account.id + && *to_account_id == cash_account.id + } _ => false, } } diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_matchers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_matchers.rs index 76189d3..5f5ef4f 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_matchers.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/bucket_matchers.rs @@ -1,34 +1,16 @@ -use schist_models::{budget_period_unit::BudgetPeriodUnit, bucket::Bucket}; +use schist_models::bucket::Bucket; -use super::bucket_names::{BUCKET_NAMES, BUCKET_NAME_BILLS, BUCKET_NAME_BILLS_FLEXIBLE, BUCKET_NAME_FOOD, BUCKET_NAME_GENERAL, BUCKET_NAME_INCOME, BUCKET_NAME_SAVINGS, BUCKET_NAME_STARTING_BALANCES}; +use super::bucket_names::BUCKET_NAMES; -pub fn get_bucket_matchers( -) -> Vec<Box<dyn Fn(&Bucket) -> bool>> { +pub fn get_bucket_matchers() -> Vec<Box<dyn Fn(&Bucket) -> bool>> { BUCKET_NAMES .iter() .map(|bucket_name| -> Box<dyn Fn(&Bucket) -> bool> { Box::new(|c| matches_bucket(c, bucket_name)) - }).collect() + }) + .collect() } -fn matches_bucket(bucket: &Bucket, bucket_name: &str,) -> bool { - bucket.name == bucket_name - && bucket.balance == 0 - && bucket.balance_date < "2024-08-01".parse().unwrap() - && bucket.budget_period == 1 - && bucket.budget_period_unit == BudgetPeriodUnit::Month - && bucket.budget_quantity == get_expected_budget_quantity(bucket_name) -} - -fn get_expected_budget_quantity(bucket_name: &str) -> i32 { - match bucket_name { - BUCKET_NAME_FOOD => 200_00, - BUCKET_NAME_GENERAL => 100_00, - BUCKET_NAME_BILLS => 125_00, - BUCKET_NAME_BILLS_FLEXIBLE => 25_00, - BUCKET_NAME_SAVINGS => 400_00, - BUCKET_NAME_STARTING_BALANCES => 0, - BUCKET_NAME_INCOME => 0, - _ => panic!("unexpected bucket name {}", bucket_name), - } +fn matches_bucket(bucket: &Bucket, bucket_name: &str) -> bool { + bucket.name == bucket_name && bucket.balance == None && bucket.balance_cache_key == None } 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 index 3b46d76..038e4f7 100644 --- 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 @@ -2,11 +2,10 @@ use schist_models::{bucket::Bucket, bucket_transfer::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)), - ] +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( @@ -27,12 +26,14 @@ fn matches_120_savings_to_general_23_oct_2024( BucketTransfer { id: _id, description, - quantity: 120_00, + amount: 120_00, from_bucket_id, to_bucket_id, - } => *from_bucket_id == savings_bucket.id - && *to_bucket_id == general_bucket.id - && description.as_str() == "", + } => { + *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/budget_drip_matchers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/budget_drip_matchers.rs index 97f81a2..bd42d76 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/budget_drip_matchers.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/budget_drip_matchers.rs @@ -1,9 +1,12 @@ use std::sync::LazyLock; -use schist_models::{budget_drip::BudgetDrip, bucket::Bucket, date_utc::DateUtc}; +use schist_models::{bucket::Bucket, date_utc::DateUtc, drip::Drip}; use schist_traits::dateable::Dateable; -use crate::common::bucket_names::{BUCKET_NAME_BILLS, BUCKET_NAME_BILLS_FLEXIBLE, BUCKET_NAME_FOOD, BUCKET_NAME_GENERAL, BUCKET_NAME_SAVINGS}; +use crate::common::bucket_names::{ + BUCKET_NAME_BILLS, BUCKET_NAME_BILLS_FLEXIBLE, BUCKET_NAME_FOOD, BUCKET_NAME_GENERAL, + BUCKET_NAME_SAVINGS, +}; pub fn get_num_expected_budget_drips() -> usize { let num_expected_dates = DATES.len(); @@ -11,12 +14,11 @@ pub fn get_num_expected_budget_drips() -> usize { num_expected_dates * num_expected_buckets } -pub fn assert_budget_drip_matches( - budget_drip: &BudgetDrip, - buckets: &[Bucket], -) { +pub fn assert_budget_drip_matches(budget_drip: &Drip, buckets: &[Bucket]) { assert!( - DATES.iter().any(|date| *LazyLock::force(date) == budget_drip.date), + DATES + .iter() + .any(|date| *LazyLock::force(date) == budget_drip.date), "budget drip had unexpected date: {:?}", budget_drip, ); @@ -25,11 +27,13 @@ pub fn assert_budget_drip_matches( .iter() .find(|c| c.id == budget_drip.bucket_id) .map(|c| c.name.clone()) - .expect(format!( - "budget drip {:?} had unexpected bucket ID. buckets were: {:?}", - budget_drip, - buckets, - ).as_str()); + .expect( + format!( + "budget drip {:?} had unexpected bucket ID. buckets were: {:?}", + budget_drip, buckets, + ) + .as_str(), + ); let expected_quantity = match ( bucket_name.as_str(), @@ -58,7 +62,9 @@ pub fn assert_budget_drip_matches( (BUCKET_NAME_BILLS_FLEXIBLE, 8 | 10, _) => 80, (BUCKET_NAME_BILLS_FLEXIBLE, 9 | 11, 1) => 93, (BUCKET_NAME_BILLS_FLEXIBLE, 9 | 11, _) => 83, - (BUCKET_NAME_BILLS_FLEXIBLE, _, _) => panic!("budget drip {:?} had unexpected date", budget_drip), + (BUCKET_NAME_BILLS_FLEXIBLE, _, _) => { + panic!("budget drip {:?} had unexpected date", budget_drip) + } (BUCKET_NAME_SAVINGS, 8 | 10, 1) => 13_00, (BUCKET_NAME_SAVINGS, 8 | 10, _) => 12_90, @@ -68,14 +74,12 @@ pub fn assert_budget_drip_matches( (_, _, _) => panic!( "budget drip {:?} had unexpected bucket name {}", - budget_drip, - bucket_name, + budget_drip, bucket_name, ), }; assert_eq!( - expected_quantity, - budget_drip.quantity, + expected_quantity, budget_drip.amount, "budget drip {:?} had unexpected quantity", budget_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 62255b0..f067d0d 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 @@ -1,23 +1,22 @@ mod account_matchers; mod account_names; mod account_transfer_matchers; -mod budget_drip_matchers; mod bucket_matchers; mod bucket_names; mod bucket_transfer_matchers; +mod budget_drip_matchers; mod db_url; mod test_context; mod transaction_matchers; -mod transaction_categorisation_matchers; pub use account_matchers::get_account_matchers; pub use account_names::ACCOUNT_NAMES_DICT; pub use account_transfer_matchers::get_account_transfer_matchers; -pub use budget_drip_matchers::assert_budget_drip_matches; -pub use budget_drip_matchers::get_num_expected_budget_drips; -pub use bucket_names::BUCKET_NAMES_DICT; 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 budget_drip_matchers::assert_budget_drip_matches; +pub use budget_drip_matchers::get_num_expected_budget_drips; pub use db_url::TEST_IN_DB_URL; pub use db_url::TEST_OUT_DB_URL; pub use test_context::TestContext; @@ -26,4 +25,3 @@ pub use transaction_matchers::matches_bank_account_initial_transaction; pub use transaction_matchers::matches_cash_account_initial_transaction; pub use transaction_matchers::matches_food_transaction; pub use transaction_matchers::matches_income_transaction; -pub use transaction_categorisation_matchers::get_transaction_categorisation_matchers; diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/transaction_categorisation_matchers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/transaction_categorisation_matchers.rs deleted file mode 100644 index 8082055..0000000 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/transaction_categorisation_matchers.rs +++ /dev/null @@ -1,141 +0,0 @@ -use schist_models::{account::Account, bucket::Bucket, transaction::Transaction, transaction_categorisation::TransactionCategorisation}; - -use super::{bucket_names::{BUCKET_NAME_FOOD, BUCKET_NAME_INCOME, BUCKET_NAME_STARTING_BALANCES}, matches_bank_account_initial_transaction, matches_cash_account_initial_transaction, matches_food_transaction, matches_income_transaction}; - -pub fn get_transaction_categorisation_matchers( -) -> Vec<Box<dyn Fn(&TransactionCategorisation, &[Transaction], &[Bucket], &[Account]) -> bool>> { - vec![ - Box::new(|tc, t, c, a| matches_bank_account_initial_transaction_categorisation(tc, t, c, a)), - Box::new(|tc, t, c, a| matches_cash_account_initial_transaction_categorisation(tc, t, c, a)), - Box::new(|tc, t, c, a| matches_income_transaction_categorisation(tc, t, c, a, "08")), - Box::new(|tc, t, c, a| matches_income_transaction_categorisation(tc, t, c, a, "09")), - Box::new(|tc, t, c, a| matches_income_transaction_categorisation(tc, t, c, a, "10")), - Box::new(|tc, t, c, a| matches_income_transaction_categorisation(tc, t, c, a, "11")), - Box::new(|tc, t, c, a| matches_food_transaction_categorisation(tc, t, c, a, "08")), - Box::new(|tc, t, c, a| matches_food_transaction_categorisation(tc, t, c, a, "09")), - Box::new(|tc, t, c, a| matches_food_transaction_categorisation(tc, t, c, a, "10")), - Box::new(|tc, t, c, a| matches_food_transaction_categorisation(tc, t, c, a, "11")), - ] -} - -fn matches_bank_account_initial_transaction_categorisation( - transaction_categorisation: &TransactionCategorisation, - transactions: &[Transaction], - buckets: &[Bucket], - accounts: &[Account], -) -> bool { - let bank_account_initial_transaction = transactions - .iter() - .find(|t| matches_bank_account_initial_transaction(t, accounts)) - .expect("failed to find bank account initial transaction"); - - let starting_balance_bucket = buckets - .iter() - .find(|c| c.name == BUCKET_NAME_STARTING_BALANCES) - .expect("failed to find starting balance bucket"); - - match transaction_categorisation { - TransactionCategorisation { - id: _id, - description, - quantity: 1200_00, - transaction_id, - bucket_id, - } => description == "" - && *transaction_id == bank_account_initial_transaction.id - && *bucket_id == starting_balance_bucket.id, - _ => false, - } -} - -fn matches_cash_account_initial_transaction_categorisation( - transaction_categorisation: &TransactionCategorisation, - transactions: &[Transaction], - buckets: &[Bucket], - accounts: &[Account], -) -> bool { - let cash_account_initial_transaction = transactions - .iter() - .find(|t| matches_cash_account_initial_transaction(t, accounts)) - .expect("failed to find cash account initial transaction"); - - let starting_balance_bucket = buckets - .iter() - .find(|c| c.name == BUCKET_NAME_STARTING_BALANCES) - .expect("failed to find starting balance bucket"); - - match transaction_categorisation { - TransactionCategorisation { - id: _id, - description, - quantity: 45_10, - transaction_id, - bucket_id, - } => description == "" - && *transaction_id == cash_account_initial_transaction.id - && *bucket_id == starting_balance_bucket.id, - _ => false, - } -} - -fn matches_income_transaction_categorisation( - transaction_categorisation: &TransactionCategorisation, - transactions: &[Transaction], - buckets: &[Bucket], - accounts: &[Account], - month: &str, -) -> bool { - let income_transaction = transactions - .iter() - .find(|t| matches_income_transaction(t, accounts, month)) - .expect(format!("failed to find income transaction for month {}", month).as_str()); - - let income_bucket = buckets - .iter() - .find(|c| c.name == BUCKET_NAME_INCOME) - .expect("failed to find income bucket"); - - match transaction_categorisation { - TransactionCategorisation { - id: _id, - description, - quantity: 846_46, - transaction_id, - bucket_id, - } => description == "" - && *transaction_id == income_transaction.id - && *bucket_id == income_bucket.id, - _ => false, - } -} - -fn matches_food_transaction_categorisation( - transaction_categorisation: &TransactionCategorisation, - transactions: &[Transaction], - buckets: &[Bucket], - accounts: &[Account], - month: &str, -) -> bool { - let food_transaction = transactions - .iter() - .find(|t| matches_food_transaction(t, accounts, month)) - .expect(format!("failed to find food transaction for month {}", month).as_str()); - - let food_bucket = buckets - .iter() - .find(|c| c.name == BUCKET_NAME_FOOD) - .expect("failed to find food bucket"); - - match transaction_categorisation { - TransactionCategorisation { - id: _id, - description, - quantity: -200_00, - transaction_id, - bucket_id, - } => description == "" - && *transaction_id == food_transaction.id - && *bucket_id == food_bucket.id, - _ => false, - } -} diff --git a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/transaction_matchers.rs b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/transaction_matchers.rs index c3763c1..ea22054 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/transaction_matchers.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/common/transaction_matchers.rs @@ -2,8 +2,7 @@ use schist_models::{account::Account, transaction::Transaction}; use super::ACCOUNT_NAMES_DICT; -pub fn get_transaction_matchers( -) -> Vec<Box<dyn Fn(&Transaction, &[Account]) -> bool>> { +pub fn get_transaction_matchers() -> Vec<Box<dyn Fn(&Transaction, &[Account]) -> bool>> { vec![ Box::new(|t, a| matches_bank_account_initial_transaction(t, a)), Box::new(|t, a| matches_cash_account_initial_transaction(t, a)), @@ -18,10 +17,7 @@ pub fn get_transaction_matchers( ] } -pub fn matches_bank_account_initial_transaction( - t: &Transaction, - accounts: &[Account], -) -> bool { +pub fn matches_bank_account_initial_transaction(t: &Transaction, accounts: &[Account]) -> bool { let bank_account_id = accounts .iter() .find(|a| a.name == ACCOUNT_NAMES_DICT.bank_account) @@ -32,23 +28,24 @@ pub fn matches_bank_account_initial_transaction( match t { Transaction { account_id, + bucket_id, date, id: _id, description, - payee, - quantity: 1200_00, - } => *account_id == bank_account_id - && *date == "2024-08-01".parse().unwrap() - && description == "" - && payee == "Starting Balance", + counterparty: payee, + amount: 1200_00, + } => { + *account_id == bank_account_id + && *bucket_id == get_bucket_id() + && *date == "2024-08-01".parse().unwrap() + && description == "" + && payee == "Starting Balance" + } _ => false, } } -pub fn matches_cash_account_initial_transaction( - t: &Transaction, - accounts: &[Account], -) -> bool { +pub fn matches_cash_account_initial_transaction(t: &Transaction, accounts: &[Account]) -> bool { let bank_account_id = accounts .iter() .find(|a| a.name == ACCOUNT_NAMES_DICT.cash_account) @@ -59,24 +56,24 @@ pub fn matches_cash_account_initial_transaction( match t { Transaction { account_id, + bucket_id, date, id: _id, description, - payee, - quantity: 45_10, - } => *account_id == bank_account_id - && *date == "2024-08-01".parse().unwrap() - && description == "" - && payee == "Starting Balance", + counterparty: payee, + amount: 45_10, + } => { + *account_id == bank_account_id + && *bucket_id == get_bucket_id() + && *date == "2024-08-01".parse().unwrap() + && description == "" + && payee == "Starting Balance" + } _ => false, } } -pub fn matches_income_transaction( - t: &Transaction, - accounts: &[Account], - month: &str, -) -> bool { +pub fn matches_income_transaction(t: &Transaction, accounts: &[Account], month: &str) -> bool { let bank_account_id = accounts .iter() .find(|a| a.name == ACCOUNT_NAMES_DICT.bank_account) @@ -87,25 +84,24 @@ pub fn matches_income_transaction( match t { Transaction { account_id, + bucket_id, date, description, id: _id, - payee, - quantity: 846_46, - } => + counterparty: payee, + amount: 846_46, + } => { *account_id == bank_account_id + && *bucket_id == get_bucket_id() && *date == format!("2024-{}-21", month).parse().unwrap() && description == "Salary" - && payee == "Employer Ltd", + && payee == "Employer Ltd" + } _ => false, } } -pub fn matches_food_transaction( - t: &Transaction, - accounts: &[Account], - month: &str, -) -> bool { +pub fn matches_food_transaction(t: &Transaction, accounts: &[Account], month: &str) -> bool { let bank_account_id = accounts .iter() .find(|a| a.name == ACCOUNT_NAMES_DICT.bank_account) @@ -116,15 +112,23 @@ pub fn matches_food_transaction( match t { Transaction { account_id, + bucket_id, date, description, id: _id, - payee, - quantity: -200_00, - } => *account_id == bank_account_id - && *date == format!("2024-{}-08", month).parse().unwrap() - && description == "" - && payee == "Tesco", + counterparty: payee, + amount: -200_00, + } => { + *account_id == bank_account_id + && *bucket_id == get_bucket_id() + && *date == format!("2024-{}-08", month).parse().unwrap() + && description == "" + && payee == "Tesco" + } _ => false, } } + +fn get_bucket_id() -> Option<i32> { + todo!() +} 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 4c13d55..62e1159 100644 --- a/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/run.rs +++ b/transformers/actualbudget_transformer/actualbudget_to_schist_transformer/tests/run.rs @@ -1,9 +1,18 @@ pub mod common; use actualbudget_to_schist_transformer::run::run; -use common::{assert_budget_drip_matches, get_account_matchers, get_account_transfer_matchers, get_bucket_matchers, get_bucket_transfer_matchers, get_num_expected_budget_drips, get_transaction_categorisation_matchers, get_transaction_matchers, TestContext, TEST_IN_DB_URL, TEST_OUT_DB_URL}; +use common::{ + assert_budget_drip_matches, get_account_matchers, get_account_transfer_matchers, + get_bucket_matchers, get_bucket_transfer_matchers, get_num_expected_budget_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, budget_drips::get_all_budget_drips, buckets::get_all_buckets, bucket_transfers::get_all_bucket_transfers, transaction_categorisations::get_all_transaction_categorisations, transactions::get_all_transactions}; +use schist_models::pipe::Pipe; +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, +}; #[test] fn given_test_database_when_run_then_exports_expected_schist_state() { @@ -12,11 +21,11 @@ fn given_test_database_when_run_then_exports_expected_schist_state() { let connection = &mut SqliteConnection::establish(TEST_OUT_DB_URL).unwrap(); let accounts = get_all_accounts(connection).unwrap(); let account_transfers = get_all_account_transfers(connection).unwrap(); - let budget_drips = get_all_budget_drips(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 pipes = get_all_pipes(connection).unwrap(); let transactions = get_all_transactions(connection).unwrap(); - let transaction_categorisations = get_all_transaction_categorisations(connection).unwrap(); let account_matchers = get_account_matchers(); assert_eq!(account_matchers.len(), accounts.len()); @@ -32,14 +41,16 @@ fn given_test_database_when_run_then_exports_expected_schist_state() { assert_eq!(account_transfer_matchers.len(), account_transfers.len()); for account_transfer in account_transfers { assert!( - account_transfer_matchers.iter().any(|m| m(&account_transfer, &accounts)), + account_transfer_matchers + .iter() + .any(|m| m(&account_transfer, &accounts)), "no match found for account_transfer {:?}", account_transfer, ); } - assert_eq!(get_num_expected_budget_drips(), budget_drips.len()); - for budget_drip in budget_drips { + assert_eq!(get_num_expected_budget_drips(), drips.len()); + for budget_drip in drips { assert_budget_drip_matches(&budget_drip, &buckets); } @@ -57,7 +68,9 @@ fn given_test_database_when_run_then_exports_expected_schist_state() { assert_eq!(bucket_transfer_matchers.len(), bucket_transfers.len()); for bucket_transfer in bucket_transfers { assert!( - bucket_transfer_matchers.iter().any(|m| m(&bucket_transfer, &buckets)), + bucket_transfer_matchers + .iter() + .any(|m| m(&bucket_transfer, &buckets)), "no match found for bucket_transfer {:?}", bucket_transfer, ); @@ -67,24 +80,13 @@ fn given_test_database_when_run_then_exports_expected_schist_state() { assert_eq!(transaction_matchers.len(), transactions.len()); for transaction in transactions.clone() { assert!( - transaction_matchers.iter().any(|m| m(&transaction, &accounts)), + transaction_matchers + .iter() + .any(|m| m(&transaction, &accounts)), "no match found for transaction {:?}", transaction, ); } - let transaction_categorisation_matchers = get_transaction_categorisation_matchers(); - assert_eq!(transaction_categorisation_matchers.len(), transaction_categorisations.len()); - for transaction_categorisation in transaction_categorisations { - assert!( - transaction_categorisation_matchers.iter().any(|m| m( - &transaction_categorisation, - &transactions, - &buckets, - &accounts, - )), - "no match found for transaction_categorisation {:?}", - transaction_categorisation, - ); - } + assert_eq!(Vec::<Pipe>::new(), pipes); } |
