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 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 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}