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;
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 => {
21                let method_id = tx.from_method;
22                *last_balance.get_mut(&method_id).unwrap() += tx.amount;
23            }
24            TxType::Expense => {
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    Ok(())
56}
57
58pub fn parse_tx_fields<'a>(
59    date: &'a str,
60    details: &'a str,
61    from_method: &'a str,
62    to_method: &'a str,
63    amount: &'a str,
64    tx_type: &'a str,
65    db_conn: &impl ConnCache,
66) -> Result<NewTx<'a>> {
67    let date = date.parse::<NaiveDate>()?;
68
69    let local_now = Local::now().naive_local();
70
71    let new_date = if date == local_now.date() {
72        local_now
73    } else {
74        date.and_time(NaiveTime::MIN)
75    };
76
77    let details = if details.is_empty() {
78        None
79    } else {
80        Some(details)
81    };
82
83    let amount = Dollar::new(amount.parse()?).cent().value();
84
85    let from_method = db_conn.cache().get_method_id(from_method)?;
86    let to_method = if to_method.is_empty() {
87        None
88    } else {
89        Some(db_conn.cache().get_method_id(to_method)?)
90    };
91
92    let new_tx = NewTx::new(new_date, details, from_method, to_method, amount, tx_type);
93    Ok(new_tx)
94}
95
96pub fn parse_search_fields<'a>(
97    date: &'a str,
98    details: &'a str,
99    from_method: &'a str,
100    to_method: &'a str,
101    amount: &'a str,
102    tx_type: &'a str,
103    tags: &'a str,
104    db_conn: &impl ConnCache,
105) -> Result<NewSearch<'a>> {
106    let date_nature = if date.is_empty() {
107        None
108    } else {
109        let split_date = date.trim().split('-').collect::<Vec<&str>>();
110
111        match split_date.len() {
112            1 => {
113                let year = split_date[0].parse::<i32>()?;
114
115                let start_date = NaiveDate::from_ymd_opt(year, 1, 1)
116                    .ok_or_else(|| anyhow!("{year} is an invalid year"))?
117                    .and_time(NaiveTime::MIN);
118
119                let end_date = NaiveDate::from_ymd_opt(year + 1, 1, 1)
120                    .ok_or_else(|| anyhow!("{year} is an invalid year"))?
121                    .and_time(NaiveTime::MIN);
122
123                Some(DateNature::ByYear {
124                    start_date,
125                    end_date,
126                })
127            }
128            2 => {
129                let year = split_date[0].parse::<i32>()?;
130                let month = split_date[1].parse::<u32>()?;
131
132                let start_date = NaiveDate::from_ymd_opt(year, month, 1)
133                    .ok_or_else(|| anyhow!("{year} or {month} value is invalid"))?
134                    .and_time(NaiveTime::MIN);
135
136                let end_date = start_date + Months::new(1) - Days::new(1);
137
138                Some(DateNature::ByMonth {
139                    start_date,
140                    end_date,
141                })
142            }
143            3 => {
144                let date = date.parse::<NaiveDate>()?.and_time(NaiveTime::MIN);
145                Some(DateNature::Exact(date))
146            }
147            _ => None,
148        }
149    };
150
151    let details = if details.is_empty() {
152        None
153    } else {
154        Some(details)
155    };
156
157    let from_method = if from_method.is_empty() {
158        None
159    } else {
160        Some(db_conn.cache().get_method_id(from_method)?)
161    };
162
163    let to_method = if to_method.is_empty() {
164        None
165    } else {
166        Some(db_conn.cache().get_method_id(to_method)?)
167    };
168
169    let amount = if amount.is_empty() {
170        None
171    } else {
172        parse_amount_nature_cent(amount)?
173    };
174
175    let tx_type = if tx_type.is_empty() {
176        None
177    } else {
178        Some(tx_type)
179    };
180
181    let tags = if tags.is_empty() {
182        None
183    } else {
184        let tags = tags.split(',').map(str::trim).collect::<Vec<&str>>();
185        let tags = tags
186            .iter()
187            .map(|t| db_conn.cache().get_tag_id(t))
188            .filter_map(Result::ok)
189            .collect::<Vec<i32>>();
190
191        Some(tags)
192    };
193
194    let search_tx = NewSearch::new(
195        date_nature,
196        details,
197        tx_type,
198        from_method,
199        to_method,
200        amount,
201        tags,
202    );
203
204    Ok(search_tx)
205}