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 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(LAST_POSSIBLE_TIME);
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
135 let end_date = start_date + Months::new(1) - Days::new(1);
136
137 let start_date = start_date.and_time(NaiveTime::MIN);
138 let end_date = end_date.and_time(LAST_POSSIBLE_TIME);
139
140 Some(DateNature::ByMonth {
141 start_date,
142 end_date,
143 })
144 }
145 3 => {
146 let date = date.parse::<NaiveDate>()?.and_time(NaiveTime::MIN);
147 Some(DateNature::Exact(date))
148 }
149 _ => None,
150 }
151 };
152
153 let details = if details.is_empty() {
154 None
155 } else {
156 Some(details)
157 };
158
159 let from_method = if from_method.is_empty() {
160 None
161 } else {
162 Some(db_conn.cache().get_method_id(from_method)?)
163 };
164
165 let to_method = if to_method.is_empty() {
166 None
167 } else {
168 Some(db_conn.cache().get_method_id(to_method)?)
169 };
170
171 let amount = if amount.is_empty() {
172 None
173 } else {
174 parse_amount_nature_cent(amount)?
175 };
176
177 let tx_type = if tx_type.is_empty() {
178 None
179 } else {
180 Some(tx_type)
181 };
182
183 let tags = if tags.is_empty() {
184 None
185 } else {
186 let tags = tags.split(',').map(str::trim).collect::<Vec<&str>>();
187 let tags = tags
188 .iter()
189 .map(|t| db_conn.cache().get_tag_id(t))
190 .filter_map(Result::ok)
191 .collect::<Vec<i32>>();
192
193 Some(tags)
194 };
195
196 let search_tx = NewSearch::new(
197 date_nature,
198 details,
199 tx_type,
200 from_method,
201 to_method,
202 amount,
203 tags,
204 );
205
206 Ok(search_tx)
207}