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

441 lines
13 KiB
Rust
Raw Normal View History

2022-11-12 21:58:08 +01:00
use channels::stocks::{detailed_product, detailed_products, Error};
2022-11-08 07:49:06 +01:00
use channels::AsyncClient;
use config::SharedAppConfig;
2022-11-11 16:32:07 +01:00
use db_utils::PgT;
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-12 21:58:08 +01:00
use crate::{begin_t, dbm_run};
2022-11-08 07:49:06 +01:00
pub async fn detailed_product(
2022-11-12 21:58:08 +01:00
input: detailed_product::Input,
db: Database,
2022-11-11 16:32:07 +01:00
_mqtt: AsyncClient,
_config: SharedAppConfig,
2022-11-08 07:49:06 +01:00
) -> detailed_product::Output {
2022-11-12 21:58:08 +01:00
let mut t = begin_t!(db, Error::InternalServerError);
let res = inner_detailed_product(input, &mut t, Some(_mqtt), Some(_config)).await;
t.commit().await.ok();
res
}
async fn inner_detailed_product(
input: detailed_product::Input,
t: &mut PgT<'_>,
_mqtt: Option<AsyncClient>,
_config: Option<SharedAppConfig>,
) -> detailed_product::Output {
let dbm = crate::db::FindProduct {
product_id: input.product_id,
};
let product = dbm_run!(dbm, &mut *t, Error::ProductNotFound(input.product_id));
let dbm = ProductsVariants {
product_ids: vec![input.product_id],
limit: Some(10.into()),
offset: Some(0.into()),
};
let variants = dbm_run!(dbm, &mut *t, Error::ProductVariants(vec![input.product_id]));
let dbm = ProductVariantsStock {
product_variant_ids: variants.iter().map(|p| p.id).collect(),
};
let stocks = dbm_run!(
dbm,
t,
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 = dbm_run!(
dbm,
t,
Error::VariantPhotos(variants.into_iter().map(|p| p.id).collect())
);
let mut variants = utils::vec_to_hash_vec(variants, 10, |p| p.product_id);
let mut stocks = utils::vec_to_hash_vec(stocks, 10, |s| s.product_variant_id);
let mut photos = utils::vec_to_hash_vec(photos, 10, |p| p.product_variant_id);
let product = utils::map_product(product, &mut variants, &mut stocks, &mut photos);
Ok(detailed_product::Output2 { product })
2022-11-08 07:49:06 +01:00
}
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-12 21:58:08 +01:00
let mut t = begin_t!(db, Error::InternalServerError);
2022-11-11 16:32:07 +01:00
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,
};
2022-11-12 21:58:08 +01:00
let products = dbm_run!(dbm, &mut *t, Error::Products);
2022-11-11 16:32:07 +01:00
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()),
};
2022-11-12 21:58:08 +01:00
let variants = dbm_run!(
dbm,
&mut *t,
Error::ProductVariants(products.into_iter().map(|p| p.id).collect(),)
);
2022-11-11 16:32:07 +01:00
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);
2022-11-12 21:58:08 +01:00
return Err(Error::VariantStocks(
2022-11-11 16:32:07 +01:00
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);
2022-11-12 21:58:08 +01:00
return Err(Error::VariantPhotos(
2022-11-11 16:32:07 +01:00
variants.into_iter().map(|p| p.id).collect(),
));
}
};
2022-11-12 21:58:08 +01:00
let mut variants = utils::vec_to_hash_vec(variants, 10, |p| p.product_id);
let mut stocks = utils::vec_to_hash_vec(stocks, 10, |s| s.product_variant_id);
let mut photos = utils::vec_to_hash_vec(photos, 10, |p| p.product_variant_id);
2022-11-11 16:32:07 +01:00
let products = products
.into_iter()
2022-11-12 21:58:08 +01:00
.map(|product| utils::map_product(product, &mut variants, &mut stocks, &mut photos))
2022-11-11 16:32:07 +01:00
.collect();
Ok(detailed_products::Details { products })
}
2022-11-12 21:58:08 +01:00
mod utils {
use std::collections::HashMap;
use std::hash::Hash;
use model::v2::*;
pub fn vec_to_hash_vec<Id: Hash + Eq, R, F: Fn(&R) -> Id>(
v: Vec<R>,
cap: usize,
f: F,
) -> HashMap<Id, Vec<R>> {
let len = v.len();
v.into_iter().fold(HashMap::with_capacity(len), |mut h, r| {
h.entry(f(&r))
.or_insert_with(|| Vec::with_capacity(cap))
.push(r);
h
})
}
pub fn map_product(
product: Product,
variants: &mut HashMap<ProductId, Vec<ProductVariant>>,
stocks: &mut HashMap<ProductVariantId, Vec<Stock>>,
photos: &mut HashMap<ProductVariantId, Vec<ProductLinkedPhoto>>,
) -> DetailedProduct {
let Product {
id,
name,
category,
deliver_days_flag,
} = product;
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(),
}
}
}
2022-11-11 16:32:07 +01:00
#[cfg(test)]
mod tests {
2022-11-13 15:04:14 +01:00
use channels::stocks::{detailed_product, detailed_products, Error};
2022-11-11 16:32:07 +01:00
use config::UpdateConfig;
use db_utils::PgT;
use model::v2::*;
use uuid::Uuid;
2022-11-13 15:04:14 +01:00
use crate::actions::load::{inner_detailed_product, inner_detailed_products};
2022-11-11 16:32:07 +01:00
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,
2022-11-14 16:51:24 +01:00
name: ProductVariantName::new(format!("{}", Uuid::new_v4())),
2022-11-11 16:32:07 +01:00
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);
2022-11-12 21:58:08 +01:00
2022-11-11 16:32:07 +01:00
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-13 15:04:14 +01:00
#[tokio::test]
async fn load_detail() {
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 res1 = inner_detailed_product(
detailed_product::Input {
product_id: product_1.id,
},
&mut t,
None,
None,
)
.await;
let res2 = inner_detailed_product(
detailed_product::Input {
product_id: product_3.id,
},
&mut t,
None,
None,
)
.await;
let res3 = inner_detailed_product(
detailed_product::Input {
product_id: (-1).into(),
},
&mut t,
None,
None,
)
.await;
testx::db_rollback!(t);
{
let res = res1.unwrap();
assert_eq!(res.product.id, product_1.id);
let product = res.product;
assert_eq!(product.variants.len(), 3);
for variant in product.variants {
assert_eq!(variant.photos.len(), 3);
assert_eq!(variant.stocks.len(), 1);
}
}
{
let res = res2.unwrap();
assert_eq!(res.product.id, product_3.id);
let product = res.product;
assert_eq!(product.variants.len(), 2);
for variant in product.variants {
assert_eq!(variant.photos.len(), 3);
assert_eq!(variant.stocks.len(), 1);
}
}
assert_eq!(res3, Err(Error::ProductNotFound((-1).into())));
}
2022-11-08 07:49:06 +01:00
}