1
use crate::error::{FinanceError, SplitError};
2
use sqlx::types::Uuid;
3
use sqlx::types::chrono::{DateTime, Utc};
4
use sqlx::{Connection, query_file};
5
use supp_macro::Builder;
6

            
7
#[derive(Debug, sqlx::FromRow, Builder)]
8
#[builder(error_kind = "FinanceError")]
9
pub 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

            
21
impl Split {
22
2
    pub async fn commit<E>(&self, conn: &mut E) -> Result<(), FinanceError>
23
2
    where
24
2
        E: Connection<Database = sqlx::Postgres>,
25
2
    {
26
2
        let mut tr = conn.begin().await?;
27

            
28
2
        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
2
        .execute(&mut *tr)
41
2
        .await?;
42
2
        tr.commit().await?;
43

            
44
2
        Ok(())
45
2
    }
46
}
47

            
48
#[cfg(test)]
49
mod 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
6
    async fn setup(pool: &PgPool) {
69
4
        CONTEXT
70
4
            .get_or_init(|| async {
71
                #[cfg(feature = "testlog")]
72
2
                let _ = env_logger::builder()
73
2
                    .is_test(true)
74
2
                    .filter_level(log::LevelFilter::Trace)
75
2
                    .try_init();
76
4
            })
77
4
            .await;
78
4
        COMMODITY
79
4
            .get_or_init(|| async {
80
2
                CommodityBuilder::new()
81
2
                    .fraction(1000)
82
2
                    .id(Uuid::new_v4())
83
2
                    .build()
84
2
                    .unwrap()
85
4
            })
86
4
            .await;
87
4
        let mut conn = pool.acquire().await.unwrap();
88
4
        COMMODITY.get().unwrap().commit(&mut *conn).await.unwrap();
89

            
90
4
        ACCOUNT
91
4
            .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
92
4
            .await;
93

            
94
4
        let mut conn = pool.acquire().await.unwrap();
95
4
        ACCOUNT.get().unwrap().commit(&mut *conn).await.unwrap();
96

            
97
4
        TRANSACTION
98
4
            .get_or_init(|| async {
99
2
                TransactionBuilder::new()
100
2
                    .id(Uuid::new_v4())
101
2
                    .post_date(Local::now().into())
102
2
                    .enter_date(Local::now().into())
103
2
                    .build()
104
2
                    .unwrap()
105
4
            })
106
4
            .await;
107

            
108
4
        let mut conn = pool.acquire().await.unwrap();
109
4
        sqlx::query_file!(
110
            "sql/transaction_insert.sql",
111
4
            &TRANSACTION.get().unwrap().id,
112
4
            &TRANSACTION.get().unwrap().post_date,
113
4
            &TRANSACTION.get().unwrap().enter_date
114
        )
115
4
        .execute(&mut *conn)
116
4
        .await
117
4
        .unwrap();
118
4
    }
119

            
120
    #[sqlx::test(migrations = "../migrations")]
121
    async fn test_split_store(pool: PgPool) -> anyhow::Result<()> {
122
        setup(&pool).await;
123
        let split = Split {
124
            id: Uuid::new_v4(),
125
            tx_id: TRANSACTION.get().unwrap().id,
126
            account_id: ACCOUNT.get().unwrap().id,
127
            commodity_id: COMMODITY.get().unwrap().id,
128
            value_num: 100,
129
            value_denom: 1,
130
            reconcile_state: None,
131
            reconcile_date: None,
132
            lot_id: None,
133
        };
134

            
135
        let mut conn = pool.acquire().await?;
136

            
137
        sqlx::query!(
138
            "SELECT post_date FROM transactions WHERE id = $1",
139
            TRANSACTION.get().unwrap().id
140
        )
141
        .fetch_one(&mut *conn)
142
        .await?;
143

            
144
        let mut conn = pool.begin().await?;
145
        sqlx::query!("INSERT INTO splits (id, tx_id, account_id, commodity_id, value_num, value_denom) VALUES ($1, $2, $3, $4, $5, $6)",
146
	    &split.id,
147
	    &split.tx_id,
148
	    &split.account_id,
149
	    &split.commodity_id,
150
	    &split.value_num,
151
	    &split.value_denom)
152
	    .execute(&mut *conn)
153
	    .await
154
	    ?;
155
        conn.commit().await?;
156

            
157
        let mut conn = pool.acquire().await?;
158

            
159
        let result = sqlx::query!("SELECT id FROM splits WHERE value_num = 100")
160
            .fetch_one(&mut *conn)
161
            .await?;
162

            
163
        assert_eq!(split.id, result.id);
164

            
165
        let split2 = Split {
166
            id: Uuid::new_v4(),
167
            value_num: 200,
168
            ..split
169
        };
170

            
171
        let mut conn = pool.acquire().await?;
172
        split2.commit(&mut *conn).await?;
173

            
174
        let result = sqlx::query!("SELECT id FROM splits WHERE value_num = 200")
175
            .fetch_one(&mut *conn)
176
            .await?;
177

            
178
        assert_eq!(split2.id, result.id);
179

            
180
        Ok(())
181
    }
182

            
183
    #[sqlx::test(migrations = "../migrations")]
184
    async fn test_split_builder(pool: PgPool) -> anyhow::Result<()> {
185
        setup(&pool).await;
186

            
187
        let split = Split::builder()
188
            .id(Uuid::new_v4())
189
            .tx_id(TRANSACTION.get().unwrap().id)
190
            .account_id(ACCOUNT.get().unwrap().id)
191
            .commodity_id(COMMODITY.get().unwrap().id)
192
            .value_num(100)
193
            .value_denom(1)
194
            .build()?;
195

            
196
        assert_eq!(split.value_num, 100);
197
        Ok(())
198
    }
199
}