summaryrefslogtreecommitdiff
path: root/rust/schist_models
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2024-11-24 15:45:04 +0000
committerJoe Carstairs <me@joeac.net>2024-11-24 15:56:01 +0000
commit1f5dcf9762a83d5972c0c8dbad22732505bf621c (patch)
tree5a1f5edd993733d48016ce2a8a8691b8f446858a /rust/schist_models
parent022795ffcaaa0d904c9f911d8a059885c2254278 (diff)
Swaps budget updates for budget drips
Diffstat (limited to 'rust/schist_models')
-rw-r--r--rust/schist_models/Cargo.toml1
-rw-r--r--rust/schist_models/diesel.toml9
-rw-r--r--rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql2
-rw-r--r--rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql16
-rw-r--r--rust/schist_models/src/budget.rs43
-rw-r--r--rust/schist_models/src/budget_drip.rs14
-rw-r--r--rust/schist_models/src/budget_period_unit.rs112
-rw-r--r--rust/schist_models/src/budget_update.rs16
-rw-r--r--rust/schist_models/src/category.rs7
-rw-r--r--rust/schist_models/src/category_balance.rs7
-rw-r--r--rust/schist_models/src/category_budget.rs9
-rw-r--r--rust/schist_models/src/lib.rs6
-rw-r--r--rust/schist_models/src/schema.rs14
-rw-r--r--rust/schist_models/tests/budget_period_unit.rs86
14 files changed, 251 insertions, 91 deletions
diff --git a/rust/schist_models/Cargo.toml b/rust/schist_models/Cargo.toml
index 6be4e21..a3e5021 100644
--- a/rust/schist_models/Cargo.toml
+++ b/rust/schist_models/Cargo.toml
@@ -13,4 +13,5 @@ schist_traits = { path = "../schist_traits" }
serde = { workspace = true, features = ["derive"] }
[dev-dependencies]
+libsqlite3-sys = { workspace = true, features = ["bundled"] }
schist_queries = { path = "../schist_queries" }
diff --git a/rust/schist_models/diesel.toml b/rust/schist_models/diesel.toml
new file mode 100644
index 0000000..2b8ebd8
--- /dev/null
+++ b/rust/schist_models/diesel.toml
@@ -0,0 +1,9 @@
+# For documentation on how to configure this file,
+# see https://diesel.rs/guides/configuring-diesel-cli
+
+[print_schema]
+file = "src/schema.rs"
+custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
+
+[migrations_directory]
+dir = "/home/joeac/src/schist/rust/schist_models/migrations"
diff --git a/rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql b/rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql
index 8cf4b1e..b9b1675 100644
--- a/rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql
+++ b/rust/schist_models/migrations/2024-08-31-084439_initial_setup/down.sql
@@ -1,5 +1,5 @@
DROP TABLE accounts;
-DROP TABLE budget_updates;
+DROP TABLE budget_drips;
DROP TABLE categories;
DROP TABLE transaction_categorisations;
DROP TABLE category_transfers;
diff --git a/rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql b/rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql
index 9038b54..a684ce7 100644
--- a/rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql
+++ b/rust/schist_models/migrations/2024-08-31-084439_initial_setup/up.sql
@@ -7,22 +7,26 @@ CREATE TABLE accounts(
opening_date TEXT NOT NULL
);
-CREATE TABLE budget_updates(
+CREATE TABLE budget_drips(
id INTEGER NOT NULL PRIMARY KEY,
category_id INTEGER NOT NULL,
date TEXT NOT NULL,
- new_budget INTEGER NOT NULL,
- new_period INTEGER NOT NULL,
- UNIQUE (category_id, date),
+ quantity INTEGER NOT NULL,
FOREIGN KEY (category_id)
REFERENCES categories (id)
ON UPDATE CASCADE
- ON DELETE RESTRICT
+ ON DELETE RESTRICT,
+ UNIQUE(category_id, date)
);
CREATE TABLE categories(
id INTEGER NOT NULL PRIMARY KEY,
- name TEXT NOT NULL
+ 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(
diff --git a/rust/schist_models/src/budget.rs b/rust/schist_models/src/budget.rs
deleted file mode 100644
index 03261e5..0000000
--- a/rust/schist_models/src/budget.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-use serde::Serialize;
-
-use super::budget_update::BudgetUpdate;
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
-pub struct Budget {
- pub quantity: i32,
- pub period: i32,
-}
-
-impl From<&BudgetUpdate> for Budget {
- fn from(budget_update: &BudgetUpdate) -> Self {
- Self {
- quantity: budget_update.new_budget,
- period: budget_update.new_period,
- }
- }
-}
-
-#[cfg(test)]
-mod test {
- use crate::date_utc::DateUtc;
- use schist_traits::nowlike::Nowlike;
-
- use super::*;
-
- #[test]
- fn when_from_budget_update_then_returns_budget() {
- let quantity = 2;
- let period = 3;
- let budget_update = BudgetUpdate {
- id: 0,
- category_id: 1,
- date: DateUtc::now(),
- new_budget: quantity,
- new_period: period,
- };
-
- let budget = Budget::from(&budget_update);
-
- assert_eq!(budget, Budget { quantity, period });
- }
-}
diff --git a/rust/schist_models/src/budget_drip.rs b/rust/schist_models/src/budget_drip.rs
new file mode 100644
index 0000000..f467ecd
--- /dev/null
+++ b/rust/schist_models/src/budget_drip.rs
@@ -0,0 +1,14 @@
+use derive_builder::Builder;
+use diesel::prelude::{Identifiable, Insertable, Queryable, Selectable};
+use serde::{Deserialize, Serialize};
+
+use crate::date_utc::DateUtc;
+
+#[derive(Builder, Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize, Insertable)]
+#[diesel(table_name = crate::schema::budget_drips)]
+pub struct BudgetDrip {
+ pub id: i32,
+ pub category_id: i32,
+ pub date: DateUtc,
+ pub quantity: i32,
+}
diff --git a/rust/schist_models/src/budget_period_unit.rs b/rust/schist_models/src/budget_period_unit.rs
new file mode 100644
index 0000000..77d8641
--- /dev/null
+++ b/rust/schist_models/src/budget_period_unit.rs
@@ -0,0 +1,112 @@
+use core::fmt;
+use std::str::FromStr;
+
+use anyhow::{Result, bail};
+use diesel::{backend::Backend, deserialize::{self, FromSql, FromSqlRow}, expression::AsExpression, serialize::{self, ToSql}, sql_types::Text, sqlite::Sqlite};
+use serde::{Deserialize, Serialize};
+
+#[derive(
+ Eq,
+ PartialEq,
+ Serialize,
+ Deserialize,
+ Clone,
+ Debug,
+ AsExpression,
+ FromSqlRow,
+)]
+#[diesel(sql_type=Text)]
+pub enum BudgetPeriodUnit {
+ Day,
+ Month,
+ Year,
+}
+
+impl fmt::Display for BudgetPeriodUnit {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ BudgetPeriodUnit::Day => "DAY".fmt(f),
+ BudgetPeriodUnit::Month => "MONTH".fmt(f),
+ BudgetPeriodUnit::Year => "YEAR".fmt(f),
+ }
+ }
+}
+
+impl FromStr for BudgetPeriodUnit {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self> {
+ match s {
+ "DAY" => Ok(BudgetPeriodUnit::Day),
+ "MONTH" => Ok(BudgetPeriodUnit::Month),
+ "YEAR" => Ok(BudgetPeriodUnit::Year),
+ _ => bail!("\"{}\" is not a valid budget period unit. Valid values are: DAY, MONTH, YEAR", s),
+ }
+ }
+}
+
+impl ToSql<Text, Sqlite> for BudgetPeriodUnit {
+ fn to_sql<'b>(
+ &'b self,
+ out: &mut diesel::serialize::Output<'b, '_, Sqlite>,
+ ) -> serialize::Result {
+ out.set_value(self.to_string());
+ Ok(serialize::IsNull::No)
+ }
+}
+
+impl FromSql<Text, Sqlite> for BudgetPeriodUnit {
+ fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<BudgetPeriodUnit> {
+ let str = <String as FromSql<Text, Sqlite>>::from_sql(bytes)?;
+ Ok(str.parse()?)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use anyhow::Result;
+
+ use super::BudgetPeriodUnit;
+
+ #[test]
+ fn display_when_day_then_day() {
+ assert_eq!("DAY", format!("{}", BudgetPeriodUnit::Day));
+ }
+
+ #[test]
+ fn display_when_month_then_month() {
+ assert_eq!("MONTH", format!("{}", BudgetPeriodUnit::Month));
+ }
+
+ #[test]
+ fn display_when_year_then_year() {
+ assert_eq!("YEAR", format!("{}", BudgetPeriodUnit::Year));
+ }
+
+ #[test]
+ fn from_str_when_day_then_day() {
+ let result: Result<BudgetPeriodUnit> = "DAY".parse();
+ assert!(result.is_ok());
+ assert_eq!(BudgetPeriodUnit::Day, result.unwrap());
+ }
+
+ #[test]
+ fn from_str_when_month_then_month() {
+ let result: Result<BudgetPeriodUnit> = "MONTH".parse();
+ assert!(result.is_ok());
+ assert_eq!(BudgetPeriodUnit::Month, result.unwrap());
+ }
+
+ #[test]
+ fn from_str_when_year_then_year() {
+ let result: Result<BudgetPeriodUnit> = "YEAR".parse();
+ assert!(result.is_ok());
+ assert_eq!(BudgetPeriodUnit::Year, result.unwrap());
+ }
+
+ #[test]
+ fn from_str_when_sentence_case_day_then_err() {
+ let result: Result<BudgetPeriodUnit> = "Day".parse();
+ assert!(result.is_err());
+ }
+}
diff --git a/rust/schist_models/src/budget_update.rs b/rust/schist_models/src/budget_update.rs
deleted file mode 100644
index 86645cb..0000000
--- a/rust/schist_models/src/budget_update.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use derive_builder::Builder;
-use diesel::prelude::*;
-use serde::Serialize;
-
-use crate::{category::Category, date_utc::DateUtc};
-
-#[derive(Builder, Clone, Serialize, Queryable, Identifiable, Selectable, Associations, Debug, PartialEq, Eq, Insertable)]
-#[diesel(table_name = crate::schema::budget_updates)]
-#[diesel(belongs_to(Category))]
-pub struct BudgetUpdate {
- pub id: i32,
- pub category_id: i32,
- pub date: DateUtc,
- pub new_budget: i32,
- pub new_period: i32,
-}
diff --git a/rust/schist_models/src/category.rs b/rust/schist_models/src/category.rs
index 55c7b97..c85ff51 100644
--- a/rust/schist_models/src/category.rs
+++ b/rust/schist_models/src/category.rs
@@ -2,9 +2,16 @@ use derive_builder::Builder;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
+use crate::{budget_period_unit::BudgetPeriodUnit, date_utc::DateUtc};
+
#[derive(Builder, Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize, Insertable)]
#[diesel(table_name = crate::schema::categories)]
pub struct Category {
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,
}
diff --git a/rust/schist_models/src/category_balance.rs b/rust/schist_models/src/category_balance.rs
deleted file mode 100644
index 796b40b..0000000
--- a/rust/schist_models/src/category_balance.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use serde::Serialize;
-
-#[derive(Serialize)]
-pub struct CategoryBalance {
- pub category_id: i32,
- pub balance: i32,
-}
diff --git a/rust/schist_models/src/category_budget.rs b/rust/schist_models/src/category_budget.rs
deleted file mode 100644
index 9796261..0000000
--- a/rust/schist_models/src/category_budget.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use serde::Serialize;
-
-use super::budget::Budget;
-
-#[derive(Serialize)]
-pub struct CategoryBudget {
- pub category_id: i32,
- pub budget: Option<Budget>,
-}
diff --git a/rust/schist_models/src/lib.rs b/rust/schist_models/src/lib.rs
index 9e60e80..1712c36 100644
--- a/rust/schist_models/src/lib.rs
+++ b/rust/schist_models/src/lib.rs
@@ -1,9 +1,7 @@
pub mod account;
-pub mod budget;
-pub mod budget_update;
+pub mod budget_drip;
+pub mod budget_period_unit;
pub mod category;
-pub mod category_balance;
-pub mod category_budget;
pub mod category_transfer;
pub mod date_utc;
pub mod datetime_utc;
diff --git a/rust/schist_models/src/schema.rs b/rust/schist_models/src/schema.rs
index 372f8f3..5164d07 100644
--- a/rust/schist_models/src/schema.rs
+++ b/rust/schist_models/src/schema.rs
@@ -10,12 +10,11 @@ diesel::table! {
}
diesel::table! {
- budget_updates (id) {
+ budget_drips (id) {
id -> Integer,
category_id -> Integer,
date -> Text,
- new_budget -> Integer,
- new_period -> Integer,
+ quantity -> Integer,
}
}
@@ -23,6 +22,11 @@ diesel::table! {
categories (id) {
id -> Integer,
name -> Text,
+ balance -> Integer,
+ balance_date -> Text,
+ budget_period -> Integer,
+ budget_period_unit -> Text,
+ budget_quantity -> Integer,
}
}
@@ -57,14 +61,14 @@ diesel::table! {
}
}
-diesel::joinable!(budget_updates -> categories (category_id));
+diesel::joinable!(budget_drips -> categories (category_id));
diesel::joinable!(transaction_categorisations -> categories (category_id));
diesel::joinable!(transaction_categorisations -> transactions (transaction_id));
diesel::joinable!(transactions -> accounts (account_id));
diesel::allow_tables_to_appear_in_same_query!(
accounts,
- budget_updates,
+ budget_drips,
categories,
category_transfers,
transaction_categorisations,
diff --git a/rust/schist_models/tests/budget_period_unit.rs b/rust/schist_models/tests/budget_period_unit.rs
new file mode 100644
index 0000000..6e8928a
--- /dev/null
+++ b/rust/schist_models/tests/budget_period_unit.rs
@@ -0,0 +1,86 @@
+mod common;
+
+use anyhow::Result;
+use common::test_context::TestContext;
+use diesel::{sql_query, Connection, ExpressionMethods, QueryDsl, RunQueryDsl, Selectable, SelectableHelper, SqliteConnection};
+use diesel::prelude::{Insertable, Queryable};
+use schist_models::budget_period_unit::BudgetPeriodUnit;
+
+diesel::table! {
+ budget_period_unit_test (id) {
+ id -> Integer,
+ budget_period_unit -> Text,
+ }
+}
+
+#[derive(Insertable, Queryable, Selectable, Clone)]
+#[diesel(table_name = budget_period_unit_test)]
+pub struct BudgetPeriodUnitTest {
+ pub id: i32,
+ pub budget_period_unit: BudgetPeriodUnit,
+}
+
+fn new_test_context() -> Result<TestContext> {
+ let test_context = TestContext::new();
+ let connection = &mut SqliteConnection::establish(&test_context.db_url)?;
+ let query = "CREATE TABLE budget_period_unit_test(id INTEGER PRIMARY KEY, budget_period_unit TEXT);";
+ sql_query(query).execute(connection)?;
+ Ok(test_context)
+}
+
+fn insert_budget_period_unit(id: i32, budget_period_unit: &BudgetPeriodUnit, connection: &mut SqliteConnection) -> Result<usize> {
+ let model = BudgetPeriodUnitTest {
+ id,
+ budget_period_unit: budget_period_unit.clone(),
+ };
+ let num_rows_inserted = diesel::insert_into(budget_period_unit_test::dsl::budget_period_unit_test)
+ .values(&[model])
+ .execute(connection)?;
+ Ok(num_rows_inserted)
+}
+
+fn get_budget_period_unit(id: i32, connection: &mut SqliteConnection) -> Result<BudgetPeriodUnitTest> {
+ let budget_period_unit: Vec<BudgetPeriodUnitTest> = budget_period_unit_test::dsl::budget_period_unit_test
+ .select(BudgetPeriodUnitTest::as_select())
+ .filter(budget_period_unit_test::id.eq(id))
+ .load(connection)?;
+ Ok(budget_period_unit[0].clone())
+}
+
+#[test]
+fn when_insert_day_and_select_then_returns_day() {
+ let test_context = new_test_context().unwrap();
+ let connection = &mut SqliteConnection::establish(&test_context.db_url).unwrap();
+
+ let budget_period_unit = BudgetPeriodUnit::Day;
+ let num_rows_inserted = insert_budget_period_unit(0, &budget_period_unit, connection);
+
+ assert!(num_rows_inserted.is_ok());
+ let num_rows_inserted = num_rows_inserted.unwrap();
+ assert_eq!(num_rows_inserted, 1);
+
+ let budget_period_unit_returned = get_budget_period_unit(0, connection);
+
+ assert!(budget_period_unit_returned.is_ok());
+ let budget_period_unit_returned = budget_period_unit_returned.unwrap();
+ assert_eq!(BudgetPeriodUnit::Day, budget_period_unit_returned.budget_period_unit);
+}
+
+#[test]
+fn when_insert_month_and_select_then_returns_month() {
+ let test_context = new_test_context().unwrap();
+ let connection = &mut SqliteConnection::establish(&test_context.db_url).unwrap();
+
+ let budget_period_unit = BudgetPeriodUnit::Month;
+ let num_rows_inserted = insert_budget_period_unit(0, &budget_period_unit, connection);
+
+ assert!(num_rows_inserted.is_ok());
+ let num_rows_inserted = num_rows_inserted.unwrap();
+ assert_eq!(num_rows_inserted, 1);
+
+ let budget_period_unit_returned = get_budget_period_unit(0, connection);
+
+ assert!(budget_period_unit_returned.is_ok());
+ let budget_period_unit_returned = budget_period_unit_returned.unwrap();
+ assert_eq!(BudgetPeriodUnit::Month, budget_period_unit_returned.budget_period_unit);
+}