Skip to main content

finance/
split.rs

1use crate::error::{FinanceError, SplitError};
2use sqlx::types::Uuid;
3use sqlx::types::chrono::{DateTime, Utc};
4use sqlx::{Connection, query_file};
5use supp_macro::Builder;
6
7#[derive(Debug, sqlx::FromRow, Builder)]
8#[builder(error_kind = "FinanceError")]
9pub struct Split {
10    pub id: Uuid,
11    pub tx_id: Uuid,
12    pub account_id: Uuid,
13    pub commodity_id: Uuid,
14    pub reconcile_state: Option<bool>,
15    pub reconcile_date: Option<DateTime<Utc>>,
16    pub value_num: i64,
17    pub value_denom: i64,
18    pub lot_id: Option<Uuid>,
19}
20
21impl Split {
22    pub async fn commit<E>(&self, conn: &mut E) -> Result<(), FinanceError>
23    where
24        E: Connection<Database = sqlx::Postgres>,
25    {
26        let mut tr = conn.begin().await?;
27
28        query_file!(
29            "sql/split_insert.sql",
30            &self.id,
31            &self.tx_id,
32            &self.account_id,
33            &self.commodity_id,
34            self.reconcile_state,
35            self.reconcile_date,
36            &self.value_num,
37            &self.value_denom,
38            self.lot_id,
39        )
40        .execute(&mut *tr)
41        .await?;
42        tr.commit().await?;
43
44        Ok(())
45    }
46}
47
48#[cfg(test)]
49mod split_tests {
50    use super::*;
51    use crate::account::{Account, AccountBuilder};
52    use crate::commodity::{Commodity, CommodityBuilder};
53    use crate::transaction::{Transaction, TransactionBuilder};
54    #[cfg(feature = "testlog")]
55    use env_logger;
56    #[cfg(feature = "testlog")]
57    use log;
58    use sqlx::PgPool;
59    use sqlx::types::chrono::Local;
60    use tokio::sync::OnceCell;
61
62    /// Context for keeping environment intact
63    static CONTEXT: OnceCell<()> = OnceCell::const_new();
64    static COMMODITY: OnceCell<Commodity> = OnceCell::const_new();
65    static ACCOUNT: OnceCell<Account> = OnceCell::const_new();
66    static TRANSACTION: OnceCell<Transaction> = OnceCell::const_new();
67
68    async fn setup(pool: &PgPool) {
69        CONTEXT
70            .get_or_init(|| async {
71                #[cfg(feature = "testlog")]
72                let _ = env_logger::builder()
73                    .is_test(true)
74                    .filter_level(log::LevelFilter::Trace)
75                    .try_init();
76            })
77            .await;
78        COMMODITY
79            .get_or_init(|| async { CommodityBuilder::new().id(Uuid::new_v4()).build().unwrap() })
80            .await;
81        let mut conn = pool.acquire().await.unwrap();
82        COMMODITY.get().unwrap().commit(&mut *conn).await.unwrap();
83
84        ACCOUNT
85            .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
86            .await;
87
88        let mut conn = pool.acquire().await.unwrap();
89        ACCOUNT.get().unwrap().commit(&mut *conn).await.unwrap();
90
91        TRANSACTION
92            .get_or_init(|| async {
93                TransactionBuilder::new()
94                    .id(Uuid::new_v4())
95                    .post_date(Local::now().into())
96                    .enter_date(Local::now().into())
97                    .build()
98                    .unwrap()
99            })
100            .await;
101
102        let mut conn = pool.acquire().await.unwrap();
103        sqlx::query_file!(
104            "sql/transaction_insert.sql",
105            &TRANSACTION.get().unwrap().id,
106            &TRANSACTION.get().unwrap().post_date,
107            &TRANSACTION.get().unwrap().enter_date
108        )
109        .execute(&mut *conn)
110        .await
111        .unwrap();
112    }
113
114    #[sqlx::test(migrations = "../migrations")]
115    async fn test_split_store(pool: PgPool) -> anyhow::Result<()> {
116        setup(&pool).await;
117        let split = Split {
118            id: Uuid::new_v4(),
119            tx_id: TRANSACTION.get().unwrap().id,
120            account_id: ACCOUNT.get().unwrap().id,
121            commodity_id: COMMODITY.get().unwrap().id,
122            value_num: 100,
123            value_denom: 1,
124            reconcile_state: None,
125            reconcile_date: None,
126            lot_id: None,
127        };
128
129        let mut conn = pool.acquire().await?;
130
131        sqlx::query!(
132            "SELECT post_date FROM transactions WHERE id = $1",
133            TRANSACTION.get().unwrap().id
134        )
135        .fetch_one(&mut *conn)
136        .await?;
137
138        let mut conn = pool.begin().await?;
139        sqlx::query!("INSERT INTO splits (id, tx_id, account_id, commodity_id, value_num, value_denom) VALUES ($1, $2, $3, $4, $5, $6)",
140	    &split.id,
141	    &split.tx_id,
142	    &split.account_id,
143	    &split.commodity_id,
144	    &split.value_num,
145	    &split.value_denom)
146	    .execute(&mut *conn)
147	    .await
148	    ?;
149        conn.commit().await?;
150
151        let mut conn = pool.acquire().await?;
152
153        let result = sqlx::query!("SELECT id FROM splits WHERE value_num = 100")
154            .fetch_one(&mut *conn)
155            .await?;
156
157        assert_eq!(split.id, result.id);
158
159        let split2 = Split {
160            id: Uuid::new_v4(),
161            value_num: 200,
162            ..split
163        };
164
165        let mut conn = pool.acquire().await?;
166        split2.commit(&mut *conn).await?;
167
168        let result = sqlx::query!("SELECT id FROM splits WHERE value_num = 200")
169            .fetch_one(&mut *conn)
170            .await?;
171
172        assert_eq!(split2.id, result.id);
173
174        Ok(())
175    }
176
177    #[sqlx::test(migrations = "../migrations")]
178    async fn test_split_builder(pool: PgPool) -> anyhow::Result<()> {
179        setup(&pool).await;
180
181        let split = Split::builder()
182            .id(Uuid::new_v4())
183            .tx_id(TRANSACTION.get().unwrap().id)
184            .account_id(ACCOUNT.get().unwrap().id)
185            .commodity_id(COMMODITY.get().unwrap().id)
186            .value_num(100)
187            .value_denom(1)
188            .build()?;
189
190        assert_eq!(split.value_num, 100);
191        Ok(())
192    }
193}