summaryrefslogtreecommitdiff
path: root/rust/schist_queries/src/categories.rs
blob: f54373c9e9f2c2b8af3b1b66852adb15cbecd10b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use anyhow::{Context, Result};
use diesel::{QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection};
use schist_models::{budget::Budget, category::Category, category_balance::CategoryBalance, category_budget::CategoryBudget, date_utc::DateUtc};
use schist_schema::schema::categories::{self as categories_schema, dsl::categories as categories_table};

use crate::{
    budget_updates::{
        get_all_budget_updates,
        get_first_budget_update_with_date_and_category_id,
        get_most_recent_budget_update_date_with_category_id,
    },
    category_transfers::{
        sum_category_transfer_quantity_per_from_category_id,
        sum_category_transfer_quantity_per_to_category_id,
    },
    transaction_categorisations::sum_transaction_categorisation_quantity_per_category_id,
    utils::calculate_category_balance,
};

pub fn delete_all_categories(connection: &mut SqliteConnection) -> Result<usize> {
    let num_rows_deleted = diesel::delete(categories_table)
        .execute(connection)
        .with_context(|| "failed to delete all categories")?;
    Ok(num_rows_deleted)
}

pub fn get_all_categories(connection: &mut SqliteConnection) -> Result<Vec<Category>> {
    let all_categories = categories_table
        .select(Category::as_select())
        .load(connection)
        .with_context(|| "failed to get all categories")?;
    Ok(all_categories)
}

pub fn get_all_category_balances(
    connection: &mut SqliteConnection,
) -> Result<Vec<CategoryBalance>> {
    get_all_category_balances_without_context(connection)
        .with_context(|| "failed to get all category balances")
}

fn get_all_category_balances_without_context(
    connection: &mut SqliteConnection,
) -> Result<Vec<CategoryBalance>> {
    let categories = get_all_categories(connection)?;
    let transaction_sums = sum_transaction_categorisation_quantity_per_category_id(connection)?;
    let category_transfer_from_sums =
        sum_category_transfer_quantity_per_from_category_id(connection)?;
    let category_transfer_to_sums = sum_category_transfer_quantity_per_to_category_id(connection)?;
    let budget_updates = get_all_budget_updates(connection)?;

    let mut balances = Vec::<CategoryBalance>::new();
    for category in categories {
        let balance = calculate_category_balance(
            &category,
            &transaction_sums,
            &category_transfer_from_sums,
            &category_transfer_to_sums,
            &budget_updates,
        )?;

        balances.push(CategoryBalance {
            category_id: category.id,
            balance: balance.try_into().unwrap(),
        })
    }

    Ok(balances)
}

pub fn get_all_category_ids(connection: &mut SqliteConnection) -> Result<Vec<i32>> {
    let all_category_ids = categories_table
        .select(categories_schema::id)
        .load(connection)
        .with_context(|| "failed to get all category IDs")?;
    Ok(all_category_ids)
}

pub fn get_most_recent_category_budget_per_category_id(
    connection: &mut SqliteConnection,
) -> Result<Vec<CategoryBudget>> {
    get_most_recent_category_budget_per_category_id_without_context(connection)
        .with_context(|| "failed to get most recent category budget per category ID")
}

fn get_most_recent_category_budget_per_category_id_without_context(
    connection: &mut SqliteConnection,
) -> Result<Vec<CategoryBudget>> {
    let category_ids: Vec<i32> = get_all_category_ids(connection)?;

    let mut budgets = Vec::<CategoryBudget>::new();
    for category_id in category_ids {
        let most_recent_budget_update_date =
            get_most_recent_budget_update_date_with_category_id(category_id, connection)?;

        budgets.push(match most_recent_budget_update_date {
            None => CategoryBudget {
                category_id,
                budget: Option::None,
            },
            Some(most_recent_budget_update_date) =>
                get_most_recent_category_budget_with_date_and_category_id(
                    &most_recent_budget_update_date,
                    category_id,
                    connection,
            )?,
        })
    }

    Ok(budgets)
}

fn get_most_recent_category_budget_with_date_and_category_id(
    most_recent_budget_update_date: &DateUtc,
    category_id: i32,
    connection: &mut SqliteConnection
) -> Result<CategoryBudget> {
    let most_recent_budget_update = get_first_budget_update_with_date_and_category_id(
        &most_recent_budget_update_date,
        category_id,
        connection,
    )?;

    let budget = match most_recent_budget_update {
        None => None,
        Some(most_recent_budget_update) => Some(Budget::from(&most_recent_budget_update)),
    };

    Ok(CategoryBudget {
        category_id,
        budget,
    })
}

pub fn insert_categories(
    categories: &[Category],
    connection: &mut SqliteConnection,
) -> Result<usize> {
    let num_categories_inserted = diesel::insert_into(categories_table)
        .values(categories)
        .execute(connection)
        .with_context(|| insert_err_msg(categories))?;
    Ok(num_categories_inserted)
}

fn insert_err_msg(categories: &[Category]) -> String {
    format!(
        "failed to insert categories: [{}]",
        categories
            .iter()
            .map(|c| format!("\"{}\": \"{}\"", c.id, c.name))
            .collect::<Vec<String>>()
            .join(", ")
    )
}