bazzar/crates/database_manager/src/stocks.rs

371 lines
9.0 KiB
Rust
Raw Normal View History

2022-04-18 22:07:52 +02:00
use actix::Message;
2022-05-06 11:47:18 +02:00
use model::{ProductId, Quantity, QuantityUnit, Stock, StockId};
2022-05-12 20:33:16 +02:00
use crate::{MultiLoad, Result};
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Unable to load all stocks")]
All,
#[error("Unable to create stock")]
Create,
#[error("Unable to update stock")]
Update,
#[error("Unable to delete stock")]
Delete,
2022-05-12 20:33:16 +02:00
#[error("Unable find stock for product")]
ProductStock,
2022-06-06 15:09:13 +02:00
#[error("Stock does not exists")]
NotFound,
}
#[derive(Message)]
#[rtype(result = "Result<Vec<model::Stock>>")]
pub struct AllStocks;
crate::db_async_handler!(AllStocks, all_stocks, Vec<Stock>, inner_all_stocks);
async fn all_stocks(
_msg: AllStocks,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<Stock>> {
sqlx::query_as(
r#"
SELECT id, product_id, quantity, quantity_unit
FROM stocks
2022-06-05 21:03:22 +02:00
ORDER BY id ASC
"#,
)
.fetch_all(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Stock(Error::All)
})
}
2022-06-06 15:09:13 +02:00
#[derive(Message)]
#[rtype(result = "Result<model::Stock>")]
pub struct FindStock {
pub id: StockId,
}
crate::db_async_handler!(FindStock, find_stock, Stock, inner_find_stock);
async fn find_stock(
msg: FindStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Stock> {
sqlx::query_as(
r#"
SELECT id, product_id, quantity, quantity_unit
FROM stocks
WHERE id = $1
"#,
)
.bind(msg.id)
.fetch_one(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-06-06 15:09:13 +02:00
dbg!(e);
crate::Error::Stock(Error::NotFound)
})
}
#[derive(Message)]
#[rtype(result = "Result<model::Stock>")]
pub struct CreateStock {
pub product_id: ProductId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
crate::db_async_handler!(CreateStock, create_stock, Stock, inner_create_stock);
async fn create_stock(
msg: CreateStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Stock> {
sqlx::query_as(
r#"
2022-04-19 16:49:30 +02:00
INSERT INTO stocks (product_id, quantity, quantity_unit)
VALUES ($1, $2, $3)
RETURNING id, product_id, quantity, quantity_unit
"#,
)
.bind(msg.product_id)
.bind(msg.quantity)
.bind(msg.quantity_unit)
.fetch_one(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-06-06 15:09:13 +02:00
dbg!(e);
2022-05-06 11:47:18 +02:00
crate::Error::Stock(Error::Create)
})
}
#[derive(Message)]
#[rtype(result = "Result<model::Stock>")]
pub struct UpdateStock {
pub id: StockId,
pub product_id: ProductId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
crate::db_async_handler!(UpdateStock, update_stock, Stock, inner_update_stock);
async fn update_stock(
msg: UpdateStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Stock> {
sqlx::query_as(
r#"
UPDATE stocks
2022-06-06 15:09:13 +02:00
SET product_id = $1,
quantity = $2,
quantity_unit = $3
WHERE id = $4
RETURNING id, product_id, quantity, quantity_unit
"#,
)
.bind(msg.product_id)
.bind(msg.quantity)
.bind(msg.quantity_unit)
.bind(msg.id)
.fetch_one(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Stock(Error::Update)
})
}
#[derive(Message)]
#[rtype(result = "Result<Option<model::Stock>>")]
pub struct DeleteStock {
pub stock_id: StockId,
}
crate::db_async_handler!(
DeleteStock,
delete_stock,
Option<model::Stock>,
inner_delete_stock
);
async fn delete_stock(
msg: DeleteStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Option<Stock>> {
sqlx::query_as(
r#"
DELETE FROM stocks
WHERE id = $1
RETURNING id, product_id, quantity, quantity_unit
"#,
)
.bind(msg.stock_id)
.fetch_optional(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Stock(Error::Delete)
})
}
2022-05-12 20:33:16 +02:00
#[derive(Message)]
#[rtype(result = "Result<Vec<model::Stock>>")]
pub struct ProductsStock {
pub product_ids: Vec<ProductId>,
}
crate::db_async_handler!(
ProductsStock,
product_stock,
Vec<model::Stock>,
inner_product_stock
);
async fn product_stock(
msg: ProductsStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<Stock>> {
MultiLoad::new(
2022-05-12 20:33:16 +02:00
pool,
r#"
2022-06-06 15:09:13 +02:00
SELECT id, product_id, quantity, quantity_unit
FROM stocks
WHERE
2022-05-12 20:33:16 +02:00
"#,
" product_id =",
)
.load(
msg.product_ids.len(),
msg.product_ids.into_iter().map(|id| *id),
|_e| crate::Error::Stock(Error::ProductStock),
)
.await
2022-05-12 20:33:16 +02:00
}
#[cfg(test)]
mod tests {
use config::UpdateConfig;
use fake::faker::lorem::en as lorem;
use fake::Fake;
use model::*;
use uuid::Uuid;
pub struct NoOpts;
impl UpdateConfig for NoOpts {}
use crate::*;
async fn test_product(pool: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Product {
create_product(
CreateProduct {
name: ProductName::new(format!("db stocks test product {}", Uuid::new_v4())),
short_description: ProductShortDesc::new(lorem::Paragraph(1..2).fake::<String>()),
long_description: ProductLongDesc::new(lorem::Paragraph(4..5).fake::<String>()),
category: None,
price: Price::from_u32(12321),
deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]),
},
pool,
)
.await
.unwrap()
}
async fn test_stock(
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
product_id: Option<ProductId>,
quantity: Option<Quantity>,
quantity_unit: Option<QuantityUnit>,
) -> Stock {
let product_id = match product_id {
Some(id) => id,
_ => test_product(&mut *pool).await.id,
};
let quantity = quantity.unwrap_or_else(|| Quantity::from_u32(345));
let quantity_unit = quantity_unit.unwrap_or(QuantityUnit::Piece);
super::create_stock(
CreateStock {
product_id,
quantity_unit,
quantity,
},
&mut *pool,
)
.await
.unwrap()
}
#[actix::test]
async fn create_stock() {
testx::db_t_ref!(t);
2022-06-05 21:03:22 +02:00
test_stock(&mut t, None, None, None).await;
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
}
#[actix::test]
async fn products_stock() {
testx::db_t_ref!(t);
let first = test_stock(&mut t, None, None, None).await;
let second = test_stock(&mut t, None, None, None).await;
let stocks: Vec<Stock> = super::product_stock(
ProductsStock {
product_ids: vec![first.product_id, second.product_id],
},
&mut t,
)
.await
.unwrap();
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
assert_eq!(stocks, vec![first, second]);
}
2022-06-05 21:03:22 +02:00
#[actix::test]
async fn all_stocks() {
testx::db_t_ref!(t);
2022-06-05 21:03:22 +02:00
let first = test_stock(&mut t, None, None, None).await;
let second = test_stock(&mut t, None, None, None).await;
let stocks: Vec<Stock> = super::all_stocks(AllStocks, &mut t).await.unwrap();
testx::db_rollback!(t);
assert_eq!(stocks, vec![first, second]);
}
#[actix::test]
async fn delete_stock() {
testx::db_t_ref!(t);
2022-06-05 21:03:22 +02:00
let first = test_stock(&mut t, None, None, None).await;
let second = test_stock(&mut t, None, None, None).await;
let deleted: Option<Stock> = super::delete_stock(
DeleteStock {
stock_id: second.id,
},
&mut t,
)
.await
.unwrap();
2022-06-06 15:09:13 +02:00
let reloaded = super::find_stock(FindStock { id: second.id }, &mut t).await;
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
assert_eq!(deleted, Some(second));
assert_ne!(deleted, Some(first));
2022-06-06 15:09:13 +02:00
assert_eq!(reloaded, Err(crate::Error::Stock(super::Error::NotFound)));
}
#[actix::test]
async fn update_stock() {
testx::db_t_ref!(t);
2022-06-06 15:09:13 +02:00
let first = test_stock(&mut t, None, None, None).await;
let second = test_stock(&mut t, None, None, None).await;
let another_product = test_product(&mut t).await;
let updated: Stock = super::update_stock(
UpdateStock {
id: second.id,
product_id: another_product.id,
quantity: Quantity::from_u32(19191),
quantity_unit: QuantityUnit::Gram,
},
&mut t,
)
.await
.unwrap();
let reloaded = super::find_stock(FindStock { id: second.id }, &mut t)
.await
.unwrap();
testx::db_rollback!(t);
assert_eq!(
updated,
Stock {
id: second.id,
product_id: another_product.id,
quantity: Quantity::from_u32(19191),
quantity_unit: QuantityUnit::Gram,
}
);
assert_ne!(updated, second);
assert_ne!(updated, first);
assert_eq!(reloaded, updated);
2022-06-05 21:03:22 +02:00
}
}