bazzar/crates/stock_manager/src/actions/load.rs

337 lines
9.8 KiB
Rust
Raw Normal View History

2022-11-11 16:32:07 +01:00
use std::collections::HashMap;
2022-11-08 07:49:06 +01:00
use channels::stocks::{detailed_product, detailed_products};
use channels::AsyncClient;
use config::SharedAppConfig;
2022-11-11 16:32:07 +01:00
use db_utils::PgT;
use model::v2::{DetailedProduct, DetailedProductVariant, Product, ProductVariant};
use model::Limit;
2022-11-08 07:49:06 +01:00
2022-11-11 16:32:07 +01:00
use crate::db::{Database, PhotosForProductVariants, ProductVariantsStock, ProductsVariants};
2022-11-08 07:49:06 +01:00
pub async fn detailed_product(
2022-11-11 16:32:07 +01:00
_input: detailed_product::Input,
_db: Database,
_mqtt: AsyncClient,
_config: SharedAppConfig,
2022-11-08 07:49:06 +01:00
) -> detailed_product::Output {
todo!()
}
pub async fn detailed_products(
input: detailed_products::Input,
db: Database,
2022-11-11 16:32:07 +01:00
_mqtt: AsyncClient,
_config: SharedAppConfig,
2022-11-08 07:49:06 +01:00
) -> detailed_products::Output {
2022-11-11 16:32:07 +01:00
let mut t = match db.pool().begin().await {
Err(e) => {
tracing::error!("{}", e);
return Err(detailed_products::Error::InternalServerError);
}
Ok(t) => t,
};
let res = inner_detailed_products(input, &mut t, Some(_mqtt), Some(_config)).await;
t.commit().await.ok();
res
}
async fn inner_detailed_products(
input: detailed_products::Input,
t: &mut PgT<'_>,
_mqtt: Option<AsyncClient>,
_config: Option<SharedAppConfig>,
) -> detailed_products::Output {
let dbm = crate::db::AllProducts {
limit: input.limit,
offset: input.offset,
};
let products = match dbm.run(&mut *t).await {
Ok(products) => products,
Err(e) => {
tracing::error!("{}", e);
return Err(detailed_products::Error::Products);
}
};
let dbm = ProductsVariants {
product_ids: products.iter().map(|p| p.id).collect(),
limit: Some(Limit::from_u32(products.len() as u32 * 10)),
offset: Some(0.into()),
};
let variants = match dbm.run(&mut *t).await {
Ok(variants) => variants,
Err(e) => {
tracing::error!("{}", e);
return Err(detailed_products::Error::ProductVariants(
products.into_iter().map(|p| p.id).collect(),
));
}
};
let dbm = ProductVariantsStock {
product_variant_ids: variants.iter().map(|p| p.id).collect(),
};
let stocks = match dbm.run(&mut *t).await {
Ok(stocks) => stocks,
Err(e) => {
tracing::error!("{}", e);
return Err(detailed_products::Error::VariantStocks(
variants.into_iter().map(|p| p.id).collect(),
));
}
};
let dbm = PhotosForProductVariants {
product_variant_ids: variants.iter().map(|p| p.id).collect(),
};
let photos = match dbm.run(t).await {
Ok(photos) => photos,
Err(e) => {
tracing::error!("{}", e);
return Err(detailed_products::Error::VariantPhotos(
variants.into_iter().map(|p| p.id).collect(),
));
}
};
let mut variants = {
let len = variants.len();
variants
.into_iter()
.fold(HashMap::with_capacity(len), |mut h, variant| {
h.entry(variant.product_id)
.or_insert_with(|| Vec::with_capacity(10))
.push(variant);
h
})
};
let mut stocks = {
let len = stocks.len();
stocks
.into_iter()
.fold(HashMap::with_capacity(len), |mut h, stock| {
h.entry(stock.product_variant_id)
.or_insert_with(|| Vec::with_capacity(10))
.push(stock);
h
})
};
let mut photos =
photos
.into_iter()
.fold(HashMap::with_capacity(stocks.len()), |mut h, photo| {
h.entry(photo.product_variant_id)
.or_insert_with(|| Vec::with_capacity(10))
.push(photo);
h
});
let products = products
.into_iter()
.map(
|Product {
id,
name,
category,
deliver_days_flag,
}| DetailedProduct {
id,
name,
category,
deliver_days_flag,
variants: variants
.remove(&id)
.unwrap_or(vec![])
.into_iter()
.map(
|ProductVariant {
id,
product_id: _,
name,
short_description,
long_description,
price,
}| DetailedProductVariant {
id,
name,
short_description,
long_description,
price,
stocks: stocks.remove(&id).unwrap_or_default(),
photos: photos.remove(&id).unwrap_or_default(),
},
)
.collect(),
},
)
.collect();
Ok(detailed_products::Details { products })
}
#[cfg(test)]
mod tests {
use channels::stocks::detailed_products;
use config::UpdateConfig;
use db_utils::PgT;
use model::v2::*;
use uuid::Uuid;
use crate::actions::load::inner_detailed_products;
use crate::db::*;
pub struct NoOpts;
impl UpdateConfig for NoOpts {}
async fn test_product(t: &mut PgT<'_>) -> Product {
CreateProduct {
name: ProductName::new(format!("{}", Uuid::new_v4())),
category: None,
deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]),
}
.run(t)
.await
.unwrap()
}
async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant {
CreateProductVariant {
product_id,
name: ProductName::new(format!("{}", Uuid::new_v4())),
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
price: Default::default(),
}
.run(t)
.await
.unwrap()
}
async fn test_photo(t: &mut PgT<'_>) -> Photo {
CreatePhoto {
local_path: LocalPath::new(format!("{}", Uuid::new_v4())),
file_name: FileName::new(format!("{}", Uuid::new_v4())),
unique_name: UniqueName::new(format!("{}", Uuid::new_v4())),
}
.run(t)
.await
.unwrap()
}
async fn test_product_photo(
product_variant_id: ProductVariantId,
photo_id: PhotoId,
t: &mut PgT<'_>,
) -> ProductPhoto {
CreateProductPhoto {
product_variant_id,
photo_id,
}
.run(t)
.await
.unwrap()
}
async fn n_test_photo(
n: usize,
product_variant_id: ProductVariantId,
t: &mut PgT<'_>,
) -> Vec<(Photo, ProductPhoto)> {
let mut res = Vec::with_capacity(n);
for _ in 0..n {
let photo = test_photo(t).await;
let product_photo = test_product_photo(product_variant_id, photo.id, t).await;
res.push((photo, product_photo));
}
res
}
async fn test_stock(product_variant_id: ProductVariantId, pool: &mut PgT<'_>) -> Stock {
let quantity = Quantity::from_u32(345);
let quantity_unit = QuantityUnit::Piece;
CreateStock {
product_variant_id,
quantity_unit,
quantity,
}
.run(&mut *pool)
.await
.unwrap()
}
async fn n_test_variant(
variant_count: usize,
product_id: ProductId,
t: &mut PgT<'_>,
) -> Vec<(ProductVariant, Stock, Vec<(Photo, ProductPhoto)>)> {
let mut variants = Vec::with_capacity(variant_count);
for _ in 0..variant_count {
let variant = test_product_variant(product_id, t).await;
let stock = test_stock(variant.id, t).await;
let photos = n_test_photo(3, variant.id, t).await;
variants.push((variant, stock, photos));
}
variants
}
#[tokio::test]
async fn load_details() {
testx::db_t_ref!(t);
let product_1 = test_product(&mut t).await;
let _variants_1 = n_test_variant(3, product_1.id, &mut t).await;
let product_2 = test_product(&mut t).await;
let _variants_2 = n_test_variant(5, product_2.id, &mut t).await;
let product_3 = test_product(&mut t).await;
let _variants_2 = n_test_variant(2, product_3.id, &mut t).await;
let res = inner_detailed_products(
detailed_products::Input {
limit: Limit::from_u32(2000),
offset: Offset::from_u32(0),
},
&mut t,
None,
None,
)
.await;
testx::db_rollback!(t);
let mut res = res.unwrap();
assert_eq!(res.products.len(), 3);
let product = res.products.remove(0);
assert_eq!(product.variants.len(), 3);
for variant in product.variants {
assert_eq!(variant.photos.len(), 3);
assert_eq!(variant.stocks.len(), 1);
}
let product = res.products.remove(0);
assert_eq!(product.variants.len(), 5);
for variant in product.variants {
assert_eq!(variant.photos.len(), 3);
assert_eq!(variant.stocks.len(), 1);
}
let product = res.products.remove(0);
assert_eq!(product.variants.len(), 2);
for variant in product.variants {
assert_eq!(variant.photos.len(), 3);
assert_eq!(variant.stocks.len(), 1);
}
}
2022-11-08 07:49:06 +01:00
}