Skip to main content

rex_db/models/
activity_txs.rs

1use chrono::Datelike;
2use diesel::prelude::*;
3use diesel::result::Error;
4use rex_shared::models::Cent;
5use std::collections::HashMap;
6
7use crate::ConnCache;
8use crate::models::{
9    ActivityTxTag, AmountNature, AmountType, DateNature, EMPTY, FullTx, NewSearch, NewTx, Tag,
10    TxMethod, TxType,
11};
12use crate::schema::activity_txs;
13
14#[derive(Clone, Queryable, Selectable, Insertable)]
15pub struct ActivityTx {
16    pub id: i32,
17    date: Option<String>,
18    details: Option<String>,
19    from_method: Option<i32>,
20    to_method: Option<i32>,
21    amount: Option<i64>,
22    amount_type: Option<String>,
23    tx_type: Option<String>,
24    display_order: Option<i32>,
25    activity_num: i32,
26}
27
28pub struct FullActivityTx {
29    pub id: i32,
30    date: Option<String>,
31    pub details: Option<String>,
32    from_method: Option<TxMethod>,
33    to_method: Option<TxMethod>,
34    amount: Option<AmountNature>,
35    tx_type: Option<TxType>,
36    pub display_order: Option<i32>,
37    tags: Vec<Tag>,
38}
39
40#[derive(Insertable)]
41#[diesel(table_name = activity_txs)]
42pub struct NewActivityTx {
43    date: Option<String>,
44    details: Option<String>,
45    from_method: Option<i32>,
46    to_method: Option<i32>,
47    amount: Option<i64>,
48    amount_type: Option<String>,
49    tx_type: Option<String>,
50    display_order: Option<i32>,
51    activity_num: i32,
52}
53
54impl NewActivityTx {
55    #[allow(clippy::too_many_arguments)]
56    #[must_use]
57    pub fn new(
58        date: Option<String>,
59        details: Option<String>,
60        from_method: Option<i32>,
61        to_method: Option<i32>,
62        amount: Option<i64>,
63        amount_type: Option<String>,
64        tx_type: Option<String>,
65        display_order: Option<i32>,
66        activity_num: i32,
67    ) -> Self {
68        Self {
69            date,
70            details,
71            from_method,
72            to_method,
73            amount,
74            amount_type,
75            tx_type,
76            display_order,
77            activity_num,
78        }
79    }
80
81    #[must_use]
82    pub fn new_from_new_tx(tx: &NewTx, activity_num: i32) -> Self {
83        let date = Some(tx.date.to_string());
84
85        let details = Some(tx.details.unwrap_or_default().to_string());
86
87        let from_method = Some(tx.from_method);
88        let amount = Some(tx.amount);
89        let amount_type = Some(AmountType::Exact.into());
90        let tx_type = Some(tx.tx_type.to_string());
91
92        Self {
93            date,
94            details,
95            from_method,
96            to_method: tx.to_method,
97
98            amount,
99            amount_type,
100            tx_type,
101            display_order: None,
102            activity_num,
103        }
104    }
105
106    #[must_use]
107    pub fn new_from_full_tx(tx: &FullTx, set_display_order: bool, activity_num: i32) -> Self {
108        let date = Some(tx.date.to_string());
109
110        let details = tx.details.as_ref().map(std::string::ToString::to_string);
111
112        let from_method = Some(tx.from_method.id);
113        let to_method = tx.to_method.as_ref().map(|to_method| to_method.id);
114
115        let amount = Some(tx.amount.value());
116        let amount_type = Some(AmountType::Exact.into());
117        let tx_type = Some(tx.tx_type.to_string());
118
119        let display_order = if set_display_order {
120            Some(tx.display_order)
121        } else {
122            None
123        };
124
125        Self {
126            date,
127            details,
128            from_method,
129            to_method,
130            amount,
131            amount_type,
132            tx_type,
133            display_order,
134            activity_num,
135        }
136    }
137
138    #[must_use]
139    pub fn new_from_search_tx(tx: &NewSearch, activity_num: i32) -> Self {
140        let date = if let Some(date) = tx.date.as_ref() {
141            match date {
142                DateNature::Exact(d) => Some(d.to_string()),
143                DateNature::ByMonth {
144                    start_date,
145                    end_date: _,
146                } => Some(format!("{}-{}", start_date.year(), start_date.month())),
147                DateNature::ByYear {
148                    start_date,
149                    end_date: _,
150                } => Some(format!("{}", start_date.year())),
151            }
152        } else {
153            None
154        };
155
156        Self {
157            date,
158            details: tx.details.map(std::string::ToString::to_string),
159            from_method: tx.from_method,
160            to_method: tx.to_method,
161            amount: tx.amount.map(|a| a.extract().value()),
162            amount_type: tx.amount.map(|a| a.to_type().into()),
163            tx_type: tx.tx_type.map(std::string::ToString::to_string),
164            display_order: None,
165            activity_num,
166        }
167    }
168
169    pub fn insert(self, db_conn: &mut impl ConnCache) -> Result<ActivityTx, Error> {
170        use crate::schema::activity_txs::dsl::activity_txs;
171
172        diesel::insert_into(activity_txs)
173            .values(self)
174            .returning(ActivityTx::as_select())
175            .get_result(db_conn.conn())
176    }
177}
178
179impl ActivityTx {
180    #[allow(clippy::too_many_arguments)]
181    #[must_use]
182    pub fn new(
183        date: Option<String>,
184        details: Option<String>,
185        from_method: Option<i32>,
186        to_method: Option<i32>,
187        amount: Option<i64>,
188        amount_type: Option<String>,
189        tx_type: Option<String>,
190        display_order: Option<i32>,
191        activity_num: i32,
192        id: i32,
193    ) -> Self {
194        Self {
195            id,
196            date,
197            details,
198            from_method,
199            to_method,
200            amount,
201            amount_type,
202            tx_type,
203            display_order,
204            activity_num,
205        }
206    }
207
208    pub fn insert(self, db_conn: &mut impl ConnCache) -> Result<usize, Error> {
209        use crate::schema::activity_txs::dsl::activity_txs;
210
211        diesel::insert_into(activity_txs)
212            .values(self)
213            .execute(db_conn.conn())
214    }
215
216    pub fn convert_to_full_tx(
217        txs: Vec<&Self>,
218        db_conn: &mut impl ConnCache,
219    ) -> Result<Vec<FullActivityTx>, Error> {
220        let tx_ids = txs.iter().map(|t| t.id).collect::<Vec<i32>>();
221
222        let tx_tags = ActivityTxTag::get_by_tx_ids(tx_ids, db_conn)?;
223
224        let mut tx_tags_map = HashMap::new();
225
226        for tag in tx_tags {
227            tx_tags_map
228                .entry(tag.tx_id)
229                .or_insert(Vec::new())
230                .push(tag.tag_id);
231        }
232
233        let mut to_return = Vec::new();
234
235        for tx in txs {
236            let tags: Vec<Tag> = {
237                let tag_ids = tx_tags_map.get(&tx.id).unwrap_or(&EMPTY);
238                let mut v = Vec::with_capacity(tag_ids.len());
239                for tag_id in tag_ids {
240                    v.push(db_conn.cache().tags.get(tag_id).unwrap().clone());
241                }
242                v
243            };
244
245            let from_method = tx
246                .from_method
247                .as_ref()
248                .map(|method_id| db_conn.cache().tx_methods.get(method_id).unwrap().clone());
249
250            let to_method = tx
251                .to_method
252                .as_ref()
253                .map(|method_id| db_conn.cache().tx_methods.get(method_id).unwrap().clone());
254
255            let tx_type = tx.tx_type.as_ref().map(|tx_type| tx_type.as_str().into());
256
257            let mut amount = None;
258
259            if let Some(a) = tx.amount.as_ref() {
260                let amount_type: AmountType = tx.amount_type.as_ref().unwrap().as_str().into();
261                amount = Some(AmountNature::from_type(amount_type, Cent::new(*a)));
262            }
263
264            let full_tx = FullActivityTx {
265                id: tx.id,
266                date: tx.date.clone(),
267                details: tx.details.clone(),
268                from_method,
269                to_method,
270                amount,
271                tx_type,
272                tags,
273                display_order: tx.display_order,
274            };
275
276            to_return.push(full_tx);
277        }
278
279        Ok(to_return)
280    }
281}
282
283impl FullActivityTx {
284    #[must_use]
285    pub fn to_array(&self) -> Vec<String> {
286        let amount = if let Some(amount) = self.amount.as_ref() {
287            amount.to_string()
288        } else {
289            String::new()
290        };
291
292        let method_name = if let Some(tx_type) = self.tx_type.as_ref() {
293            match tx_type {
294                TxType::Transfer => {
295                    let to_method = self
296                        .to_method
297                        .as_ref()
298                        .map_or("?".to_string(), |m| m.name.clone());
299
300                    let from_method = self
301                        .from_method
302                        .as_ref()
303                        .map_or("?".to_string(), |m| m.name.clone());
304
305                    format!("{to_method} → {from_method}")
306                }
307                TxType::Income
308                | TxType::Expense
309                | TxType::Borrow
310                | TxType::Lend
311                | TxType::BorrowRepay
312                | TxType::LendRepay => self
313                    .from_method
314                    .as_ref()
315                    .map(|m| m.name.clone())
316                    .unwrap_or_default(),
317            }
318        } else {
319            self.from_method
320                .as_ref()
321                .map(|m| m.name.clone())
322                .unwrap_or_default()
323        };
324
325        vec![
326            self.date.clone().unwrap_or_default(),
327            self.details.clone().unwrap_or_default(),
328            method_name,
329            amount,
330            self.tx_type
331                .as_ref()
332                .map(std::string::ToString::to_string)
333                .unwrap_or_default(),
334            self.tags
335                .iter()
336                .map(|t| t.name.clone())
337                .collect::<Vec<String>>()
338                .join(", "),
339        ]
340    }
341}