1use chrono::{Datelike, Months, NaiveDate};
2use diesel::prelude::*;
3use diesel::result::Error;
4use diesel::upsert::excluded;
5use rex_shared::models::Cent;
6use std::collections::{HashMap, HashSet};
7
8use crate::ConnCache;
9use crate::models::FetchNature;
10use crate::schema::balances;
11
12#[derive(Clone, Debug, Queryable, Insertable, Selectable)]
13pub struct Balance {
14 pub method_id: i32,
15 pub year: i32,
16 pub month: i32,
17 pub balance: i64,
18 pub is_final_balance: bool,
19}
20
21impl Balance {
22 #[must_use]
23 pub fn new(
24 method_id: i32,
25 year: i32,
26 month: i32,
27 balance: i64,
28 is_final_balance: bool,
29 ) -> Self {
30 Balance {
31 method_id,
32 year,
33 month,
34 balance,
35 is_final_balance,
36 }
37 }
38
39 pub fn insert(&self, db_conn: &mut impl ConnCache) -> Result<usize, Error> {
40 use crate::schema::balances::dsl::{balance, balances, method_id, month, year};
41
42 diesel::insert_into(balances)
43 .values(self)
44 .on_conflict((method_id, year, month))
45 .do_update()
46 .set(balance.eq(excluded(balance)))
47 .execute(db_conn.conn())
48 }
49
50 pub fn insert_batch_final_balance(
51 txs: Vec<Balance>,
52 db_conn: &mut impl ConnCache,
53 ) -> Result<usize, Error> {
54 use crate::schema::balances::dsl::balances;
55
56 diesel::insert_into(balances)
57 .values(txs)
58 .execute(db_conn.conn())
59 }
60
61 pub fn update_final_balance(&self, db_conn: &mut impl ConnCache) -> Result<usize, Error> {
62 use crate::schema::balances::dsl::{balance, balances, is_final_balance, method_id};
63
64 diesel::update(
65 balances
66 .filter(method_id.eq(self.method_id))
67 .filter(is_final_balance.eq(true)),
68 )
69 .set(balance.eq(self.balance))
70 .execute(db_conn.conn())
71 }
72
73 pub fn get_balance_map(
74 date: NaiveDate,
75 db_conn: &mut impl ConnCache,
76 ) -> Result<HashMap<i32, Self>, Error> {
77 use crate::schema::balances::dsl::{balances, is_final_balance, method_id, month, year};
78
79 let date_year = date.year();
80 let date_month = date.month() as i32;
81
82 let tx_method_ids: Vec<i32> = db_conn.cache().tx_methods.keys().copied().collect();
83
84 let balance_list = balances
85 .filter(year.eq(date_year))
86 .filter(month.eq(date_month))
87 .filter(method_id.eq_any(tx_method_ids))
88 .filter(is_final_balance.eq(false))
89 .select(Self::as_select())
90 .load(db_conn.conn())?;
91
92 let mut balance_map: HashMap<i32, Self> =
93 balance_list.into_iter().map(|b| (b.method_id, b)).collect();
94
95 if balance_map.len() == db_conn.cache().tx_methods.len() {
96 return Ok(balance_map);
97 }
98
99 for bal in db_conn.cache().tx_methods.values() {
100 balance_map
101 .entry(bal.id)
102 .or_insert_with(|| Balance::new(bal.id, date_year, date_month, 0, false));
103 }
104
105 Ok(balance_map)
106 }
107
108 pub fn get_balance(
109 date: NaiveDate,
110 nature: FetchNature,
111 db_conn: &mut impl ConnCache,
112 ) -> Result<Vec<Self>, Error> {
113 use crate::schema::balances::dsl::{balances, is_final_balance, method_id, month, year};
114
115 let mut pending_balance_tx_methods = HashSet::new();
116
117 for key in db_conn.cache().tx_methods.keys().copied() {
118 pending_balance_tx_methods.insert(key);
119 }
120
121 let date_year = date.year();
122 let mut date_month = date.month() as i32;
123
124 if let FetchNature::Yearly = nature {
125 date_month = 1;
126 }
127
128 let tx_method_ids: Vec<i32> = db_conn.cache().tx_methods.keys().copied().collect();
129
130 if let FetchNature::All = nature {
131 let mut balance_list = Vec::new();
132
133 for m_id in pending_balance_tx_methods {
134 let new_bal = Balance::new(m_id, date_year, date_month, 0, false);
135 balance_list.push(new_bal);
136 }
137
138 return Ok(balance_list);
139 }
140
141 let mut balance_list = balances
142 .filter(year.eq(date_year))
143 .filter(month.eq(date_month))
144 .filter(method_id.eq_any(tx_method_ids))
145 .filter(is_final_balance.eq(false))
146 .select(Self::as_select())
147 .load(db_conn.conn())?;
148
149 if balance_list.len() == pending_balance_tx_methods.len() {
150 return Ok(balance_list);
151 }
152
153 for bal in &balance_list {
154 pending_balance_tx_methods.remove(&bal.method_id);
155 }
156
157 for m_id in pending_balance_tx_methods {
158 let new_bal = Balance::new(m_id, date_year, date_month, 0, false);
159 balance_list.push(new_bal);
160 }
161
162 Ok(balance_list)
163 }
164
165 pub fn get_last_balance(
167 date: NaiveDate,
168 nature: FetchNature,
169 db_conn: &mut impl ConnCache,
170 ) -> Result<HashMap<i32, Cent>, Error> {
171 use crate::schema::balances::dsl::{balances, is_final_balance, method_id, month, year};
172
173 let mut ongoing_date = date;
174
175 if let FetchNature::Yearly = nature {
176 ongoing_date = NaiveDate::from_ymd_opt(date.year(), 1, 1).unwrap();
177 }
178
179 let mut found_method_balances = HashMap::new();
180
181 let mut pending_balance_tx_methods = HashSet::new();
182
183 if let FetchNature::All = nature {
186 let mut to_return = HashMap::new();
187
188 for key in db_conn.cache().tx_methods.keys().copied() {
189 to_return.insert(key, Cent::new(0));
190 }
191
192 return Ok(to_return);
193 }
194
195 for key in db_conn.cache().tx_methods.keys().copied() {
196 pending_balance_tx_methods.insert(key);
197 }
198
199 for _ in 0..3 {
200 ongoing_date = ongoing_date - Months::new(1);
201
202 let date_year = ongoing_date.year();
203 let date_month = ongoing_date.month() as i32;
204
205 let Some(last_balances) = balances
206 .filter(year.eq(date_year))
207 .filter(month.eq(date_month))
208 .filter(method_id.eq_any(&pending_balance_tx_methods))
209 .filter(is_final_balance.eq(false))
210 .select(Self::as_select())
211 .load(db_conn.conn())
212 .optional()?
213 else {
214 continue;
215 };
216
217 for bal in last_balances {
218 if found_method_balances.contains_key(&bal.method_id) {
219 continue;
220 }
221
222 found_method_balances.insert(bal.method_id, Cent::new(bal.balance));
223 pending_balance_tx_methods.remove(&bal.method_id);
224 }
225 }
226
227 let date = date - Months::new(1);
229
230 if !pending_balance_tx_methods.is_empty() {
231 for mid in pending_balance_tx_methods {
232 if let Some(last_balance) = balances
233 .filter(method_id.eq(mid))
234 .filter(is_final_balance.eq(false))
235 .filter(
236 year.lt(date.year())
237 .or(year.eq(date.year()).and(month.lt(date.month() as i32))),
238 )
239 .order((year.desc(), month.desc()))
240 .select(Self::as_select())
241 .first::<Self>(db_conn.conn())
242 .optional()?
243 {
244 found_method_balances.insert(mid, Cent::new(last_balance.balance));
245 }
246 }
247 }
248
249 if found_method_balances.len() != db_conn.cache().tx_methods.len() {
250 for method in db_conn.cache().tx_methods.keys() {
251 if !found_method_balances.contains_key(method) {
252 found_method_balances.insert(*method, Cent::new(0));
253 }
254 }
255 }
256
257 Ok(found_method_balances)
258 }
259
260 pub fn get_final_balance(db_conn: &mut impl ConnCache) -> Result<HashMap<i32, Self>, Error> {
261 use crate::schema::balances::dsl::{balances, is_final_balance};
262
263 let balance_list = balances
264 .filter(is_final_balance.eq(true))
265 .select(Balance::as_select())
266 .load(db_conn.conn())?;
267
268 assert!(
269 (balance_list.len() == db_conn.cache().tx_methods.len()),
270 "Final balances are not set for all transaction methods"
271 );
272
273 let balance_map = balance_list.into_iter().map(|b| (b.method_id, b)).collect();
274
275 Ok(balance_map)
276 }
277
278 pub fn get_balance_highest_date(db_conn: &mut impl ConnCache) -> Result<Vec<Self>, Error> {
279 use crate::schema::balances::dsl::{balances, is_final_balance, month, year};
280
281 let total_methods = db_conn.cache().tx_methods.len();
282
283 balances
284 .filter(is_final_balance.eq(false))
285 .order((year.desc(), month.desc()))
286 .limit(total_methods as i64)
287 .select(Self::as_select())
288 .load(db_conn.conn())
289 }
290
291 pub fn get_highest_date(db_conn: &mut impl ConnCache) -> Result<Self, Error> {
292 use crate::schema::balances::dsl::{balances, is_final_balance, month, year};
293
294 let total_methods = db_conn.cache().tx_methods.len();
295
296 balances
297 .filter(is_final_balance.eq(false))
298 .order((year.desc(), month.desc()))
299 .limit(total_methods as i64)
300 .select(Self::as_select())
301 .first(db_conn.conn())
302 }
303}