Product variant in shopping cart

This commit is contained in:
eraden 2022-11-23 16:03:58 +01:00
parent abe0d693e8
commit f98d1e6903
12 changed files with 290 additions and 113 deletions

View File

@ -25,7 +25,7 @@ CREATE TABLE shopping_carts (
CREATE TABLE shopping_cart_items ( CREATE TABLE shopping_cart_items (
id serial NOT NULL, id serial NOT NULL,
product_id integer NOT NULL, product_variant_id integer NOT NULL,
shopping_cart_id integer, shopping_cart_id integer,
quantity integer DEFAULT 0 NOT NULL, quantity integer DEFAULT 0 NOT NULL,
quantity_unit "QuantityUnit" NOT NULL, quantity_unit "QuantityUnit" NOT NULL,

View File

@ -1,3 +1,4 @@
use model::v2::ProductVariantId;
use model::*; use model::*;
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@ -18,10 +19,10 @@ pub enum Error {
CartItems(ShoppingCartId), CartItems(ShoppingCartId),
#[error("Can't find shopping cart item doe to lack of identity")] #[error("Can't find shopping cart item doe to lack of identity")]
NoIdentity, NoIdentity,
#[error("Failed to update shopping cart item with id {shopping_cart_item_id:?} and/or product id {product_id:?}")] #[error("Failed to update shopping cart item with id {shopping_cart_item_id:?} and/or product variant id {product_variant_id:?}")]
Update { Update {
shopping_cart_item_id: Option<ShoppingCartItemId>, shopping_cart_item_id: Option<ShoppingCartItemId>,
product_id: Option<ProductId>, product_variant_id: Option<ProductVariantId>,
}, },
} }
@ -36,7 +37,7 @@ impl AllShoppingCartItems {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT shopping_cart_items.id, SELECT shopping_cart_items.id,
shopping_cart_items.product_id, shopping_cart_items.product_variant_id,
shopping_cart_items.shopping_cart_id, shopping_cart_items.shopping_cart_id,
shopping_cart_items.quantity, shopping_cart_items.quantity,
shopping_cart_items.quantity_unit shopping_cart_items.quantity_unit
@ -72,7 +73,7 @@ impl AccountShoppingCartItems {
Some(shopping_cart_id) => sqlx::query_as( Some(shopping_cart_id) => sqlx::query_as(
r#" r#"
SELECT shopping_cart_items.id as id, SELECT shopping_cart_items.id as id,
shopping_cart_items.product_id as product_id, shopping_cart_items.product_variant_id as product_variant_id,
shopping_cart_items.shopping_cart_id as shopping_cart_id, shopping_cart_items.shopping_cart_id as shopping_cart_id,
shopping_cart_items.quantity as quantity, shopping_cart_items.quantity as quantity,
shopping_cart_items.quantity_unit as quantity_unit shopping_cart_items.quantity_unit as quantity_unit
@ -88,7 +89,7 @@ ORDER BY shopping_cart_items.id
None => sqlx::query_as( None => sqlx::query_as(
r#" r#"
SELECT shopping_cart_items.id as id, SELECT shopping_cart_items.id as id,
shopping_cart_items.product_id as product_id, shopping_cart_items.product_variant_id as product_variant_id,
shopping_cart_items.shopping_cart_id as shopping_cart_id, shopping_cart_items.shopping_cart_id as shopping_cart_id,
shopping_cart_items.quantity as quantity, shopping_cart_items.quantity as quantity,
shopping_cart_items.quantity_unit as quantity_unit shopping_cart_items.quantity_unit as quantity_unit
@ -112,7 +113,7 @@ ORDER BY shopping_cart_items.id
#[derive(Debug)] #[derive(Debug)]
pub struct CreateShoppingCartItem { pub struct CreateShoppingCartItem {
pub product_id: ProductId, pub product_variant_id: ProductVariantId,
pub shopping_cart_id: ShoppingCartId, pub shopping_cart_id: ShoppingCartId,
pub quantity: Quantity, pub quantity: Quantity,
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
@ -126,12 +127,12 @@ impl CreateShoppingCartItem {
let msg = self; let msg = self;
sqlx::query_as( sqlx::query_as(
r#" r#"
INSERT INTO shopping_cart_items (product_id, shopping_cart_id, quantity, quantity_unit) INSERT INTO shopping_cart_items (product_variant_id, shopping_cart_id, quantity, quantity_unit)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4)
RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
"#, "#,
) )
.bind(msg.product_id) .bind(msg.product_variant_id)
.bind(msg.shopping_cart_id) .bind(msg.shopping_cart_id)
.bind(msg.quantity) .bind(msg.quantity)
.bind(msg.quantity_unit) .bind(msg.quantity_unit)
@ -148,7 +149,7 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
#[derive(Debug)] #[derive(Debug)]
pub struct UpdateShoppingCartItem { pub struct UpdateShoppingCartItem {
pub id: ShoppingCartItemId, pub id: ShoppingCartItemId,
pub product_id: ProductId, pub product_variant_id: ProductVariantId,
pub shopping_cart_id: ShoppingCartId, pub shopping_cart_id: ShoppingCartId,
pub quantity: Quantity, pub quantity: Quantity,
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
@ -163,13 +164,13 @@ impl UpdateShoppingCartItem {
sqlx::query_as( sqlx::query_as(
r#" r#"
UPDATE shopping_cart_items UPDATE shopping_cart_items
SET product_id = $2, shopping_cart_id = $3, quantity = $4, quantity_unit = $5 SET product_variant_id = $2, shopping_cart_id = $3, quantity = $4, quantity_unit = $5
WHERE id = $1 WHERE id = $1
RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
"#, "#,
) )
.bind(msg.id) .bind(msg.id)
.bind(msg.product_id) .bind(msg.product_variant_id)
.bind(msg.shopping_cart_id) .bind(msg.shopping_cart_id)
.bind(msg.quantity) .bind(msg.quantity)
.bind(msg.quantity_unit) .bind(msg.quantity_unit)
@ -197,7 +198,7 @@ impl DeleteShoppingCartItem {
r#" r#"
DELETE FROM shopping_cart_items DELETE FROM shopping_cart_items
WHERE id = $1 WHERE id = $1
RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
"#, "#,
) )
.bind(msg.id) .bind(msg.id)
@ -223,7 +224,7 @@ impl FindShoppingCartItem {
let msg = self; let msg = self;
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, product_id, shopping_cart_id, quantity, quantity_unit SELECT id, product_variant_id, shopping_cart_id, quantity, quantity_unit
FROM shopping_cart_items FROM shopping_cart_items
WHERE id = $1 WHERE id = $1
"#, "#,
@ -240,7 +241,7 @@ WHERE id = $1
#[derive(Debug)] #[derive(Debug)]
pub struct ActiveCartItemByProduct { pub struct ActiveCartItemByProduct {
pub product_id: ProductId, pub product_variant_id: ProductVariantId,
} }
impl ActiveCartItemByProduct { impl ActiveCartItemByProduct {
@ -252,19 +253,19 @@ impl ActiveCartItemByProduct {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT shopping_cart_items.id, SELECT shopping_cart_items.id,
shopping_cart_items.product_id, shopping_cart_items.product_variant_id,
shopping_cart_items.shopping_cart_id, shopping_cart_items.shopping_cart_id,
shopping_cart_items.quantity, shopping_cart_items.quantity,
shopping_cart_items.quantity_unit shopping_cart_items.quantity_unit
FROM shopping_cart_items FROM shopping_cart_items
INNER JOIN shopping_carts INNER JOIN shopping_carts
ON shopping_cart_items.shopping_cart_id = shopping_carts.id ON shopping_cart_items.shopping_cart_id = shopping_carts.id
WHERE product_id = $1 WHERE product_variant_id = $1
AND shopping_carts.state = $2 AND shopping_carts.state = $2
ORDER BY shopping_cart_items.id ASC ORDER BY shopping_cart_items.id ASC
"#, "#,
) )
.bind(msg.product_id) .bind(msg.product_variant_id)
.bind(model::ShoppingCartState::Active) .bind(model::ShoppingCartState::Active)
.fetch_optional(t) .fetch_optional(t)
.await .await
@ -290,7 +291,7 @@ impl CartItems {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, SELECT id,
product_id, product_variant_id,
shopping_cart_id, shopping_cart_id,
quantity, quantity,
quantity_unit quantity_unit
@ -313,7 +314,7 @@ ORDER BY shopping_cart_items.id ASC
pub struct RemoveCartItem { pub struct RemoveCartItem {
pub shopping_cart_id: ShoppingCartId, pub shopping_cart_id: ShoppingCartId,
pub shopping_cart_item_id: Option<ShoppingCartItemId>, pub shopping_cart_item_id: Option<ShoppingCartItemId>,
pub product_id: Option<ProductId>, pub product_variant_id: Option<ProductVariantId>,
} }
impl RemoveCartItem { impl RemoveCartItem {
@ -322,35 +323,35 @@ impl RemoveCartItem {
t: &mut sqlx::Transaction<'_, sqlx::Postgres>, t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Option<ShoppingCartItem>> { ) -> Result<Option<ShoppingCartItem>> {
let msg = self; let msg = self;
match (msg.shopping_cart_item_id, msg.product_id) { match (msg.shopping_cart_item_id, msg.product_variant_id) {
(Some(shopping_cart_item_id), None) => sqlx::query_as( (Some(shopping_cart_item_id), None) => sqlx::query_as(
r#" r#"
DELETE FROM shopping_cart_items DELETE FROM shopping_cart_items
WHERE shopping_cart_id = $1 AND id = $2 WHERE shopping_cart_id = $1 AND id = $2
RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
"#, "#,
) )
.bind(msg.shopping_cart_id) .bind(msg.shopping_cart_id)
.bind(shopping_cart_item_id), .bind(shopping_cart_item_id),
(Some(shopping_cart_item_id), Some(product_id)) => sqlx::query_as( (Some(shopping_cart_item_id), Some(product_variant_id)) => sqlx::query_as(
r#" r#"
DELETE FROM shopping_cart_items DELETE FROM shopping_cart_items
WHERE shopping_cart_id = $1 AND id = $2 AND product_id = $3 WHERE shopping_cart_id = $1 AND id = $2 AND product_variant_id = $3
RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
"#, "#,
) )
.bind(msg.shopping_cart_id) .bind(msg.shopping_cart_id)
.bind(shopping_cart_item_id) .bind(shopping_cart_item_id)
.bind(product_id), .bind(product_variant_id),
(None, Some(product_id)) => sqlx::query_as( (None, Some(product_variant_id)) => sqlx::query_as(
r#" r#"
DELETE FROM shopping_cart_items DELETE FROM shopping_cart_items
WHERE shopping_cart_id = $1 AND product_id = $2 WHERE shopping_cart_id = $1 AND product_variant_id = $2
RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
"#, "#,
) )
.bind(msg.shopping_cart_id) .bind(msg.shopping_cart_id)
.bind(product_id), .bind(product_variant_id),
_ => return Err(Error::NoIdentity), _ => return Err(Error::NoIdentity),
} }
.fetch_optional(t) .fetch_optional(t)
@ -359,7 +360,7 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
tracing::error!("{e:?}"); tracing::error!("{e:?}");
Error::Update { Error::Update {
shopping_cart_item_id: msg.shopping_cart_item_id, shopping_cart_item_id: msg.shopping_cart_item_id,
product_id: msg.product_id, product_variant_id: msg.product_variant_id,
} }
}) })
} }
@ -423,7 +424,7 @@ WHERE buyer_id = $1
async fn test_shopping_cart_item( async fn test_shopping_cart_item(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>, t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
shopping_cart_id: Option<ShoppingCartId>, shopping_cart_id: Option<ShoppingCartId>,
product_id: Option<ProductId>, product_variant_id: Option<ProductVariantId>,
) -> ShoppingCartItem { ) -> ShoppingCartItem {
let shopping_cart_id = match shopping_cart_id { let shopping_cart_id = match shopping_cart_id {
Some(id) => id, Some(id) => id,
@ -433,12 +434,12 @@ WHERE buyer_id = $1
.id .id
} }
}; };
let product_id = match product_id { let product_variant_id = match product_variant_id {
Some(id) => id, Some(id) => id,
_ => 1.into(), _ => 1.into(),
}; };
CreateShoppingCartItem { CreateShoppingCartItem {
product_id, product_variant_id,
shopping_cart_id, shopping_cart_id,
quantity: Quantity::from_u32(496879), quantity: Quantity::from_u32(496879),
quantity_unit: QuantityUnit::Gram, quantity_unit: QuantityUnit::Gram,
@ -565,7 +566,7 @@ WHERE buyer_id = $1
let updated = UpdateShoppingCartItem { let updated = UpdateShoppingCartItem {
id: item.id, id: item.id,
product_id: item.product_id, product_variant_id: item.product_variant_id,
shopping_cart_id: item.shopping_cart_id, shopping_cart_id: item.shopping_cart_id,
quantity: Quantity::from_u32(987979879), quantity: Quantity::from_u32(987979879),
quantity_unit: QuantityUnit::Kilogram, quantity_unit: QuantityUnit::Kilogram,
@ -579,7 +580,7 @@ WHERE buyer_id = $1
updated, updated,
ShoppingCartItem { ShoppingCartItem {
id: item.id, id: item.id,
product_id: item.product_id, product_variant_id: item.product_variant_id,
shopping_cart_id: item.shopping_cart_id, shopping_cart_id: item.shopping_cart_id,
quantity: Quantity::from_u32(987979879), quantity: Quantity::from_u32(987979879),
quantity_unit: QuantityUnit::Kilogram, quantity_unit: QuantityUnit::Kilogram,

View File

@ -50,6 +50,10 @@ pub enum Error {
UpdateProductStock(ProductVariantId), UpdateProductStock(ProductVariantId),
#[error("Failed to load all product photos")] #[error("Failed to load all product photos")]
AllProductPhotos, AllProductPhotos,
#[error("Failed to load products {0:?}")]
FindProducts(Vec<ProductVariantId>),
#[error("Failed to load product variants {0:?}")]
FindProductVariants(Vec<ProductVariantId>),
} }
pub mod rpc { pub mod rpc {
@ -113,6 +117,14 @@ pub mod rpc {
/// List of products with stock size and photos /// List of products with stock size and photos
async fn detailed_products(input: detailed_products::Input) -> detailed_products::Output; async fn detailed_products(input: detailed_products::Input) -> detailed_products::Output;
/// List of products for shopping cart
async fn shopping_cart_products(input: find_products::Input) -> find_products::Output;
/// List of products variants for shopping cart
async fn shopping_cart_product_variants(
input: find_product_variants::Input,
) -> find_product_variants::Output;
} }
pub async fn create_client(config: SharedAppConfig) -> StocksClient { pub async fn create_client(config: SharedAppConfig) -> StocksClient {

View File

@ -80,6 +80,46 @@ pub mod delete_product {
pub type Output = Result<Details, Error>; pub type Output = Result<Details, Error>;
} }
pub mod find_products {
use model::v2::*;
use crate::stocks::Error;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Input {
pub variant_ids: Vec<ProductVariantId>,
pub limit: Limit,
pub offset: Offset,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Details {
pub products: Vec<Product>,
}
pub type Output = Result<Details, Error>;
}
pub mod find_product_variants {
use model::v2::*;
use crate::stocks::Error;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Input {
pub variant_ids: Vec<ProductVariantId>,
pub limit: Limit,
pub offset: Offset,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Details {
pub product_variants: Vec<ProductVariant>,
}
pub type Output = Result<Details, Error>;
}
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Copy, Clone, Debug, PartialOrd, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Topic { pub enum Topic {
ProductCreated, ProductCreated,

View File

@ -173,7 +173,7 @@ pub struct Order {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct ShoppingCartItem { pub struct ShoppingCartItem {
pub id: ShoppingCartItemId, pub id: ShoppingCartItemId,
pub product_id: ProductId, pub product_variant_id: ProductVariantId,
pub shopping_cart_id: ShoppingCartId, pub shopping_cart_id: ShoppingCartId,
pub quantity: Quantity, pub quantity: Quantity,
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
@ -183,7 +183,7 @@ impl From<crate::ShoppingCartItem> for ShoppingCartItem {
fn from( fn from(
crate::ShoppingCartItem { crate::ShoppingCartItem {
id, id,
product_id, product_variant_id,
shopping_cart_id, shopping_cart_id,
quantity, quantity,
quantity_unit, quantity_unit,
@ -191,7 +191,7 @@ impl From<crate::ShoppingCartItem> for ShoppingCartItem {
) -> Self { ) -> Self {
Self { Self {
id, id,
product_id, product_variant_id,
shopping_cart_id, shopping_cart_id,
quantity, quantity,
quantity_unit, quantity_unit,
@ -233,13 +233,13 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
.map( .map(
|crate::ShoppingCartItem { |crate::ShoppingCartItem {
id, id,
product_id, product_variant_id,
shopping_cart_id, shopping_cart_id,
quantity, quantity,
quantity_unit, quantity_unit,
}| ShoppingCartItem { }| ShoppingCartItem {
id, id,
product_id, product_variant_id,
shopping_cart_id, shopping_cart_id,
quantity, quantity,
quantity_unit, quantity_unit,

View File

@ -14,6 +14,7 @@ use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
pub use crate::encrypt::*; pub use crate::encrypt::*;
use crate::v2::ProductVariantId;
#[derive(Debug, Hash, thiserror::Error)] #[derive(Debug, Hash, thiserror::Error)]
pub enum TransformError { pub enum TransformError {
@ -961,7 +962,7 @@ pub mod v2 {
pub use crate::{ pub use crate::{
Day, Days, FileName, Limit, LocalPath, Offset, PhotoId, Price, ProductCategory, ProductId, Day, Days, FileName, Limit, LocalPath, Offset, PhotoId, Price, ProductCategory, ProductId,
ProductLongDesc, ProductName, ProductPhotoId, ProductShortDesc, Quantity, QuantityUnit, ProductLongDesc, ProductName, ProductPhotoId, ProductShortDesc, Quantity, QuantityUnit,
RecordId, StockId, UniqueName, RecordId, ShoppingCartId, StockId, UniqueName,
}; };
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
@ -988,7 +989,7 @@ pub mod v2 {
#[cfg_attr(feature = "db", sqlx(transparent))] #[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Deref, From)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Deref, From)]
#[serde(transparent)] #[serde(transparent)]
pub struct ProductVariantId(RecordId); pub struct ProductVariantId(pub RecordId);
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DetailedProductVariant { pub struct DetailedProductVariant {
@ -1215,7 +1216,7 @@ pub struct ShoppingCartItemId(RecordId);
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ShoppingCartItem { pub struct ShoppingCartItem {
pub id: ShoppingCartItemId, pub id: ShoppingCartItemId,
pub product_id: ProductId, pub product_variant_id: ProductVariantId,
pub shopping_cart_id: ShoppingCartId, pub shopping_cart_id: ShoppingCartId,
pub quantity: Quantity, pub quantity: Quantity,
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,

View File

@ -1,4 +1,4 @@
use channels::stocks::{create_product, delete_product, update_product, Error}; use channels::stocks::{create_product, delete_product, find_products, update_product, Error};
use channels::AsyncClient; use channels::AsyncClient;
use config::SharedAppConfig; use config::SharedAppConfig;
use db_utils::PgT; use db_utils::PgT;
@ -195,6 +195,49 @@ pub async fn inner_delete_product(
dbm.run(t).await.map(|product| (input.product_id, product)) dbm.run(t).await.map(|product| (input.product_id, product))
} }
pub async fn find_products(
input: find_products::Input,
db: Database,
_mqtt: AsyncClient,
_config: SharedAppConfig,
) -> find_products::Output {
let mut t = begin_t!(db, Error::InternalServerError);
match inner_find_products(input, &mut t).await {
Ok(details) => {
if let Err(e) = t.commit().await {
tracing::error!("{}", e);
Err(Error::InternalServerError)
} else {
Ok(details)
}
}
Err(e) => {
t.rollback().await.ok();
Err(e)
}
}
}
async fn inner_find_products(
input: find_products::Input,
t: &mut PgT<'_>,
) -> find_products::Output {
let dbm = crate::db::ShoppingCartProducts {
variant_ids: input.variant_ids.clone(),
limit: input.limit,
offset: input.offset,
};
match dbm.run(t).await {
Ok(products) => Ok(find_products::Details { products }),
Err(e) => {
tracing::warn!("{}", e);
Err(Error::FindProducts(input.variant_ids))
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use config::UpdateConfig; use config::UpdateConfig;
@ -303,4 +346,11 @@ mod tests {
let (id, _new_product) = res.unwrap(); let (id, _new_product) = res.unwrap();
assert_eq!(id, product.id); assert_eq!(id, product.id);
} }
#[tokio::test]
async fn shopping_cart_products() {
testx::db_t_ref!(t);
testx::db_rollback!(t);
}
} }

View File

@ -1,5 +1,6 @@
use channels::stocks::{ use channels::stocks::{
create_product_variant, delete_product_variant, update_product_variant, Error, create_product_variant, delete_product_variant, find_product_variants, update_product_variant,
Error,
}; };
use channels::AsyncClient; use channels::AsyncClient;
use config::SharedAppConfig; use config::SharedAppConfig;
@ -151,6 +152,51 @@ async fn inner_delete_product_variant(
} }
} }
pub async fn find_product_variants(
input: find_product_variants::Input,
db: Database,
_mqtt: AsyncClient,
_config: SharedAppConfig,
) -> find_product_variants::Output {
let variant_ids = input.variant_ids.clone();
let mut t = begin_t!(db, Error::InternalServerError);
match inner_find_product_variants(input, &mut t).await {
Ok(details) => {
if let Err(e) = t.commit().await {
tracing::warn!("{}", e);
Err(Error::FindProductVariants(variant_ids))
} else {
Ok(details)
}
}
Err(e) => {
t.rollback().await.ok();
Err(e)
}
}
}
pub async fn inner_find_product_variants(
input: find_product_variants::Input,
t: &mut PgT<'_>,
) -> find_product_variants::Output {
let dbm = crate::db::ShoppingCartProductVariants {
variant_ids: input.variant_ids.clone(),
limit: input.limit,
offset: input.offset,
};
match dbm.run(t).await {
Ok(variants) => Ok(find_product_variants::Details {
product_variants: variants,
}),
Err(e) => {
tracing::warn!("{}", e);
Err(Error::FindProductVariants(input.variant_ids))
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use channels::stocks::{ use channels::stocks::{

View File

@ -1,5 +1,6 @@
use db_utils::PgT; use db_utils::PgT;
use model::v2::*; use model::v2::*;
use model::Ranged;
#[derive(Debug, PartialEq, thiserror::Error)] #[derive(Debug, PartialEq, thiserror::Error)]
pub enum Error { pub enum Error {
@ -7,12 +8,54 @@ pub enum Error {
CreateProductVariant, CreateProductVariant,
#[error("Failed to load variants for products {0:?}")] #[error("Failed to load variants for products {0:?}")]
ProductsVariants(Vec<ProductId>), ProductsVariants(Vec<ProductId>),
#[error("Failed to load variants with ids {0:?}")]
FindProductsVariants(Vec<ProductVariantId>),
#[error("Failed to delete product variant {0:?}")] #[error("Failed to delete product variant {0:?}")]
DeleteProductVariant(ProductVariantId), DeleteProductVariant(ProductVariantId),
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct ShoppingCartProductVariants {
pub variant_ids: Vec<ProductVariantId>,
pub limit: Limit,
pub offset: Offset,
}
impl ShoppingCartProductVariants {
pub async fn run(self, t: &mut PgT<'_>) -> Result<Vec<ProductVariant>> {
db_utils::MultiLoad::new(
t,
r#"
SELECT id,
product_id,
name,
short_description,
long_description,
price
FROM product_variants
WHERE
"#,
"shopping_cart_id = $1",
)
.with_sorting("products.id")
.with_padding(
self.limit.in_range(1..200).into_raw(),
self.offset.in_range(0..u32::MAX).into_raw(),
)
.load(
self.variant_ids.len(),
self.variant_ids.iter().map(|id| id.0),
|e| {
tracing::warn!("{e:?}");
Error::FindProductsVariants(self.variant_ids.clone())
},
)
.await
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct CreateProductVariant { pub struct CreateProductVariant {
pub product_id: ProductId, pub product_id: ProductId,

View File

@ -1,8 +1,8 @@
use db_utils::PgT; use db_utils::PgT;
use model::v2::*; use model::v2::*;
use model::{Ranged, ShoppingCartId}; use model::Ranged;
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Unable to load all products")] #[error("Unable to load all products")]
All, All,
@ -12,12 +12,10 @@ pub enum Error {
Update(ProductId), Update(ProductId),
#[error("Unable to delete product")] #[error("Unable to delete product")]
Delete(ProductId), Delete(ProductId),
#[error("Unable to find products for shopping cart")]
ShoppingCartProducts(ShoppingCartId),
#[error("Product with id {0} can't be found")] #[error("Product with id {0} can't be found")]
Single(ProductId), Single(ProductId),
#[error("Failed to load products for given ids")] #[error("Failed to load products for given variant ids")]
FindProducts, FindProductsByVariants(Vec<ProductVariantId>),
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@ -44,8 +42,8 @@ ORDER BY id
LIMIT $1 OFFSET $2 LIMIT $1 OFFSET $2
"#, "#,
) )
.bind(self.limit.max(1).min(200)) .bind(self.limit.in_range(1..200))
.bind(self.offset.max(0)) .bind(self.offset.in_range(0..u32::MAX))
.fetch_all(pool) .fetch_all(pool)
.await .await
.map_err(|e| { .map_err(|e| {
@ -193,71 +191,41 @@ RETURNING id,
#[derive(Debug)] #[derive(Debug)]
pub struct ShoppingCartProducts { pub struct ShoppingCartProducts {
pub shopping_cart_id: ShoppingCartId, pub variant_ids: Vec<ProductVariantId>,
pub limit: Limit, pub limit: Limit,
pub offset: Offset, pub offset: Offset,
} }
impl ShoppingCartProducts { impl ShoppingCartProducts {
pub async fn run(self, t: &mut PgT<'_>) -> Result<Vec<Product>> { pub async fn run(self, t: &mut PgT<'_>) -> Result<Vec<Product>> {
sqlx::query_as( db_utils::MultiLoad::new(
t,
r#" r#"
SELECT products.id, SELECT p.id,
products.name, p.name,
products.category, p.category,
products.deliver_days_flag p.deliver_days_flag
FROM products FROM products p
INNER JOIN shopping_cart_items ON shopping_cart_items.product_id = products.id INNER JOIN product_variants v
WHERE shopping_cart_id = $1 ON v.product_id = p.id
ORDER BY products.id WHERE
LIMIT $2 OFFSET $3
"#, "#,
"p.id = $1",
)
.with_sorting("p.id")
.with_padding(
self.limit.in_range(1..200).into_raw(),
self.offset.in_range(0..u32::MAX).into_raw(),
)
.load(
self.variant_ids.len(),
self.variant_ids.iter().map(|id| id.0),
|e| {
tracing::warn!("{e:?}");
Error::FindProductsByVariants(self.variant_ids.clone())
},
) )
.bind(self.shopping_cart_id)
.bind(self.limit.in_range(1..200))
.bind(self.offset.in_range(0..u32::MAX))
.fetch_all(t)
.await .await
.map_err(|e| {
tracing::warn!("{e:?}");
Error::ShoppingCartProducts(self.shopping_cart_id)
})
}
}
#[derive(Debug)]
pub struct FindProducts {
pub product_ids: Vec<ProductId>,
}
impl FindProducts {
pub async fn run(
self,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<Product>> {
let mut loader = db_utils::MultiLoad::new(
pool,
r#"
SELECT id,
name,
category,
deliver_days_flag
FROM products
WHERE
"#,
"products.id =",
)
.with_size(200);
loader
.load(
self.product_ids.len(),
self.product_ids.into_iter().map(|id| *id),
|e| {
tracing::warn!("{e:?}");
Error::FindProducts
},
)
.await
} }
} }

View File

@ -10,8 +10,8 @@ pub enum Error {
Update(StockId), Update(StockId),
#[error("Unable to delete stock {0:?}")] #[error("Unable to delete stock {0:?}")]
Delete(StockId), Delete(StockId),
#[error("Unable to delete all stock for variant {0:?}")] // #[error("Unable to delete all stock for variant {0:?}")]
DeleteAllProductStocks(ProductId), // DeleteAllProductStocks(ProductId),
#[error("Unable find stock for product")] #[error("Unable find stock for product")]
ProductVariantStock, ProductVariantStock,
#[error("Stock {0:?} does not exists")] #[error("Stock {0:?} does not exists")]

View File

@ -125,6 +125,22 @@ pub mod rpc {
) -> detailed_products::Output { ) -> detailed_products::Output {
actions::detailed_products(input, self.db, self.mqtt_client, self.config).await actions::detailed_products(input, self.db, self.mqtt_client, self.config).await
} }
async fn shopping_cart_products(
self,
_: context::Context,
input: find_products::Input,
) -> find_products::Output {
actions::find_products(input, self.db, self.mqtt_client, self.config).await
}
async fn shopping_cart_product_variants(
self,
_: context::Context,
input: find_product_variants::Input,
) -> find_product_variants::Output {
actions::find_product_variants(input, self.db, self.mqtt_client, self.config).await
}
} }
} }