rex_app/modifier/
shared.rs

1use anyhow::{Result, anyhow};
2use chrono::{Days, Local, Months, NaiveDate, NaiveTime};
3use rex_db::ConnCache;
4use rex_db::models::{Balance, DateNature, FetchNature, NewSearch, NewTx, Tx, TxType};
5use rex_shared::models::{Dollar, LAST_POSSIBLE_TIME};
6
7use crate::utils::parse_amount_nature_cent;
8
9pub(crate) fn tidy_balances(date: NaiveDate, db_conn: &mut impl ConnCache) -> Result<()> {
10    let nature = FetchNature::Monthly;
11
12    let txs = Tx::get_txs(date, nature, db_conn)?;
13
14    let current_balance = Balance::get_balance(date, nature, db_conn)?;
15
16    let mut last_balance = Balance::get_last_balance(date, nature, db_conn)?;
17
18    for tx in txs {
19        match tx.tx_type.as_str().into() {
20            TxType::Income | TxType::Borrow | TxType::LendRepay => {
21                let method_id = tx.from_method;
22                *last_balance.get_mut(&method_id).unwrap() += tx.amount;
23            }
24            TxType::Expense | TxType::Lend | TxType::BorrowRepay => {
25                let method_id = tx.from_method;
26                *last_balance.get_mut(&method_id).unwrap() -= tx.amount;
27            }
28
29            TxType::Transfer => {
30                let from_method_id = tx.from_method;
31                let to_method_id = tx.to_method.as_ref().unwrap();
32
33                *last_balance.get_mut(&from_method_id).unwrap() -= tx.amount;
34                *last_balance.get_mut(to_method_id).unwrap() += tx.amount;
35            }
36        }
37    }
38
39    let mut to_insert_balance = Vec::new();
40
41    for mut balance in current_balance {
42        let method_id = balance.method_id;
43        let last_balance = *last_balance.get(&method_id).unwrap();
44
45        if balance.balance != last_balance {
46            balance.balance = last_balance.value();
47            to_insert_balance.push(balance);
48        }
49    }
50
51    for to_insert in to_insert_balance {
52        to_insert.insert(db_conn)?;
53    }
54
55    // From bad delete txs, final balance got corrupted but monthly balances are fine.
56    // If does not match, trust the monthly balances
57    let mut final_balance = Balance::get_final_balance(db_conn)?;
58    let balance_highest_date = Balance::get_balance_highest_date(db_conn)?;
59
60    for balance in balance_highest_date {
61        let mut final_balance_entry = final_balance.get_mut(&balance.method_id).unwrap().clone();
62
63        if final_balance_entry.balance != balance.balance {
64            final_balance_entry.balance = balance.balance;
65            final_balance_entry.insert(db_conn)?;
66        }
67    }
68
69    Ok(())
70}
71
72pub fn parse_tx_fields<'a>(
73    date: &'a str,
74    details: &'a str,
75    from_method: &'a str,
76    to_method: &'a str,
77    amount: &'a str,
78    tx_type: &'a str,
79    db_conn: &impl ConnCache,
80) -> Result<NewTx<'a>> {
81    let date = date.parse::<NaiveDate>()?;
82
83    let local_now = Local::now().naive_local();
84
85    let new_date = if date == local_now.date() {
86        local_now
87    } else {
88        date.and_time(NaiveTime::MIN)
89    };
90
91    let details = if details.is_empty() {
92        None
93    } else {
94        Some(details)
95    };
96
97    let amount = Dollar::new(amount.parse()?).cent().value();
98
99    let from_method = db_conn.cache().get_method_id(from_method)?;
100    let to_method = if to_method.is_empty() {
101        None
102    } else {
103        Some(db_conn.cache().get_method_id(to_method)?)
104    };
105
106    let new_tx = NewTx::new(new_date, details, from_method, to_method, amount, tx_type);
107    Ok(new_tx)
108}
109
110pub fn parse_search_fields<'a>(
111    date: &'a str,
112    details: &'a str,
113    from_method: &'a str,
114    to_method: &'a str,
115    amount: &'a str,
116    tx_type: &'a str,
117    tags: &'a str,
118    db_conn: &impl ConnCache,
119) -> Result<NewSearch<'a>> {
120    let date_nature = if date.is_empty() {
121        None
122    } else {
123        let split_date = date.trim().split('-').collect::<Vec<&str>>();
124
125        match split_date.len() {
126            1 => {
127                let year = split_date[0].parse::<i32>()?;
128
129                let start_date = NaiveDate::from_ymd_opt(year, 1, 1)
130                    .ok_or_else(|| anyhow!("{year} is an invalid year"))?
131                    .and_time(NaiveTime::MIN);
132
133                let end_date = NaiveDate::from_ymd_opt(year + 1, 1, 1)
134                    .ok_or_else(|| anyhow!("{year} is an invalid year"))?
135                    .and_time(LAST_POSSIBLE_TIME);
136
137                Some(DateNature::ByYear {
138                    start_date,
139                    end_date,
140                })
141            }
142            2 => {
143                let year = split_date[0].parse::<i32>()?;
144                let month = split_date[1].parse::<u32>()?;
145
146                let start_date = NaiveDate::from_ymd_opt(year, month, 1)
147                    .ok_or_else(|| anyhow!("{year} or {month} value is invalid"))?;
148
149                let end_date = start_date + Months::new(1) - Days::new(1);
150
151                let start_date = start_date.and_time(NaiveTime::MIN);
152                let end_date = end_date.and_time(LAST_POSSIBLE_TIME);
153
154                Some(DateNature::ByMonth {
155                    start_date,
156                    end_date,
157                })
158            }
159            3 => {
160                let date = date.parse::<NaiveDate>()?.and_time(NaiveTime::MIN);
161                Some(DateNature::Exact(date))
162            }
163            _ => None,
164        }
165    };
166
167    let details = if details.is_empty() {
168        None
169    } else {
170        Some(details)
171    };
172
173    let from_method = if from_method.is_empty() {
174        None
175    } else {
176        Some(db_conn.cache().get_method_id(from_method)?)
177    };
178
179    let to_method = if to_method.is_empty() {
180        None
181    } else {
182        Some(db_conn.cache().get_method_id(to_method)?)
183    };
184
185    let amount = if amount.is_empty() {
186        None
187    } else {
188        parse_amount_nature_cent(amount)?
189    };
190
191    let tx_type = if tx_type.is_empty() {
192        None
193    } else {
194        Some(tx_type)
195    };
196
197    let tags = if tags.is_empty() {
198        None
199    } else {
200        let tags = tags.split(',').map(str::trim).collect::<Vec<&str>>();
201        let tags = tags
202            .iter()
203            .map(|t| db_conn.cache().get_tag_id(t))
204            .filter_map(Result::ok)
205            .collect::<Vec<i32>>();
206
207        Some(tags)
208    };
209
210    let search_tx = NewSearch::new(
211        date_nature,
212        details,
213        tx_type,
214        from_method,
215        to_method,
216        amount,
217        tags,
218    );
219
220    Ok(search_tx)
221}