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