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