rex_tui/tx_handler/
add_tx.rs

1use rusqlite::{Connection, Result as sqlResult};
2use std::collections::HashMap;
3
4use crate::utility::{
5    get_all_tx_methods, get_last_balance_id, get_last_balances, get_last_time_balance,
6    get_last_tx_id,
7};
8
9/// Adds a transaction to the database with the given info. The flow of this goes like this:
10/// - Add the new transaction to the database
11/// - Calculate the changes that happened to the Tx Method
12/// - Calculate the absolute final balance
13/// - Find the Changes that happened due to the transaction
14/// - Push them to the database
15pub fn add_tx(
16    date: &str,
17    details: &str,
18    tx_method: &str,
19    amount: &str,
20    tx_type: &str,
21    tags: &str,
22    id_num: Option<&str>,
23    conn: &mut Connection,
24) -> sqlResult<()> {
25    // create a connection and a savepoint
26    let sp = conn.savepoint()?;
27
28    // the process goes through 4 parts
29    // Add the tx itself in the db
30    // calculate the amount to add/subtract from the balance_all table
31    // create changes amount and push it to db
32    // Update final row balance which holds the balance after all tx
33
34    // if Some(id) means it's a transaction editing
35    // else it's a normal transaction
36    if let Some(id) = id_num {
37        let query = r#"INSERT INTO tx_all (date, details, "tx_method", amount, tx_type, id_num, tags) VALUES (?, ?, ?, ?, ?, ?, ?)"#;
38        sp.execute(query, [date, details, tx_method, amount, tx_type, id, tags])?;
39    } else {
40        let query = r#"INSERT INTO tx_all (date, details, "tx_method", amount, tx_type, tags) VALUES (?, ?, ?, ?, ?, ?)"#;
41        sp.execute(query, [date, details, tx_method, amount, tx_type, tags])?;
42    }
43
44    // 2025-05-10
45    // take 2025 and subtract 2022 = 3, means the year number 3
46    // take 05 -> 5 -> 5th month. 5 + (3 * 12) =  the row of this month's balance on balance_all table
47    // we are not subtracting 1 from month because balance_all table starts at 1
48    let split_date = date.split('-').collect::<Vec<&str>>();
49    let (year, month) = (
50        split_date[0].parse::<i32>().unwrap() - 2022,
51        split_date[1].parse::<i32>().unwrap(),
52    );
53
54    let mut from_method = String::new();
55    let mut to_method = String::new();
56
57    if tx_type == "Transfer" {
58        let split_method = tx_method.split(" to ").collect::<Vec<&str>>();
59        from_method = split_method[0].to_string();
60        to_method = split_method[1].to_string();
61    }
62
63    let target_id_num = month + (year * 12);
64
65    // This is necessary for the foreign key field in the changes_all table
66    // and must align with the latest transaction id_num
67    let mut last_id = get_last_tx_id(&sp)?;
68    if let Some(id) = id_num {
69        last_id = id.parse().unwrap();
70    }
71    let last_balance_id = get_last_balance_id(&sp)?;
72
73    // we have to get these following data to push to the database
74    // new_balance_data: the working month balance after the transaction
75    // new_changes_data: the new changes data to push to the database after this tx
76    // last_balance_data: the absolute final balance after all transaction
77    let mut new_balance_data = Vec::new();
78    let mut new_changes_data = Vec::new();
79    let mut last_balance_data = HashMap::new();
80
81    let all_tx_methods = get_all_tx_methods(&sp);
82    let last_balance = get_last_balances(&sp);
83
84    // Retrieve the current month's balance for each transaction method.
85    let mut current_month_balance =
86        get_last_time_balance(month as usize, year as usize, &all_tx_methods, &sp);
87
88    let int_amount = amount.parse::<f64>().unwrap();
89
90    // Update the current month's balance based on the transaction type.
91    match tx_type {
92        "Transfer" => {
93            let new_balance_from = current_month_balance[&from_method] - int_amount;
94            let new_balance_to = current_month_balance[&to_method] + int_amount;
95
96            // Update the current month's balance for both the "from" and "to" methods.
97            *current_month_balance.get_mut(&from_method).unwrap() = new_balance_from;
98            *current_month_balance.get_mut(&to_method).unwrap() = new_balance_to;
99        }
100        "Expense" => {
101            let new_balance = current_month_balance[tx_method] - int_amount;
102
103            // Update the current month's balance for the relevant method.
104            *current_month_balance.get_mut(tx_method).unwrap() = new_balance;
105        }
106        "Income" => {
107            let new_balance = current_month_balance[tx_method] + int_amount;
108            // Update the current month's balance for the relevant method.
109            *current_month_balance.get_mut(tx_method).unwrap() = new_balance;
110        }
111        _ => {}
112    }
113
114    // Add the current month's balances to the new balance data vector.
115    // It's done this way to match the tx method location
116    for i in &all_tx_methods {
117        new_balance_data.push(format!("{:.2}", current_month_balance[i]));
118    }
119
120    //
121    for i in 0..all_tx_methods.len() {
122        // the variable to keep track whether any changes were made to the tx method
123        let current_last_balance = last_balance[i].parse::<f64>().unwrap();
124        let mut current_change = format!("{:.2}", 0.0);
125
126        // add the proper values and changes based on the tx type
127        if tx_type == "Transfer" && all_tx_methods[i] == from_method {
128            current_change = format!("↓{:.2}", &int_amount);
129
130            let edited_balance = current_last_balance - int_amount;
131            last_balance_data.insert(&from_method, format!("{edited_balance:.2}"));
132        } else if tx_type == "Transfer" && all_tx_methods[i] == to_method {
133            current_change = format!("↑{:.2}", &int_amount);
134
135            let edited_balance = current_last_balance + int_amount;
136            last_balance_data.insert(&to_method, format!("{edited_balance:.2}"));
137        } else if tx_type != "Transfer" && all_tx_methods[i] == tx_method {
138            if tx_type == "Expense" {
139                current_change = format!("↓{:.2}", &int_amount);
140
141                let edited_balance = current_last_balance - int_amount;
142                last_balance_data.insert(&all_tx_methods[i], format!("{edited_balance:.2}"));
143            } else if tx_type == "Income" {
144                current_change = format!("↑{:.2}", &int_amount);
145
146                let edited_balance = current_last_balance + int_amount;
147                last_balance_data.insert(&all_tx_methods[i], format!("{edited_balance:.2}"));
148            }
149        }
150        new_changes_data.push(current_change);
151    }
152
153    let set_values = all_tx_methods
154        .iter()
155        .zip(new_balance_data.iter())
156        .map(|(method, value)| format!(r#""{method}" = "{value}""#,))
157        .collect::<Vec<_>>()
158        .join(", ");
159
160    let balance_query =
161        format!("UPDATE balance_all SET {set_values} WHERE id_num = {target_id_num}",);
162
163    let last_balance_query: String = if tx_type == "Transfer" {
164        format!(
165            r#"UPDATE balance_all SET "{from_method}" = "{}", "{to_method}" = "{}" WHERE id_num = {}"#,
166            last_balance_data[&from_method], last_balance_data[&to_method], last_balance_id
167        )
168    } else {
169        format!(
170            r#"UPDATE balance_all SET "{tx_method}" = "{}" WHERE id_num = {}"#,
171            last_balance_data[&tx_method.to_string()],
172            last_balance_id
173        )
174    };
175
176    let changes_query = format!(
177        "INSERT INTO changes_all (id_num, date, {}) VALUES ({}, ?, {})",
178        all_tx_methods
179            .iter()
180            .map(|s| format!(r#""{s}""#,))
181            .collect::<Vec<_>>()
182            .join(", "),
183        last_id,
184        new_changes_data
185            .iter()
186            .map(|s| format!(r#""{s}""#,))
187            .collect::<Vec<_>>()
188            .join(", ")
189    );
190
191    sp.execute(&balance_query, [])?;
192    sp.execute(&last_balance_query, [])?;
193    sp.execute(&changes_query, [date])?;
194    sp.commit()?;
195    Ok(())
196}