1use crate::error::{FinanceError, PriceError};
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 Price {
10 pub id: Uuid,
11 pub date: DateTime<Utc>,
12 pub commodity_id: Uuid,
13 pub currency_id: Uuid,
14 pub commodity_split: Option<Uuid>,
15 pub currency_split: Option<Uuid>,
16 pub value_num: i64,
17 pub value_denom: i64,
18}
19
20impl Price {
21 pub async fn commit<E>(&self, conn: &mut E) -> Result<(), FinanceError>
22 where
23 E: Connection<Database = sqlx::Postgres>,
24 {
25 let mut tr = conn.begin().await?;
26
27 query_file!(
28 "sql/price_insert.sql",
29 &self.id,
30 &self.commodity_id,
31 &self.currency_id,
32 match &self.commodity_split {
33 &Some(s) => Some(s),
34 _ => None,
35 },
36 match &self.currency_split {
37 &Some(s) => Some(s),
38 _ => None,
39 },
40 &self.date,
41 &self.value_num,
42 &self.value_denom,
43 )
44 .execute(&mut *tr)
45 .await?;
46 tr.commit().await?;
47
48 Ok(())
49 }
50}
51
52#[cfg(test)]
53mod price_tests {
54 use super::*;
55 use crate::account::{Account, AccountBuilder};
56 use crate::commodity::{Commodity, CommodityBuilder};
57 #[cfg(feature = "testlog")]
58 use env_logger;
59 #[cfg(feature = "testlog")]
60 use log;
61 use sqlx::PgPool;
62 use sqlx::types::chrono::Local;
63 use tokio::sync::OnceCell;
64
65 static CONTEXT: OnceCell<()> = OnceCell::const_new();
67 static COMMODITY_1: OnceCell<Commodity> = OnceCell::const_new();
68 static COMMODITY_2: OnceCell<Commodity> = OnceCell::const_new();
69 static ACCOUNT_1: OnceCell<Account> = OnceCell::const_new();
70 static ACCOUNT_2: OnceCell<Account> = OnceCell::const_new();
71
72 async fn setup(pool: &PgPool) {
73 CONTEXT
74 .get_or_init(|| async {
75 #[cfg(feature = "testlog")]
76 let _ = env_logger::builder()
77 .is_test(true)
78 .filter_level(log::LevelFilter::Trace)
79 .try_init();
80 })
81 .await;
82
83 COMMODITY_1
84 .get_or_init(|| async { CommodityBuilder::new().id(Uuid::new_v4()).build().unwrap() })
85 .await;
86 let mut conn = pool.acquire().await.unwrap();
87 COMMODITY_1.get().unwrap().commit(&mut *conn).await.unwrap();
88
89 COMMODITY_2
90 .get_or_init(|| async { CommodityBuilder::new().id(Uuid::new_v4()).build().unwrap() })
91 .await;
92 let mut conn = pool.acquire().await.unwrap();
93 COMMODITY_2.get().unwrap().commit(&mut *conn).await.unwrap();
94
95 ACCOUNT_1
96 .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
97 .await;
98
99 let mut conn = pool.acquire().await.unwrap();
100 ACCOUNT_1.get().unwrap().commit(&mut *conn).await.unwrap();
101
102 ACCOUNT_2
103 .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
104 .await;
105
106 let mut conn = pool.acquire().await.unwrap();
107 ACCOUNT_2.get().unwrap().commit(&mut *conn).await.unwrap();
108 }
109
110 #[sqlx::test(migrations = "../migrations")]
111 async fn test_price_store(pool: PgPool) -> anyhow::Result<()> {
112 setup(&pool).await;
113 let price = Price {
114 id: Uuid::new_v4(),
115 date: Local::now().into(),
116 commodity_id: COMMODITY_1.get().unwrap().id,
117 currency_id: COMMODITY_2.get().unwrap().id,
118 commodity_split: None,
119 currency_split: None,
120 value_num: 100,
121 value_denom: 1,
122 };
123
124 let mut conn = pool.acquire().await?;
125 price.commit(&mut *conn).await?;
126
127 let mut conn = pool.acquire().await.unwrap();
128
129 let result = sqlx::query!("SELECT id FROM prices WHERE value_num = 100")
130 .fetch_one(&mut *conn)
131 .await
132 .unwrap();
133
134 assert_eq!(price.id, result.id);
135
136 Ok(())
137 }
138
139 #[sqlx::test(migrations = "../migrations")]
140 async fn test_price_builder(pool: PgPool) -> anyhow::Result<()> {
141 setup(&pool).await;
142
143 let price = Price::builder()
144 .id(Uuid::new_v4())
145 .date(Local::now().into())
146 .commodity_id(COMMODITY_1.get().unwrap().id)
147 .currency_id(COMMODITY_2.get().unwrap().id)
148 .value_num(100)
149 .value_denom(1)
150 .build()?;
151
152 assert_eq!(price.value_num, 100);
153 Ok(())
154 }
155}