summaryrefslogtreecommitdiff
path: root/rust/schist_queries/src/utils/calculate_budgets_accrual.rs
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2024-11-17 12:16:19 +0000
committerJoe Carstairs <me@joeac.net>2024-11-17 12:20:48 +0000
commit99fa72e2b9d572e00cd232bd55a3fa24722c3134 (patch)
tree6b50ce600fffa07a0c21b7f14d2447fe5ac3c2ed /rust/schist_queries/src/utils/calculate_budgets_accrual.rs
parent10f071447594f2bdcec84a461df9ad648f71f44b (diff)
Refactors workspace
Diffstat (limited to 'rust/schist_queries/src/utils/calculate_budgets_accrual.rs')
-rw-r--r--rust/schist_queries/src/utils/calculate_budgets_accrual.rs156
1 files changed, 156 insertions, 0 deletions
diff --git a/rust/schist_queries/src/utils/calculate_budgets_accrual.rs b/rust/schist_queries/src/utils/calculate_budgets_accrual.rs
new file mode 100644
index 0000000..1d4ed02
--- /dev/null
+++ b/rust/schist_queries/src/utils/calculate_budgets_accrual.rs
@@ -0,0 +1,156 @@
+use anyhow::Result;
+use schist_models::{
+ budget_update::BudgetUpdate, category::Category, date_utc::DateUtc,
+};
+use schist_traits::nowlike::Nowlike;
+
+pub fn calculate_budgets_accrual(category: &Category, budget_updates: &[BudgetUpdate]) -> Result<i64> {
+ let mut budget_updates = budget_updates
+ .iter()
+ .filter(|bu| bu.category_id == category.id)
+ .collect::<Vec<&BudgetUpdate>>();
+ budget_updates.sort_by_key(|bu| bu.date.clone());
+ budget_updates.reverse();
+
+ let mut budgets_accrual = 0.0_f64;
+ let mut calculated_back_to_date = DateUtc::now();
+
+ for budget_update in budget_updates {
+ let days_on_this_budget = calculated_back_to_date.day_diff(&budget_update.date);
+ if days_on_this_budget == 0_u32 {
+ return Err(anyhow::Error::msg(format!(
+ "failed to calculate budgets accrual since two budget updates had the same date ({})",
+ budget_update.date,
+ )));
+ }
+ let budget_per_day = budget_update.new_budget as f64 / budget_update.new_period as f64;
+ budgets_accrual += days_on_this_budget as f64 * budget_per_day;
+ calculated_back_to_date = budget_update.date.clone();
+ }
+
+ Ok(budgets_accrual.floor() as i64)
+}
+
+#[cfg(test)]
+mod test {
+ use chrono::{Days, Utc};
+ use schist_models::{budget_update::BudgetUpdate, category::Category, date_utc::DateUtc};
+ use schist_traits::nowlike::Nowlike;
+
+ use super::calculate_budgets_accrual;
+
+ #[test]
+ fn when_no_budget_updates_then_return_zero() {
+ let category = Category {
+ id: 0,
+ name: String::from("Groceries"),
+ };
+ let budget_updates = [];
+
+ let result = calculate_budgets_accrual(&category, &budget_updates);
+
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), 0_i64);
+ }
+
+ #[test]
+ fn when_one_budget_update_for_another_category_then_return_zero() {
+ let category = Category {
+ id: 0,
+ name: String::from("Groceries"),
+ };
+ let budget_updates = [BudgetUpdate {
+ id: 0,
+ category_id: 1,
+ date: DateUtc::now(),
+ new_budget: 100,
+ new_period: 1,
+ }];
+
+ let result = calculate_budgets_accrual(&category, &budget_updates);
+
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), 0_i64);
+ }
+
+ #[test]
+ fn when_one_budget_update_then_return_quantity_times_days_in_past() {
+ let category = Category {
+ id: 0,
+ name: String::from("Groceries"),
+ };
+ let budget_updates = [BudgetUpdate {
+ id: 0,
+ category_id: 0,
+ date: DateUtc::from(Utc::now().checked_sub_days(Days::new(120)).unwrap()),
+ new_budget: 100,
+ new_period: 1,
+ }];
+
+ let result = calculate_budgets_accrual(&category, &budget_updates);
+
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), 12_000);
+ }
+
+ #[test]
+ fn when_two_budget_updates_then_calculates_as_expected() {
+ let category = Category {
+ id: 0,
+ name: String::from("Groceries"),
+ };
+ let budget_updates = [
+ BudgetUpdate {
+ id: 0,
+ category_id: 0,
+ date: DateUtc::from(&Utc::now().checked_sub_days(Days::new(60)).unwrap()),
+ new_budget: 200,
+ new_period: 1,
+ },
+ BudgetUpdate {
+ id: 0,
+ category_id: 0,
+ date: DateUtc::from(&Utc::now().checked_sub_days(Days::new(120)).unwrap()),
+ new_budget: 100,
+ new_period: 1,
+ },
+ ];
+
+ let result = calculate_budgets_accrual(&category, &budget_updates);
+
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), 18_000);
+ }
+
+ #[test]
+ fn when_two_budget_updates_on_same_day_then_returns_err() {
+ let category = Category {
+ id: 0,
+ name: String::from("Groceries"),
+ };
+ let budget_updates = [
+ BudgetUpdate {
+ id: 0,
+ category_id: 0,
+ date: DateUtc::from_ymd(2020, 12, 25).unwrap(),
+ new_budget: 200,
+ new_period: 1,
+ },
+ BudgetUpdate {
+ id: 0,
+ category_id: 0,
+ date: DateUtc::from_ymd(2020, 12, 25).unwrap(),
+ new_budget: 100,
+ new_period: 1,
+ },
+ ];
+ let mut budget_updates_reversed = budget_updates.clone();
+ budget_updates_reversed.reverse();
+
+ let result_leftways = calculate_budgets_accrual(&category, &budget_updates);
+ let result_rightways = calculate_budgets_accrual(&category, &budget_updates_reversed);
+
+ assert!(result_leftways.is_err());
+ assert!(result_rightways.is_err());
+ }
+}