Add categories
This commit is contained in:
parent
2316426a13
commit
376c1084ab
@ -26,6 +26,7 @@ CREATE TABLE shopping_carts (
|
||||
CREATE TABLE shopping_cart_items (
|
||||
id serial NOT NULL,
|
||||
product_variant_id integer NOT NULL,
|
||||
product_id integer NOT NULL,
|
||||
shopping_cart_id integer,
|
||||
quantity integer DEFAULT 0 NOT NULL,
|
||||
quantity_unit "QuantityUnit" NOT NULL,
|
||||
|
@ -83,6 +83,7 @@ pub async fn modify_item(msg: modify_item::Input, db: Database) -> modify_item::
|
||||
Some(item) => {
|
||||
let dbm = UpdateShoppingCartItem {
|
||||
id: item.id,
|
||||
product_id: msg.product_id,
|
||||
product_variant_id: msg.product_variant_id,
|
||||
shopping_cart_id: cart.id,
|
||||
quantity: msg.quantity,
|
||||
@ -99,6 +100,7 @@ pub async fn modify_item(msg: modify_item::Input, db: Database) -> modify_item::
|
||||
}
|
||||
None => {
|
||||
let dbm = CreateShoppingCartItem {
|
||||
product_id: msg.product_id,
|
||||
product_variant_id: msg.product_variant_id,
|
||||
shopping_cart_id: cart.id,
|
||||
quantity: msg.quantity,
|
||||
|
@ -37,6 +37,7 @@ impl AllShoppingCartItems {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
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.quantity,
|
||||
@ -73,6 +74,7 @@ impl AccountShoppingCartItems {
|
||||
Some(shopping_cart_id) => sqlx::query_as(
|
||||
r#"
|
||||
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.quantity as quantity,
|
||||
@ -89,6 +91,7 @@ ORDER BY shopping_cart_items.id
|
||||
None => sqlx::query_as(
|
||||
r#"
|
||||
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.quantity as quantity,
|
||||
@ -113,6 +116,7 @@ ORDER BY shopping_cart_items.id
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CreateShoppingCartItem {
|
||||
pub product_id: ProductId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
pub quantity: Quantity,
|
||||
@ -127,12 +131,13 @@ impl CreateShoppingCartItem {
|
||||
let msg = self;
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO shopping_cart_items (product_variant_id, shopping_cart_id, quantity, quantity_unit)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
INSERT INTO shopping_cart_items (product_variant_id, product_id, shopping_cart_id, quantity, quantity_unit)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, product_id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(msg.product_variant_id)
|
||||
.bind(msg.product_id)
|
||||
.bind(msg.shopping_cart_id)
|
||||
.bind(msg.quantity)
|
||||
.bind(msg.quantity_unit)
|
||||
@ -149,6 +154,7 @@ RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateShoppingCartItem {
|
||||
pub id: ShoppingCartItemId,
|
||||
pub product_id: ProductId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
pub quantity: Quantity,
|
||||
@ -164,12 +170,13 @@ impl UpdateShoppingCartItem {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE shopping_cart_items
|
||||
SET product_variant_id = $2, shopping_cart_id = $3, quantity = $4, quantity_unit = $5
|
||||
SET product_variant_id = $2, product_id = $3, shopping_cart_id = $4, quantity = $5, quantity_unit = $6
|
||||
WHERE id = $1
|
||||
RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
RETURNING id, product_id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
.bind(msg.product_id)
|
||||
.bind(msg.product_variant_id)
|
||||
.bind(msg.shopping_cart_id)
|
||||
.bind(msg.quantity)
|
||||
@ -198,7 +205,7 @@ impl DeleteShoppingCartItem {
|
||||
r#"
|
||||
DELETE FROM shopping_cart_items
|
||||
WHERE id = $1
|
||||
RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
RETURNING id, product_id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
@ -224,7 +231,7 @@ impl FindShoppingCartItem {
|
||||
let msg = self;
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
SELECT id, product_id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
FROM shopping_cart_items
|
||||
WHERE id = $1
|
||||
"#,
|
||||
@ -253,6 +260,7 @@ impl ActiveCartItemByProduct {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
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.quantity,
|
||||
@ -291,6 +299,7 @@ impl CartItems {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id,
|
||||
product_id,
|
||||
product_variant_id,
|
||||
shopping_cart_id,
|
||||
quantity,
|
||||
@ -328,7 +337,7 @@ impl RemoveCartItem {
|
||||
r#"
|
||||
DELETE FROM shopping_cart_items
|
||||
WHERE shopping_cart_id = $1 AND id = $2
|
||||
RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
RETURNING id, product_id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(msg.shopping_cart_id)
|
||||
@ -337,7 +346,7 @@ RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
r#"
|
||||
DELETE FROM shopping_cart_items
|
||||
WHERE shopping_cart_id = $1 AND id = $2 AND product_variant_id = $3
|
||||
RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
RETURNING id, product_id, product_id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(msg.shopping_cart_id)
|
||||
@ -347,7 +356,7 @@ RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
r#"
|
||||
DELETE FROM shopping_cart_items
|
||||
WHERE shopping_cart_id = $1 AND product_variant_id = $2
|
||||
RETURNING id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
RETURNING id, product_id, product_variant_id, shopping_cart_id, quantity, quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(msg.shopping_cart_id)
|
||||
@ -425,6 +434,7 @@ WHERE buyer_id = $1
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
shopping_cart_id: Option<ShoppingCartId>,
|
||||
product_variant_id: Option<ProductVariantId>,
|
||||
product_id: Option<ProductId>,
|
||||
) -> ShoppingCartItem {
|
||||
let shopping_cart_id = match shopping_cart_id {
|
||||
Some(id) => id,
|
||||
@ -438,7 +448,12 @@ WHERE buyer_id = $1
|
||||
Some(id) => id,
|
||||
_ => 1.into(),
|
||||
};
|
||||
let product_id = match product_id {
|
||||
Some(id) => id,
|
||||
_ => 1.into(),
|
||||
};
|
||||
CreateShoppingCartItem {
|
||||
product_id,
|
||||
product_variant_id,
|
||||
shopping_cart_id,
|
||||
quantity: Quantity::from_u32(496879),
|
||||
@ -453,7 +468,7 @@ WHERE buyer_id = $1
|
||||
async fn create() {
|
||||
testx::db_t_ref!(t);
|
||||
|
||||
test_shopping_cart_item(&mut t, None, None).await;
|
||||
test_shopping_cart_item(&mut t, None, None, None).await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
}
|
||||
@ -467,19 +482,19 @@ WHERE buyer_id = $1
|
||||
let mut items = Vec::with_capacity(9);
|
||||
|
||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await);
|
||||
|
||||
let cart2 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Active).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
|
||||
let cart3 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await);
|
||||
|
||||
let all = AllShoppingCartItems.run(&mut t).await.unwrap();
|
||||
|
||||
@ -496,19 +511,19 @@ WHERE buyer_id = $1
|
||||
let mut items = Vec::with_capacity(9);
|
||||
|
||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await;
|
||||
|
||||
let cart2 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Active).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
|
||||
let cart3 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await;
|
||||
|
||||
let all = AccountShoppingCartItems {
|
||||
account_id,
|
||||
@ -531,19 +546,19 @@ WHERE buyer_id = $1
|
||||
let mut items = Vec::with_capacity(9);
|
||||
|
||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await);
|
||||
|
||||
let cart2 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Active).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None, None).await);
|
||||
|
||||
let cart3 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None, None).await);
|
||||
|
||||
let all = AccountShoppingCartItems {
|
||||
account_id,
|
||||
@ -562,10 +577,11 @@ WHERE buyer_id = $1
|
||||
testx::db_t_ref!(t);
|
||||
let account_id = 1.into();
|
||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
let item = test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||
let item = test_shopping_cart_item(&mut t, Some(cart1.id), None, None).await;
|
||||
|
||||
let updated = UpdateShoppingCartItem {
|
||||
id: item.id,
|
||||
product_id: item.product_id,
|
||||
product_variant_id: item.product_variant_id,
|
||||
shopping_cart_id: item.shopping_cart_id,
|
||||
quantity: Quantity::from_u32(987979879),
|
||||
@ -581,6 +597,7 @@ WHERE buyer_id = $1
|
||||
ShoppingCartItem {
|
||||
id: item.id,
|
||||
product_variant_id: item.product_variant_id,
|
||||
product_id: item.product_id,
|
||||
shopping_cart_id: item.shopping_cart_id,
|
||||
quantity: Quantity::from_u32(987979879),
|
||||
quantity_unit: QuantityUnit::Kilogram,
|
||||
|
@ -41,6 +41,7 @@ pub mod remove_product {
|
||||
|
||||
pub mod modify_item {
|
||||
use model::v2::ProductVariantId;
|
||||
use model::ProductId;
|
||||
|
||||
use super::Error;
|
||||
|
||||
@ -48,6 +49,7 @@ pub mod modify_item {
|
||||
pub struct Input {
|
||||
pub buyer_id: model::AccountId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub product_id: ProductId,
|
||||
pub quantity: model::Quantity,
|
||||
pub quantity_unit: model::QuantityUnit,
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ pub mod detailed_product {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Output2 {
|
||||
pub struct Details {
|
||||
pub product: DetailedProduct,
|
||||
}
|
||||
|
||||
pub type Output = Result<Output2, Error>;
|
||||
pub type Output = Result<Details, Error>;
|
||||
}
|
||||
|
||||
pub mod detailed_products {
|
||||
|
@ -54,6 +54,8 @@ pub enum Error {
|
||||
FindProducts(Vec<ProductVariantId>),
|
||||
#[error("Failed to load product variants {0:?}")]
|
||||
FindProductVariants(Vec<ProductVariantId>),
|
||||
#[error("Failed to load all categories")]
|
||||
Categories,
|
||||
}
|
||||
|
||||
pub mod rpc {
|
||||
|
@ -18,6 +18,7 @@ pub mod create_product {
|
||||
pub long_description: ProductLongDesc,
|
||||
pub category: Option<ProductCategory>,
|
||||
pub price: Price,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
pub deliver_days_flag: Days,
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ pub mod create_product_variant {
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub price: Price,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
@ -38,6 +39,7 @@ pub mod update_product_variant {
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub price: Price,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -2,6 +2,7 @@ use chrono::NaiveDateTime;
|
||||
use derive_more::Deref;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::v2::{CategoryId, CategoryKey, CategoryName, CategorySvg, DetailedProduct};
|
||||
use crate::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@ -174,6 +175,7 @@ pub struct Order {
|
||||
pub struct ShoppingCartItem {
|
||||
pub id: ShoppingCartItemId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub product_id: ProductId,
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
@ -184,6 +186,7 @@ impl From<crate::ShoppingCartItem> for ShoppingCartItem {
|
||||
crate::ShoppingCartItem {
|
||||
id,
|
||||
product_variant_id,
|
||||
product_id,
|
||||
shopping_cart_id,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
@ -192,6 +195,7 @@ impl From<crate::ShoppingCartItem> for ShoppingCartItem {
|
||||
Self {
|
||||
id,
|
||||
product_variant_id,
|
||||
product_id,
|
||||
shopping_cart_id,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
@ -234,11 +238,13 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
|
||||
|crate::ShoppingCartItem {
|
||||
id,
|
||||
product_variant_id,
|
||||
product_id,
|
||||
shopping_cart_id,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
}| ShoppingCartItem {
|
||||
id,
|
||||
product_id,
|
||||
product_variant_id,
|
||||
shopping_cart_id,
|
||||
quantity,
|
||||
@ -250,43 +256,47 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Photo {
|
||||
pub id: crate::PhotoId,
|
||||
pub file_name: crate::FileName,
|
||||
pub id: PhotoId,
|
||||
pub file_name: FileName,
|
||||
pub url: String,
|
||||
pub unique_name: crate::UniqueName,
|
||||
pub unique_name: UniqueName,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct Category {
|
||||
pub name: String,
|
||||
pub key: String,
|
||||
pub svg: String,
|
||||
pub id: CategoryId,
|
||||
pub parent_id: Option<CategoryId>,
|
||||
pub name: CategoryName,
|
||||
pub key: CategoryKey,
|
||||
pub svg: CategorySvg,
|
||||
}
|
||||
|
||||
impl From<&crate::Category> for Category {
|
||||
fn from(crate::Category { name, key, svg }: &crate::Category) -> Self {
|
||||
fn from(&crate::Category { name, key, svg }: &crate::Category) -> Self {
|
||||
Self {
|
||||
name: (*name).into(),
|
||||
key: (*key).into(),
|
||||
svg: (*svg).into(),
|
||||
id: Default::default(),
|
||||
parent_id: None,
|
||||
name: CategoryName::from(name),
|
||||
key: CategoryKey::from(key),
|
||||
svg: CategorySvg::from(svg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
pub struct Product {
|
||||
pub id: crate::ProductId,
|
||||
pub name: crate::ProductName,
|
||||
pub short_description: crate::ProductShortDesc,
|
||||
pub long_description: crate::ProductLongDesc,
|
||||
pub id: ProductId,
|
||||
pub name: ProductName,
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub category: Option<Category>,
|
||||
pub price: crate::Price,
|
||||
pub price: Price,
|
||||
pub available: bool,
|
||||
pub quantity_unit: crate::QuantityUnit,
|
||||
pub deliver_days_flag: crate::Days,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
pub deliver_days_flag: Days,
|
||||
pub photos: Vec<Photo>,
|
||||
}
|
||||
|
||||
@ -315,7 +325,7 @@ impl<'path>
|
||||
): (
|
||||
crate::Product,
|
||||
&mut Vec<ProductLinkedPhoto>,
|
||||
&mut Vec<crate::Stock>,
|
||||
&mut Vec<Stock>,
|
||||
&'path str,
|
||||
),
|
||||
) -> Self {
|
||||
@ -325,21 +335,13 @@ impl<'path>
|
||||
let (available, quantity_unit) = pos
|
||||
.map(|idx| product_stocks.remove(idx))
|
||||
.map(|stock| (**stock.quantity > 0, stock.quantity_unit))
|
||||
.unwrap_or_else(|| (false, crate::QuantityUnit::Piece));
|
||||
.unwrap_or_else(|| (false, QuantityUnit::Piece));
|
||||
Self {
|
||||
id,
|
||||
name,
|
||||
short_description,
|
||||
long_description,
|
||||
category: category.and_then(|name| {
|
||||
crate::CATEGORIES.iter().find_map(|c| {
|
||||
if c.name == name.as_str() {
|
||||
Some(Category::from(c))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}),
|
||||
category: category.and_then(CategoryMapper::api_from_product_category),
|
||||
price,
|
||||
available,
|
||||
quantity_unit,
|
||||
@ -367,32 +369,32 @@ impl<'path>
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Deref)]
|
||||
#[serde(transparent)]
|
||||
pub struct Products(pub Vec<Product>);
|
||||
pub struct Products(pub Vec<DetailedProduct>);
|
||||
|
||||
impl
|
||||
From<(
|
||||
Vec<crate::Product>,
|
||||
Vec<ProductLinkedPhoto>,
|
||||
Vec<Stock>,
|
||||
String,
|
||||
)> for Products
|
||||
{
|
||||
fn from(
|
||||
(products, mut photos, mut products_stock, public_path): (
|
||||
Vec<crate::Product>,
|
||||
Vec<ProductLinkedPhoto>,
|
||||
Vec<crate::Stock>,
|
||||
String,
|
||||
),
|
||||
) -> Self {
|
||||
Self(
|
||||
products
|
||||
.into_iter()
|
||||
.map(|p| (p, &mut photos, &mut products_stock, public_path.as_str()).into())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
// impl
|
||||
// From<(
|
||||
// Vec<DetailedProduct>,
|
||||
// Vec<ProductLinkedPhoto>,
|
||||
// Vec<Stock>,
|
||||
// String,
|
||||
// )> for Products
|
||||
// {
|
||||
// fn from(
|
||||
// (products, mut photos, mut products_stock, public_path): (
|
||||
// Vec<DetailedProduct>,
|
||||
// Vec<ProductLinkedPhoto>,
|
||||
// Vec<Stock>,
|
||||
// String,
|
||||
// ),
|
||||
// ) -> Self {
|
||||
// Self(
|
||||
// products
|
||||
// .into_iter()
|
||||
// .map(|p| (p, &mut photos, &mut products_stock,
|
||||
// public_path.as_str()).into()) .collect(),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SignInInput {
|
||||
@ -453,6 +455,7 @@ pub struct DeleteItemOutput {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateItemInput {
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub product_id: ProductId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ use serde::de::{Error, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
pub use crate::encrypt::*;
|
||||
use crate::v2::ProductVariantId;
|
||||
use crate::v2::{CategoryKey, CategoryName, CategorySvg, ProductVariantId};
|
||||
|
||||
#[derive(Debug, Hash, thiserror::Error)]
|
||||
pub enum TransformError {
|
||||
@ -71,53 +71,94 @@ macro_rules! category_svg {
|
||||
};
|
||||
}
|
||||
|
||||
pub const CATEGORIES: [Category; 9] = [
|
||||
Category {
|
||||
name: Category::CAMERAS_NAME,
|
||||
key: Category::CAMERAS_KEY,
|
||||
svg: category_svg!("cameras"),
|
||||
},
|
||||
Category {
|
||||
name: Category::DRUGSTORE_NAME,
|
||||
key: Category::DRUGSTORE_KEY,
|
||||
svg: category_svg!("drugstore"),
|
||||
},
|
||||
Category {
|
||||
name: Category::SPEAKERS_NAME,
|
||||
key: Category::SPEAKERS_KEY,
|
||||
svg: category_svg!("speakers"),
|
||||
},
|
||||
Category {
|
||||
name: Category::PHONES_NAME,
|
||||
key: Category::PHONES_KEY,
|
||||
svg: category_svg!("phones"),
|
||||
},
|
||||
Category {
|
||||
name: Category::SWEETS_NAME,
|
||||
key: Category::SWEETS_KEY,
|
||||
svg: category_svg!("sweets"),
|
||||
},
|
||||
Category {
|
||||
name: Category::MEMORY_NAME,
|
||||
key: Category::MEMORY_KEY,
|
||||
svg: category_svg!("memory"),
|
||||
},
|
||||
Category {
|
||||
name: Category::PANTS_NAME,
|
||||
key: Category::PANTS_KEY,
|
||||
svg: category_svg!("pants"),
|
||||
},
|
||||
Category {
|
||||
name: Category::CLOTHES_NAME,
|
||||
key: Category::CLOTHES_KEY,
|
||||
svg: category_svg!("clothes"),
|
||||
},
|
||||
Category {
|
||||
name: Category::PLATES_NAME,
|
||||
key: Category::PLATES_KEY,
|
||||
svg: category_svg!("plates"),
|
||||
},
|
||||
];
|
||||
pub struct CategoryMapper;
|
||||
|
||||
impl CategoryMapper {
|
||||
pub const CATEGORIES: [Category; 9] = [
|
||||
Category {
|
||||
name: Category::CAMERAS_NAME,
|
||||
key: Category::CAMERAS_KEY,
|
||||
svg: category_svg!("cameras"),
|
||||
},
|
||||
Category {
|
||||
name: Category::DRUGSTORE_NAME,
|
||||
key: Category::DRUGSTORE_KEY,
|
||||
svg: category_svg!("drugstore"),
|
||||
},
|
||||
Category {
|
||||
name: Category::SPEAKERS_NAME,
|
||||
key: Category::SPEAKERS_KEY,
|
||||
svg: category_svg!("speakers"),
|
||||
},
|
||||
Category {
|
||||
name: Category::PHONES_NAME,
|
||||
key: Category::PHONES_KEY,
|
||||
svg: category_svg!("phones"),
|
||||
},
|
||||
Category {
|
||||
name: Category::SWEETS_NAME,
|
||||
key: Category::SWEETS_KEY,
|
||||
svg: category_svg!("sweets"),
|
||||
},
|
||||
Category {
|
||||
name: Category::MEMORY_NAME,
|
||||
key: Category::MEMORY_KEY,
|
||||
svg: category_svg!("memory"),
|
||||
},
|
||||
Category {
|
||||
name: Category::PANTS_NAME,
|
||||
key: Category::PANTS_KEY,
|
||||
svg: category_svg!("pants"),
|
||||
},
|
||||
Category {
|
||||
name: Category::CLOTHES_NAME,
|
||||
key: Category::CLOTHES_KEY,
|
||||
svg: category_svg!("clothes"),
|
||||
},
|
||||
Category {
|
||||
name: Category::PLATES_NAME,
|
||||
key: Category::PLATES_KEY,
|
||||
svg: category_svg!("plates"),
|
||||
},
|
||||
];
|
||||
|
||||
pub fn api_from_product_category(name: ProductCategory) -> Option<crate::api::Category> {
|
||||
Self::CATEGORIES
|
||||
.iter()
|
||||
.find(|category| category.name == name.as_str())
|
||||
.map(|&Category { name, key, svg }| crate::api::Category {
|
||||
id: Default::default(),
|
||||
parent_id: None,
|
||||
name: CategoryName::from(name),
|
||||
key: CategoryKey::from(key),
|
||||
svg: CategorySvg::from(svg),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn db_into_api(
|
||||
name: ProductCategory,
|
||||
categories: &[crate::v2::Category],
|
||||
) -> Option<crate::api::Category> {
|
||||
categories
|
||||
.iter()
|
||||
.find(|category| category.name.as_str() == name.as_str())
|
||||
.map(
|
||||
|crate::v2::Category {
|
||||
id,
|
||||
parent_id,
|
||||
name,
|
||||
key,
|
||||
svg,
|
||||
}| crate::api::Category {
|
||||
id: id.clone(),
|
||||
parent_id: parent_id.clone(),
|
||||
name: name.clone(),
|
||||
key: key.clone(),
|
||||
svg: svg.clone(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
|
||||
@ -943,7 +984,7 @@ impl ProductLongDesc {
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductCategory(String);
|
||||
pub struct ProductCategory(pub String);
|
||||
|
||||
impl ProductCategory {
|
||||
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||
@ -955,121 +996,6 @@ impl ProductCategory {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v2 {
|
||||
use derive_more::{Deref, Display, From};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use crate::{
|
||||
Day, Days, FileName, Limit, LocalPath, Offset, PhotoId, Price, ProductCategory, ProductId,
|
||||
ProductLongDesc, ProductName, ProductPhotoId, ProductShortDesc, Quantity, QuantityUnit,
|
||||
RecordId, ShoppingCartId, StockId, UniqueName,
|
||||
};
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductVariantName(String);
|
||||
|
||||
impl ProductVariantName {
|
||||
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Deref, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductVariantId(pub RecordId);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DetailedProductVariant {
|
||||
pub id: ProductVariantId,
|
||||
pub name: ProductVariantName,
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub price: Price,
|
||||
|
||||
pub stocks: Vec<Stock>,
|
||||
|
||||
pub photos: Vec<ProductLinkedPhoto>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DetailedProduct {
|
||||
pub id: ProductId,
|
||||
pub name: ProductName,
|
||||
pub category: Option<ProductCategory>,
|
||||
pub deliver_days_flag: Days,
|
||||
pub variants: Vec<DetailedProductVariant>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Product {
|
||||
pub id: ProductId,
|
||||
pub name: ProductName,
|
||||
pub category: Option<ProductCategory>,
|
||||
pub deliver_days_flag: Days,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProductVariant {
|
||||
pub id: ProductVariantId,
|
||||
pub product_id: ProductId,
|
||||
pub name: ProductVariantName,
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub price: Price,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Stock {
|
||||
pub id: StockId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Photo {
|
||||
pub id: PhotoId,
|
||||
pub local_path: LocalPath,
|
||||
pub file_name: FileName,
|
||||
pub unique_name: UniqueName,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProductLinkedPhoto {
|
||||
pub photo_id: PhotoId,
|
||||
pub local_path: LocalPath,
|
||||
pub file_name: FileName,
|
||||
pub unique_name: UniqueName,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProductPhoto {
|
||||
pub id: ProductPhotoId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub photo_id: PhotoId,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Product {
|
||||
@ -1216,6 +1142,7 @@ pub struct ShoppingCartItemId(RecordId);
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ShoppingCartItem {
|
||||
pub id: ShoppingCartItemId,
|
||||
pub product_id: ProductId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
pub quantity: Quantity,
|
||||
@ -1568,3 +1495,233 @@ pub struct OrderAddress {
|
||||
pub zip: Zip,
|
||||
pub phone: Phone,
|
||||
}
|
||||
|
||||
pub mod v2 {
|
||||
use derive_more::{Deref, Display, From};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::NonNegative;
|
||||
pub use crate::{
|
||||
Day, Days, FileName, Limit, LocalPath, Offset, PhotoId, Price, ProductCategory, ProductId,
|
||||
ProductLongDesc, ProductName, ProductPhotoId, ProductShortDesc, Quantity, QuantityUnit,
|
||||
RecordId, ShoppingCartId, StockId, UniqueName,
|
||||
};
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductVariantName(String);
|
||||
|
||||
impl ProductVariantName {
|
||||
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Deref, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductVariantId(pub RecordId);
|
||||
|
||||
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DetailedProductVariant {
|
||||
pub id: ProductVariantId,
|
||||
pub name: ProductVariantName,
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub price: Price,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
|
||||
pub stocks: Vec<Stock>,
|
||||
|
||||
pub photos: Vec<crate::api::Photo>,
|
||||
|
||||
pub available: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DetailedProduct {
|
||||
pub id: ProductId,
|
||||
pub name: ProductName,
|
||||
pub category: Option<crate::api::Category>,
|
||||
pub deliver_days_flag: Days,
|
||||
pub variants: Vec<DetailedProductVariant>,
|
||||
}
|
||||
|
||||
impl DetailedProduct {
|
||||
pub fn variant_for(&self, variant_id: ProductVariantId) -> Option<&DetailedProductVariant> {
|
||||
self.variants.iter().find(|v| v.id == variant_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Product {
|
||||
pub id: ProductId,
|
||||
pub name: ProductName,
|
||||
pub category: Option<ProductCategory>,
|
||||
pub deliver_days_flag: Days,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProductVariant {
|
||||
pub id: ProductVariantId,
|
||||
pub product_id: ProductId,
|
||||
pub name: ProductVariantName,
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub price: Price,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Stock {
|
||||
pub id: StockId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Photo {
|
||||
pub id: PhotoId,
|
||||
pub local_path: LocalPath,
|
||||
pub file_name: FileName,
|
||||
pub unique_name: UniqueName,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProductLinkedPhoto {
|
||||
pub photo_id: PhotoId,
|
||||
pub local_path: LocalPath,
|
||||
pub file_name: FileName,
|
||||
pub unique_name: UniqueName,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProductPhoto {
|
||||
pub id: ProductPhotoId,
|
||||
pub product_variant_id: ProductVariantId,
|
||||
pub photo_id: PhotoId,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Default,
|
||||
PartialOrd,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Deref,
|
||||
From,
|
||||
Display,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct CategoryId(NonNegative);
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Default,
|
||||
PartialOrd,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Deref,
|
||||
From,
|
||||
Display,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct CategoryName(pub String);
|
||||
|
||||
impl<'s> From<&'s str> for CategoryName {
|
||||
fn from(value: &'s str) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Default,
|
||||
PartialOrd,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Deref,
|
||||
From,
|
||||
Display,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct CategoryKey(pub String);
|
||||
|
||||
impl<'s> From<&'s str> for CategoryKey {
|
||||
fn from(value: &'s str) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Default,
|
||||
PartialOrd,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Deref,
|
||||
From,
|
||||
Display,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct CategorySvg(pub String);
|
||||
|
||||
impl<'s> From<&'s str> for CategorySvg {
|
||||
fn from(value: &'s str) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Category {
|
||||
pub id: CategoryId,
|
||||
pub parent_id: Option<CategoryId>,
|
||||
pub name: CategoryName,
|
||||
pub key: CategoryKey,
|
||||
pub svg: CategorySvg,
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ CREATE TABLE product_variants (
|
||||
short_description character varying NOT NULL,
|
||||
long_description character varying NOT NULL,
|
||||
price integer NOT NULL,
|
||||
quantity_unit "QuantityUnit" NOT NULL,
|
||||
CONSTRAINT non_negative CHECK ((price >= 0))
|
||||
);
|
||||
|
||||
@ -44,3 +45,11 @@ CREATE TABLE product_photos (
|
||||
product_variant_id integer REFERENCES product_variants(id) ON DELETE CASCADE NOT NULL,
|
||||
photo_id integer REFERENCES photos(id) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE categories (
|
||||
id serial NOT NULL PRIMARY KEY,
|
||||
parent_id int references categories (id) ON DELETE CASCADE,
|
||||
"name" varchar not null,
|
||||
"key" varchar not null,
|
||||
"svg" varchar not null
|
||||
);
|
||||
|
@ -2,9 +2,11 @@ use channels::stocks::{detailed_product, detailed_products, Error};
|
||||
use channels::AsyncClient;
|
||||
use config::SharedAppConfig;
|
||||
use db_utils::PgT;
|
||||
use model::Limit;
|
||||
use model::{Limit, Offset};
|
||||
|
||||
use crate::db::{Database, PhotosForProductVariants, ProductVariantsStock, ProductsVariants};
|
||||
use crate::db::{
|
||||
AllCategories, Database, PhotosForProductVariants, ProductVariantsStock, ProductsVariants,
|
||||
};
|
||||
use crate::{begin_t, dbm_run};
|
||||
|
||||
pub async fn detailed_product(
|
||||
@ -26,7 +28,7 @@ async fn inner_detailed_product(
|
||||
input: detailed_product::Input,
|
||||
t: &mut PgT<'_>,
|
||||
_mqtt: Option<AsyncClient>,
|
||||
_config: Option<SharedAppConfig>,
|
||||
config: Option<SharedAppConfig>,
|
||||
) -> detailed_product::Output {
|
||||
let dbm = crate::db::FindProduct {
|
||||
product_id: input.product_id,
|
||||
@ -55,13 +57,29 @@ async fn inner_detailed_product(
|
||||
Error::VariantPhotos(variants.into_iter().map(|p| p.id).collect())
|
||||
);
|
||||
|
||||
let dbm = AllCategories {
|
||||
limit: Limit::from_u32(2000),
|
||||
offset: Offset::from_u32(0),
|
||||
};
|
||||
let categories = dbm_run!(dbm, t, Error::Categories);
|
||||
|
||||
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);
|
||||
let product = utils::map_product(
|
||||
product,
|
||||
&mut variants,
|
||||
&mut stocks,
|
||||
&mut photos,
|
||||
&categories,
|
||||
&config
|
||||
.as_ref()
|
||||
.map(|c: &SharedAppConfig| c.lock().files().public_path())
|
||||
.unwrap_or_else(|| "https://example.com/".into()),
|
||||
);
|
||||
|
||||
Ok(detailed_product::Output2 { product })
|
||||
Ok(detailed_product::Details { product })
|
||||
}
|
||||
|
||||
pub async fn detailed_products(
|
||||
@ -83,7 +101,7 @@ async fn inner_detailed_products(
|
||||
input: detailed_products::Input,
|
||||
t: &mut PgT<'_>,
|
||||
_mqtt: Option<AsyncClient>,
|
||||
_config: Option<SharedAppConfig>,
|
||||
config: Option<SharedAppConfig>,
|
||||
) -> detailed_products::Output {
|
||||
let dbm = crate::db::AllProducts {
|
||||
limit: input.limit,
|
||||
@ -124,6 +142,11 @@ async fn inner_detailed_products(
|
||||
));
|
||||
}
|
||||
};
|
||||
let dbm = AllCategories {
|
||||
limit: Limit::from_u32(2000),
|
||||
offset: Offset::from_u32(0),
|
||||
};
|
||||
let categories = dbm_run!(dbm, t, Error::Categories);
|
||||
|
||||
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);
|
||||
@ -131,7 +154,19 @@ async fn inner_detailed_products(
|
||||
|
||||
let products = products
|
||||
.into_iter()
|
||||
.map(|product| utils::map_product(product, &mut variants, &mut stocks, &mut photos))
|
||||
.map(|product| {
|
||||
utils::map_product(
|
||||
product,
|
||||
&mut variants,
|
||||
&mut stocks,
|
||||
&mut photos,
|
||||
&categories,
|
||||
&config
|
||||
.as_ref()
|
||||
.map(|config: &SharedAppConfig| config.lock().files().public_path())
|
||||
.unwrap_or_else(|| "https:///example.com".into()),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(detailed_products::Details { products })
|
||||
@ -142,6 +177,7 @@ mod utils {
|
||||
use std::hash::Hash;
|
||||
|
||||
use model::v2::*;
|
||||
use model::CategoryMapper;
|
||||
|
||||
pub fn vec_to_hash_vec<Id: Hash + Eq, R, F: Fn(&R) -> Id>(
|
||||
v: Vec<R>,
|
||||
@ -162,6 +198,8 @@ mod utils {
|
||||
variants: &mut HashMap<ProductId, Vec<ProductVariant>>,
|
||||
stocks: &mut HashMap<ProductVariantId, Vec<Stock>>,
|
||||
photos: &mut HashMap<ProductVariantId, Vec<ProductLinkedPhoto>>,
|
||||
categories: &[Category],
|
||||
public_path: &str,
|
||||
) -> DetailedProduct {
|
||||
let Product {
|
||||
id,
|
||||
@ -172,7 +210,7 @@ mod utils {
|
||||
DetailedProduct {
|
||||
id,
|
||||
name,
|
||||
category,
|
||||
category: category.and_then(|name| CategoryMapper::db_into_api(name, categories)),
|
||||
deliver_days_flag,
|
||||
variants: variants
|
||||
.remove(&id)
|
||||
@ -186,14 +224,38 @@ mod utils {
|
||||
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(),
|
||||
quantity_unit,
|
||||
}| {
|
||||
let stocks = stocks.remove(&id).unwrap_or_default();
|
||||
DetailedProductVariant {
|
||||
id,
|
||||
name,
|
||||
short_description,
|
||||
long_description,
|
||||
price,
|
||||
quantity_unit,
|
||||
available: !stocks.is_empty(),
|
||||
stocks,
|
||||
photos: photos
|
||||
.remove(&id)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(
|
||||
|ProductLinkedPhoto {
|
||||
photo_id,
|
||||
local_path,
|
||||
file_name,
|
||||
unique_name,
|
||||
product_variant_id: _,
|
||||
}| model::api::Photo {
|
||||
id: photo_id,
|
||||
file_name,
|
||||
url: format!("{public_path}/{unique_name}"),
|
||||
unique_name,
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
@ -233,6 +295,7 @@ mod tests {
|
||||
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
|
||||
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
|
||||
price: Default::default(),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
}
|
||||
.run(t)
|
||||
.await
|
||||
|
@ -3,6 +3,7 @@ use channels::AsyncClient;
|
||||
use config::SharedAppConfig;
|
||||
use db_utils::PgT;
|
||||
use model::v2::*;
|
||||
use model::CategoryMapper;
|
||||
|
||||
use crate::begin_t;
|
||||
use crate::db::Database;
|
||||
@ -61,6 +62,7 @@ async fn inner_create_product(
|
||||
short_description: input.product.short_description,
|
||||
long_description: input.product.long_description,
|
||||
price: input.product.price,
|
||||
quantity_unit: input.product.quantity_unit,
|
||||
};
|
||||
let variant = match dbm.run(&mut *t).await {
|
||||
Ok(variant) => variant,
|
||||
@ -87,7 +89,9 @@ async fn inner_create_product(
|
||||
product: DetailedProduct {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
category: product.category,
|
||||
category: product
|
||||
.category
|
||||
.and_then(CategoryMapper::api_from_product_category),
|
||||
deliver_days_flag: product.deliver_days_flag,
|
||||
variants: vec![DetailedProductVariant {
|
||||
id: variant.id,
|
||||
@ -95,8 +99,10 @@ async fn inner_create_product(
|
||||
short_description: variant.short_description,
|
||||
long_description: variant.long_description,
|
||||
price: variant.price,
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
stocks: vec![stock],
|
||||
photos: vec![],
|
||||
available: false,
|
||||
}],
|
||||
},
|
||||
})
|
||||
@ -259,6 +265,7 @@ mod tests {
|
||||
long_description: ProductLongDesc::new("long description"),
|
||||
category: None,
|
||||
price: Default::default(),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
deliver_days_flag: Days(vec![]),
|
||||
},
|
||||
stock: create_product::StockInput {
|
||||
@ -286,6 +293,7 @@ mod tests {
|
||||
long_description: ProductLongDesc::new("long description"),
|
||||
category: None,
|
||||
price: Default::default(),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
deliver_days_flag: Days(vec![]),
|
||||
},
|
||||
stock: create_product::StockInput {
|
||||
|
@ -215,6 +215,7 @@ mod tests {
|
||||
short_description: ProductShortDesc::new(fakeit::words::sentence(4)),
|
||||
long_description: ProductLongDesc::new(fakeit::words::sentence(16)),
|
||||
price: Price::from_u32(650),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
}
|
||||
.run(t)
|
||||
.await
|
||||
|
@ -128,6 +128,7 @@ mod tests {
|
||||
short_description: ProductShortDesc::new(fakeit::words::sentence(4)),
|
||||
long_description: ProductLongDesc::new(fakeit::words::sentence(16)),
|
||||
price: Price::from_u32(650),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
}
|
||||
.run(t)
|
||||
.await
|
||||
|
@ -47,6 +47,7 @@ async fn inner_create_product_variant(
|
||||
short_description: input.short_description,
|
||||
long_description: input.long_description,
|
||||
price: input.price,
|
||||
quantity_unit: input.quantity_unit,
|
||||
};
|
||||
match dbm.run(t).await {
|
||||
Ok(variant) => Ok(create_product_variant::Details {
|
||||
@ -96,6 +97,7 @@ async fn inner_update_product_variant(
|
||||
short_description: input.short_description,
|
||||
long_description: input.long_description,
|
||||
price: input.price,
|
||||
quantity_unit: input.quantity_unit,
|
||||
};
|
||||
match dbm.run(t).await {
|
||||
Ok(product_variant) => Ok(update_product_variant::Details { product_variant }),
|
||||
@ -233,6 +235,7 @@ mod test {
|
||||
short_description: ProductShortDesc::new(fakeit::words::sentence(4)),
|
||||
long_description: ProductLongDesc::new(fakeit::words::sentence(16)),
|
||||
price: Price::from_u32(650),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
},
|
||||
t,
|
||||
)
|
||||
@ -259,6 +262,7 @@ mod test {
|
||||
short_description: short_description.clone(),
|
||||
long_description: long_description.clone(),
|
||||
price,
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
@ -296,6 +300,7 @@ mod test {
|
||||
short_description: short_description.clone(),
|
||||
long_description: long_description.clone(),
|
||||
price,
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
@ -312,6 +317,7 @@ mod test {
|
||||
short_description,
|
||||
long_description,
|
||||
price,
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
};
|
||||
assert_eq!(res.product_variant, expected);
|
||||
}
|
||||
|
124
crates/stock_manager/src/db/categories.rs
Normal file
124
crates/stock_manager/src/db/categories.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use db_utils::PgT;
|
||||
use model::v2::{Category, CategoryId, CategoryKey, CategoryName, CategorySvg};
|
||||
use model::{Limit, Offset};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Failed to load all categories")]
|
||||
All,
|
||||
#[error("Failed to create category")]
|
||||
Create,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub struct CreateCategory {
|
||||
pub parent_id: Option<CategoryId>,
|
||||
pub name: CategoryName,
|
||||
pub key: CategoryKey,
|
||||
pub svg: CategorySvg,
|
||||
}
|
||||
|
||||
impl CreateCategory {
|
||||
pub async fn run(self, t: &mut PgT<'_>) -> Result<Category> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO categories (parent_id, name, key, svg)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id,
|
||||
parent_id,
|
||||
name,
|
||||
key,
|
||||
svg
|
||||
"#,
|
||||
)
|
||||
.bind(&self.parent_id)
|
||||
.bind(&self.name)
|
||||
.bind(&self.key)
|
||||
.bind(&self.svg)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!("{e:?}");
|
||||
dbg!(e);
|
||||
Error::Create
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllCategories {
|
||||
pub limit: Limit,
|
||||
pub offset: Offset,
|
||||
}
|
||||
|
||||
impl AllCategories {
|
||||
pub async fn run(self, t: &mut PgT<'_>) -> Result<Vec<Category>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id,
|
||||
parent_id,
|
||||
name,
|
||||
key,
|
||||
svg
|
||||
FROM categories
|
||||
ORDER BY id ASC
|
||||
LIMIT $1 OFFSET $2
|
||||
"#,
|
||||
)
|
||||
.bind(self.limit)
|
||||
.bind(self.offset)
|
||||
.fetch_all(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!("{e:?}");
|
||||
dbg!(e);
|
||||
Error::All
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use config::UpdateConfig;
|
||||
use db_utils::PgT;
|
||||
use model::v2::{Category, CategoryName};
|
||||
use model::{Limit, Offset};
|
||||
|
||||
use crate::db::{AllCategories, CreateCategory, Database};
|
||||
|
||||
struct NoOpts;
|
||||
|
||||
impl UpdateConfig for NoOpts {}
|
||||
|
||||
async fn test_category(name: CategoryName, t: &mut PgT<'_>) -> Category {
|
||||
CreateCategory {
|
||||
parent_id: None,
|
||||
name,
|
||||
key: Default::default(),
|
||||
svg: Default::default(),
|
||||
}
|
||||
.run(t)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_all() {
|
||||
testx::db_t_ref!(t);
|
||||
|
||||
test_category("Electronics".into(), &mut t).await;
|
||||
test_category("Shoes".into(), &mut t).await;
|
||||
test_category("Pants".into(), &mut t).await;
|
||||
|
||||
let res = AllCategories {
|
||||
limit: Limit::from_u32(2000),
|
||||
offset: Offset::from_u32(0),
|
||||
}
|
||||
.run(&mut t)
|
||||
.await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
|
||||
assert_eq!(res.unwrap().len(), 3);
|
||||
}
|
||||
}
|
@ -2,12 +2,14 @@ use config::SharedAppConfig;
|
||||
use sqlx_core::pool::Pool;
|
||||
use sqlx_core::postgres::Postgres;
|
||||
|
||||
pub mod categories;
|
||||
pub mod photos;
|
||||
pub mod product_photos;
|
||||
pub mod product_variants;
|
||||
pub mod products;
|
||||
pub mod stocks;
|
||||
|
||||
pub use categories::*;
|
||||
pub use photos::*;
|
||||
pub use product_photos::*;
|
||||
pub use product_variants::*;
|
||||
|
@ -184,6 +184,7 @@ mod tests {
|
||||
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
|
||||
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
|
||||
price: Default::default(),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
}
|
||||
.run(t)
|
||||
.await
|
||||
|
@ -126,6 +126,7 @@ mod tests {
|
||||
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
|
||||
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
|
||||
price: Default::default(),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
}
|
||||
.run(t)
|
||||
.await
|
||||
|
@ -33,7 +33,8 @@ SELECT id,
|
||||
name,
|
||||
short_description,
|
||||
long_description,
|
||||
price
|
||||
price,
|
||||
quantity_unit
|
||||
FROM product_variants
|
||||
WHERE
|
||||
"#,
|
||||
@ -63,6 +64,7 @@ pub struct CreateProductVariant {
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub price: Price,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
impl CreateProductVariant {
|
||||
@ -74,14 +76,16 @@ INSERT INTO product_variants (
|
||||
name,
|
||||
short_description,
|
||||
long_description,
|
||||
price
|
||||
) VALUES ($1, $2, $3, $4, $5)
|
||||
price,
|
||||
quantity_unit
|
||||
) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id,
|
||||
product_id,
|
||||
name,
|
||||
short_description,
|
||||
long_description,
|
||||
price
|
||||
price,
|
||||
quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(self.product_id)
|
||||
@ -89,6 +93,7 @@ RETURNING id,
|
||||
.bind(self.short_description)
|
||||
.bind(self.long_description)
|
||||
.bind(self.price)
|
||||
.bind(self.quantity_unit)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@ -107,6 +112,7 @@ pub struct UpdateProductVariant {
|
||||
pub short_description: ProductShortDesc,
|
||||
pub long_description: ProductLongDesc,
|
||||
pub price: Price,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
impl UpdateProductVariant {
|
||||
@ -118,14 +124,16 @@ SET product_id = $2,
|
||||
name = $3,
|
||||
short_description = $4,
|
||||
long_description = $5,
|
||||
price = $6
|
||||
price = $6,
|
||||
quantity_unit = $7
|
||||
WHERE id = $1
|
||||
RETURNING id,
|
||||
product_id,
|
||||
name,
|
||||
short_description,
|
||||
long_description,
|
||||
price
|
||||
price,
|
||||
quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(self.product_variant_id)
|
||||
@ -134,6 +142,7 @@ RETURNING id,
|
||||
.bind(self.short_description)
|
||||
.bind(self.long_description)
|
||||
.bind(self.price)
|
||||
.bind(self.quantity_unit)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@ -161,7 +170,8 @@ SELECT pv.id,
|
||||
pv.name,
|
||||
pv.short_description,
|
||||
pv.long_description,
|
||||
pv.price
|
||||
pv.price,
|
||||
pv.quantity_unit
|
||||
FROM product_variants pv
|
||||
INNER JOIN products ps
|
||||
ON pv.product_id = ps.id
|
||||
@ -203,7 +213,8 @@ RETURNING id,
|
||||
name,
|
||||
short_description,
|
||||
long_description,
|
||||
price
|
||||
price,
|
||||
quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(self.product_variant_id)
|
||||
@ -234,6 +245,7 @@ mod tests {
|
||||
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
|
||||
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
|
||||
price: Default::default(),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
}
|
||||
.run(t)
|
||||
.await
|
||||
@ -268,6 +280,7 @@ mod tests {
|
||||
short_description: ProductShortDesc::new("aosdjajsodjaoisdjoajs"),
|
||||
long_description: ProductLongDesc::new("jsa a98dh 9ahsd ha89shd 98aus 98asu "),
|
||||
price: Default::default(),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
};
|
||||
let res = dbm.run(&mut t).await;
|
||||
|
||||
|
@ -223,6 +223,7 @@ mod tests {
|
||||
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
|
||||
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
|
||||
price: Default::default(),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
}
|
||||
.run(t)
|
||||
.await
|
||||
|
@ -1,6 +1,6 @@
|
||||
use model::api::OrderAddressInput;
|
||||
use model::v2::ProductVariantId;
|
||||
use model::{AccessTokenString, AddressId, PaymentMethod, RefreshTokenString};
|
||||
use model::{AccessTokenString, AddressId, PaymentMethod, ProductId, RefreshTokenString};
|
||||
use seed::fetch::{Header, Method, Request};
|
||||
|
||||
use crate::api::perform;
|
||||
@ -68,11 +68,13 @@ pub async fn sign_up(input: model::api::CreateAccountInput) -> NetRes<model::api
|
||||
pub async fn update_cart_item(
|
||||
access_token: &AccessTokenString,
|
||||
product_variant_id: ProductVariantId,
|
||||
product_id: ProductId,
|
||||
quantity: model::Quantity,
|
||||
quantity_unit: model::QuantityUnit,
|
||||
) -> NetRes<model::api::UpdateItemOutput> {
|
||||
let input = model::api::UpdateItemInput {
|
||||
product_variant_id,
|
||||
product_id,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
};
|
||||
@ -90,7 +92,7 @@ pub async fn update_cart(
|
||||
access_token: AccessTokenString,
|
||||
items: Vec<crate::shopping_cart::Item>,
|
||||
notes: String,
|
||||
payment_method: Option<model::PaymentMethod>,
|
||||
payment_method: Option<PaymentMethod>,
|
||||
) -> NetRes<model::api::UpdateCartOutput> {
|
||||
let input = model::api::UpdateCartInput {
|
||||
notes,
|
||||
@ -99,10 +101,12 @@ pub async fn update_cart(
|
||||
.map(
|
||||
|crate::shopping_cart::Item {
|
||||
product_variant_id,
|
||||
product_id,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
}| model::api::UpdateItemInput {
|
||||
product_variant_id,
|
||||
product_id,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use model::v2::{DetailedProduct, ProductVariantId};
|
||||
use model::v2::{DetailedProduct, DetailedProductVariant};
|
||||
use model::ProductId;
|
||||
use seed::Url;
|
||||
|
||||
@ -104,7 +104,7 @@ impl Products {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn filter_product_ids<F>(&self, filter: F) -> Vec<DetailedProduct>
|
||||
pub fn filter_product_ids<F>(&self, filter: F) -> Vec<ProductId>
|
||||
where
|
||||
F: Fn(&DetailedProduct) -> bool,
|
||||
{
|
||||
@ -113,4 +113,19 @@ impl Products {
|
||||
.filter_map(|id| self.products.get(id).filter(|&p| filter(p)).map(|p| p.id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn for_item<'item, 'products>(
|
||||
&'products self,
|
||||
item: &'item shopping_cart::Item,
|
||||
) -> Option<(
|
||||
&'item shopping_cart::Item,
|
||||
&'products DetailedProduct,
|
||||
&'products DetailedProductVariant,
|
||||
)> {
|
||||
self.products.get(&item.product_id).and_then(|product| {
|
||||
product
|
||||
.variant_for(item.product_variant_id)
|
||||
.map(|variant| (item, product, variant))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -28,22 +28,37 @@ pub mod layout {
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
pub fn view(
|
||||
pub fn view_with_categories<'category, Categories>(
|
||||
model: &crate::Model,
|
||||
content: Node<crate::Msg>,
|
||||
categories: Option<&[model::api::Category]>,
|
||||
) -> Node<crate::Msg> {
|
||||
let sidebar = match categories {
|
||||
Some(categories) => {
|
||||
let sidebar = super::sidebar::view(model, categories);
|
||||
div![
|
||||
C!["flex flex-col w-64 h-screen px-4 py-8 overflow-y-auto border-r"],
|
||||
sidebar
|
||||
]
|
||||
}
|
||||
_ => empty![],
|
||||
};
|
||||
categories: Categories,
|
||||
) -> Node<crate::Msg>
|
||||
where
|
||||
Categories: Iterator<Item = &'category model::api::Category>,
|
||||
{
|
||||
let sidebar = div![
|
||||
C!["flex flex-col w-64 h-screen px-4 py-8 overflow-y-auto border-r"],
|
||||
super::sidebar::view(model, categories)
|
||||
];
|
||||
let notifications = crate::shared::notification::view(model);
|
||||
|
||||
view(notifications, sidebar, content)
|
||||
}
|
||||
|
||||
pub fn view_without_categories(
|
||||
model: &crate::Model,
|
||||
content: Node<crate::Msg>,
|
||||
) -> Node<crate::Msg> {
|
||||
let notifications = crate::shared::notification::view(model);
|
||||
|
||||
view(notifications, empty![], content)
|
||||
}
|
||||
|
||||
fn view(
|
||||
notifications: Node<crate::Msg>,
|
||||
sidebar: Node<crate::Msg>,
|
||||
content: Node<crate::Msg>,
|
||||
) -> Node<crate::Msg> {
|
||||
div![
|
||||
C!["flex"],
|
||||
sidebar,
|
||||
@ -59,8 +74,14 @@ pub mod sidebar {
|
||||
|
||||
use crate::pages::Urls;
|
||||
|
||||
pub fn view<Msg>(model: &crate::Model, categories: &[model::api::Category]) -> Node<Msg> {
|
||||
let categories = categories.iter().map(|category| item(model, category));
|
||||
pub fn view<'category, Msg, Categories>(
|
||||
model: &crate::Model,
|
||||
categories: Categories,
|
||||
) -> Node<Msg>
|
||||
where
|
||||
Categories: Iterator<Item = &'category model::api::Category>,
|
||||
{
|
||||
let categories = categories.map(|category| item(model, category));
|
||||
|
||||
div![
|
||||
C!["flex flex-col justify-between mt-6"],
|
||||
|
@ -149,11 +149,12 @@ pub fn view(model: &crate::Model, page: &CheckoutPage) -> Node<crate::Msg> {
|
||||
|
||||
div![
|
||||
crate::shared::view::public_navbar::view(model, &page.products),
|
||||
super::layout::view(model, content, None)
|
||||
super::layout::view_without_categories(model, content)
|
||||
]
|
||||
}
|
||||
|
||||
mod left_side {
|
||||
use model::v2::{DetailedProduct, DetailedProductVariant};
|
||||
use rusty_money::Money;
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
@ -177,11 +178,23 @@ mod left_side {
|
||||
.values()
|
||||
.filter_map(|item: &Item| {
|
||||
page.products
|
||||
.product_variants
|
||||
.get(&item.product_variant_id)
|
||||
.map(|product| (item, product))
|
||||
.products
|
||||
.get(&item.product_id)
|
||||
.map(|product: &DetailedProduct| (item, product))
|
||||
})
|
||||
.map(|(item, variant)| product_view(model, variant, item));
|
||||
.filter_map(|(item, product): (&Item, &DetailedProduct)| {
|
||||
product
|
||||
.variants
|
||||
.iter()
|
||||
.find(|v| v.id == item.product_variant_id)
|
||||
.zip(Some((item, product)))
|
||||
})
|
||||
.map(
|
||||
|(variant, (item, product)): (
|
||||
&DetailedProductVariant,
|
||||
(&Item, &DetailedProduct),
|
||||
)| { product_view(model, product, variant, item) },
|
||||
);
|
||||
div![
|
||||
C!["w-full mx-auto text-gray-800 font-light mb-6 border-b border-gray-200 pb-6"],
|
||||
products
|
||||
@ -190,17 +203,18 @@ mod left_side {
|
||||
|
||||
fn product_view(
|
||||
model: &crate::Model,
|
||||
product: &model::api::v2::ProductVariant,
|
||||
_product: &DetailedProduct,
|
||||
variant: &DetailedProductVariant,
|
||||
item: &Item,
|
||||
) -> Node<Msg> {
|
||||
let img = product
|
||||
let img = variant
|
||||
.photos
|
||||
.first()
|
||||
.as_ref()
|
||||
.map(|photo| photo.url.as_str())
|
||||
.unwrap_or_default();
|
||||
let price = Money::from_minor(
|
||||
**(product.price * item.quantity) as i64,
|
||||
**(variant.price * item.quantity) as i64,
|
||||
model.config.currency,
|
||||
)
|
||||
.to_string();
|
||||
@ -217,7 +231,7 @@ mod left_side {
|
||||
C!["flex-grow pl-3"],
|
||||
h6![
|
||||
C!["font-semibold uppercase text-gray-600"],
|
||||
product.name.as_str()
|
||||
variant.name.as_str()
|
||||
],
|
||||
p![C!["text-gray-400"], "x ", **item.quantity]
|
||||
],
|
||||
@ -242,14 +256,22 @@ mod left_side {
|
||||
.values()
|
||||
.filter_map(|item: &Item| {
|
||||
page.products
|
||||
.product_variants
|
||||
.get(&item.product_variant_id)
|
||||
.products
|
||||
.get(&item.product_id)
|
||||
.map(|product| (item, product))
|
||||
})
|
||||
.filter_map(|(item, product): (&Item, &DetailedProduct)| {
|
||||
product
|
||||
.variants
|
||||
.iter()
|
||||
.find(|v| v.id == item.product_variant_id)
|
||||
.zip(Some((item, product)))
|
||||
})
|
||||
.map(
|
||||
|(item, product): (&Item, &model::api::v2::ProductVariant)| {
|
||||
**(product.price * item.quantity)
|
||||
},
|
||||
|(variant, (item, _product)): (
|
||||
&DetailedProductVariant,
|
||||
(&Item, &DetailedProduct),
|
||||
)| { **(variant.price * item.quantity) },
|
||||
)
|
||||
.sum::<i32>() as i64;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use model::v2::ProductVariantId;
|
||||
use model::Quantity;
|
||||
use model::v2::{DetailedProduct, DetailedProductVariant};
|
||||
use model::{ProductId, Quantity};
|
||||
use seed::app::Orders;
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
@ -15,7 +15,7 @@ use crate::shopping_cart::CartMsg;
|
||||
pub struct ListingPage {
|
||||
pub errors: Vec<String>,
|
||||
pub filters: HashSet<String>,
|
||||
pub visible_product_variants: Vec<ProductVariantId>,
|
||||
pub visible_products: Vec<ProductId>,
|
||||
|
||||
pub products: Products,
|
||||
}
|
||||
@ -32,7 +32,7 @@ pub fn init(url: Url, orders: &mut impl Orders<ListingMsg>) -> ListingPage {
|
||||
products: Products::default(),
|
||||
filters: url_to_filters(url),
|
||||
errors: vec![],
|
||||
visible_product_variants: vec![],
|
||||
visible_products: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,16 +55,16 @@ pub fn page_changed(url: Url, model: &mut ListingPage) {
|
||||
let ids = {
|
||||
model
|
||||
.products
|
||||
.filter_product_variant_ids(|product| filter_product(&*model, product))
|
||||
.filter_product_ids(|product| filter_product(&*model, product))
|
||||
};
|
||||
model.visible_product_variants = ids;
|
||||
model.visible_products = ids;
|
||||
}
|
||||
|
||||
fn filter_product(model: &ListingPage, product: &model::api::v2::ProductVariant) -> bool {
|
||||
product
|
||||
fn filter_product(model: &ListingPage, variant: &DetailedProduct) -> bool {
|
||||
variant
|
||||
.category
|
||||
.as_ref()
|
||||
.filter(|c| model.filters.contains(c.key.as_str()))
|
||||
.filter(|c| model.filters.contains(c.name.as_str()))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
@ -84,8 +84,8 @@ pub fn update(msg: ListingMsg, model: &mut ListingPage, orders: &mut impl Orders
|
||||
model.products.update(products.0);
|
||||
let ids = model
|
||||
.products
|
||||
.filter_product_variant_ids(|product| filter_product(model, product));
|
||||
model.visible_product_variants = ids;
|
||||
.filter_product_ids(|product| filter_product(model, product));
|
||||
model.visible_products = ids;
|
||||
}
|
||||
ListingMsg::ProductsFetched(NetRes::Error(_))
|
||||
| ListingMsg::ProductsFetched(NetRes::Http(_)) => {
|
||||
@ -95,17 +95,17 @@ pub fn update(msg: ListingMsg, model: &mut ListingPage, orders: &mut impl Orders
|
||||
}
|
||||
|
||||
pub fn view(model: &crate::Model, page: &ListingPage) -> Node<crate::Msg> {
|
||||
let products: Vec<Node<crate::Msg>> = if page.visible_product_variants.is_empty() {
|
||||
let products: Vec<Vec<Node<crate::Msg>>> = if page.visible_products.is_empty() {
|
||||
page.products
|
||||
.product_variant_ids
|
||||
.product_ids
|
||||
.iter()
|
||||
.filter_map(|id| page.products.product_variants.get(id))
|
||||
.filter_map(|id| page.products.products.get(id))
|
||||
.map(|p| product(model, p))
|
||||
.collect()
|
||||
} else {
|
||||
page.visible_product_variants
|
||||
page.visible_products
|
||||
.iter()
|
||||
.filter_map(|id| page.products.product_variants.get(id))
|
||||
.filter_map(|id| page.products.products.get(id))
|
||||
.map(|p| product(model, p))
|
||||
.collect()
|
||||
};
|
||||
@ -118,17 +118,28 @@ pub fn view(model: &crate::Model, page: &ListingPage) -> Node<crate::Msg> {
|
||||
|
||||
div![
|
||||
crate::shared::view::public_navbar::view(model, &page.products),
|
||||
super::layout::view(model, content, Some(&page.products.categories))
|
||||
super::layout::view_with_categories(model, content, page.products.categories.iter())
|
||||
]
|
||||
}
|
||||
|
||||
fn product(model: &crate::Model, product: &model::api::v2::ProductVariant) -> Node<crate::Msg> {
|
||||
use rusty_money::Money;
|
||||
fn product(model: &crate::Model, product: &DetailedProduct) -> Vec<Node<crate::Msg>> {
|
||||
product
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant| product_variant(model, product, variant))
|
||||
.collect()
|
||||
}
|
||||
|
||||
let price = Money::from_minor(**product.price as i64, model.config.currency).to_string();
|
||||
let _description = product.short_description.as_str();
|
||||
let name = product.name.as_str();
|
||||
let img = product
|
||||
fn product_variant(
|
||||
model: &crate::Model,
|
||||
product: &DetailedProduct,
|
||||
variant: &DetailedProductVariant,
|
||||
) -> Node<crate::Msg> {
|
||||
use rusty_money::Money;
|
||||
let price = Money::from_minor(**variant.price as i64, model.config.currency).to_string();
|
||||
let _description = variant.short_description.as_str();
|
||||
let name = variant.name.as_str();
|
||||
let img = variant
|
||||
.photos
|
||||
.first()
|
||||
.map(|photo| photo.url.as_str())
|
||||
@ -136,10 +147,11 @@ fn product(model: &crate::Model, product: &model::api::v2::ProductVariant) -> No
|
||||
|
||||
let url = Urls::new(&model.url)
|
||||
.product()
|
||||
.add_path_part((*product.id as i32).to_string());
|
||||
.add_path_part((*variant.id as i32).to_string());
|
||||
|
||||
let quantity_unit = product.quantity_unit;
|
||||
let product_variant_id = product.id;
|
||||
let quantity_unit = variant.quantity_unit;
|
||||
let product_variant_id = variant.id;
|
||||
let product_id = product.id;
|
||||
|
||||
div![
|
||||
C!["w-full px-4 lg:px-0"],
|
||||
@ -176,6 +188,7 @@ fn product(model: &crate::Model, product: &model::api::v2::ProductVariant) -> No
|
||||
ev.stop_propagation();
|
||||
crate::Msg::from(CartMsg::AddItem {
|
||||
product_variant_id,
|
||||
product_id,
|
||||
quantity_unit,
|
||||
quantity: Quantity::try_from(1).unwrap_or_default()
|
||||
})
|
||||
|
@ -1,3 +1,4 @@
|
||||
use model::v2::{DetailedProduct, DetailedProductVariant, ProductVariantId};
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
@ -12,6 +13,7 @@ pub enum ProductMsg {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ProductPage {
|
||||
pub product_id: Option<model::ProductId>,
|
||||
pub product_variant_id: Option<ProductVariantId>,
|
||||
pub products: crate::model::Products,
|
||||
pub selected_image: usize,
|
||||
}
|
||||
@ -28,6 +30,7 @@ pub fn init(mut url: Url, orders: &mut impl Orders<crate::Msg>) -> ProductPage {
|
||||
});
|
||||
ProductPage {
|
||||
product_id: Some(product_id),
|
||||
product_variant_id: None,
|
||||
products: Default::default(),
|
||||
selected_image: 0,
|
||||
}
|
||||
@ -61,16 +64,24 @@ pub fn view(model: &crate::Model, page: &ProductPage) -> Node<crate::Msg> {
|
||||
None => return empty!(),
|
||||
Some(product) => product,
|
||||
};
|
||||
let large_photo = product
|
||||
let variant: &DetailedProductVariant = match page
|
||||
.product_variant_id
|
||||
.and_then(|id| product.variant_for(id))
|
||||
{
|
||||
Some(v) => v,
|
||||
_ => return empty![],
|
||||
};
|
||||
let large_photo = variant
|
||||
.photos
|
||||
.get(page.selected_image)
|
||||
.map(image)
|
||||
.unwrap_or_else(|| empty![]);
|
||||
|
||||
let small_photos = {
|
||||
if product.photos.len() <= 1 {
|
||||
if variant.photos.len() <= 1 {
|
||||
empty![]
|
||||
} else {
|
||||
let photos = product
|
||||
let photos = variant
|
||||
.photos
|
||||
.iter()
|
||||
.enumerate()
|
||||
@ -79,13 +90,13 @@ pub fn view(model: &crate::Model, page: &ProductPage) -> Node<crate::Msg> {
|
||||
}
|
||||
};
|
||||
|
||||
let description = product
|
||||
let description = variant
|
||||
.long_description
|
||||
.as_str()
|
||||
.split('\n')
|
||||
.map(|s| div![s]);
|
||||
|
||||
let delivery = delivery_available(product, model);
|
||||
let delivery = delivery_available(product, variant, model);
|
||||
|
||||
let content = div![
|
||||
C!["max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-6"],
|
||||
@ -106,11 +117,11 @@ pub fn view(model: &crate::Model, page: &ProductPage) -> Node<crate::Msg> {
|
||||
C!["mb-2 leading-tight tracking-tight font-bold text-gray-800 text-2xl md:text-3xl"],
|
||||
product.name.as_str()
|
||||
],
|
||||
div![model.i18n.t("Price per"), " ", model.i18n.t(product.quantity_unit.name()).to_lowercase()],
|
||||
div![model.i18n.t("Price per"), " ", model.i18n.t(variant.quantity_unit.name()).to_lowercase()],
|
||||
div![
|
||||
delivery
|
||||
],
|
||||
action_section(product, model)
|
||||
action_section(product, variant, model)
|
||||
],
|
||||
],
|
||||
div![
|
||||
@ -122,39 +133,47 @@ pub fn view(model: &crate::Model, page: &ProductPage) -> Node<crate::Msg> {
|
||||
|
||||
div![
|
||||
crate::shared::view::public_navbar::view(model, &page.products),
|
||||
super::layout::view(model, content, Some(&page.products.categories))
|
||||
super::layout::view_with_categories(model, content, page.products.categories.iter())
|
||||
]
|
||||
}
|
||||
|
||||
fn action_section(product: &model::api::Product, model: &crate::Model) -> Node<ProductMsg> {
|
||||
if product.available {
|
||||
fn action_section(
|
||||
_product: &DetailedProduct,
|
||||
variant: &DetailedProductVariant,
|
||||
model: &crate::Model,
|
||||
) -> Node<ProductMsg> {
|
||||
if variant.available {
|
||||
div![
|
||||
C!["flex py-4 space-x-4"],
|
||||
div![
|
||||
C!["relative"],
|
||||
label![
|
||||
C!["text-center left-0 pt-2 right-0 absolute block text-xs uppercase text-gray-400 tracking-wide font-semibold"],
|
||||
attrs!["for" => "quantity"],
|
||||
model.i18n.t("Qty")
|
||||
],
|
||||
input![C![""], attrs!["id" => "quantity", "type" => "number"]]
|
||||
],
|
||||
button![
|
||||
C!["px-6 py-3 text-sm text-white bg-indigo-500 rounded-lg outline-none hover:bg-indigo-600 ring-indigo-300"],
|
||||
model.i18n.t("Add to Cart"),
|
||||
ev("click", move |ev| {
|
||||
ev.prevent_default();
|
||||
ev.stop_propagation();
|
||||
None as Option<ProductMsg>
|
||||
})
|
||||
]
|
||||
]
|
||||
C!["flex py-4 space-x-4"],
|
||||
div![
|
||||
C!["relative"],
|
||||
label![
|
||||
C!["text-center left-0 pt-2 right-0 absolute block text-xs uppercase text-gray-400 tracking-wide font-semibold"],
|
||||
attrs!["for" => "quantity"],
|
||||
model.i18n.t("Qty")
|
||||
],
|
||||
input![C![""], attrs!["id" => "quantity", "type" => "number"]]
|
||||
],
|
||||
button![
|
||||
C!["px-6 py-3 text-sm text-white bg-indigo-500 rounded-lg outline-none hover:bg-indigo-600 ring-indigo-300"],
|
||||
model.i18n.t("Add to Cart"),
|
||||
ev("click", move |ev| {
|
||||
ev.prevent_default();
|
||||
ev.stop_propagation();
|
||||
None as Option<ProductMsg>
|
||||
})
|
||||
]
|
||||
]
|
||||
} else {
|
||||
div![C!["text-sm text-gray-400 "], model.i18n.t("Out of stock")]
|
||||
}
|
||||
}
|
||||
|
||||
fn delivery_available(product: &model::api::Product, model: &crate::Model) -> Node<ProductMsg> {
|
||||
fn delivery_available(
|
||||
product: &DetailedProduct,
|
||||
_variant: &DetailedProductVariant,
|
||||
model: &crate::Model,
|
||||
) -> Node<ProductMsg> {
|
||||
match product.deliver_days_flag.len() {
|
||||
0 => return empty![],
|
||||
7 => return div![model.i18n.t("Delivery all week")],
|
||||
|
@ -1,3 +1,4 @@
|
||||
use model::v2::{DetailedProduct, DetailedProductVariant};
|
||||
use model::Quantity;
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
@ -63,7 +64,7 @@ pub fn view(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::Msg> {
|
||||
|
||||
div![
|
||||
crate::shared::view::public_navbar::view(model, &page.products),
|
||||
super::layout::view(model, content, None)
|
||||
super::layout::view_without_categories(model, content)
|
||||
]
|
||||
}
|
||||
|
||||
@ -153,6 +154,7 @@ mod summary_left {
|
||||
}
|
||||
|
||||
mod summary_right {
|
||||
use model::v2::{DetailedProduct, DetailedProductVariant};
|
||||
use rusty_money::Money;
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
@ -165,17 +167,13 @@ mod summary_right {
|
||||
.cart
|
||||
.items
|
||||
.values()
|
||||
.filter_map(|item: &crate::shopping_cart::Item| {
|
||||
page.products
|
||||
.product_variants
|
||||
.get(&item.product_variant_id)
|
||||
.map(|product| (item, product))
|
||||
})
|
||||
.filter_map(|item: &crate::shopping_cart::Item| page.products.for_item(item))
|
||||
.map(
|
||||
|(item, product): (
|
||||
|(item, _product, variant): (
|
||||
&crate::shopping_cart::Item,
|
||||
&model::api::v2::ProductVariant,
|
||||
)| { **(product.price * item.quantity) },
|
||||
&DetailedProduct,
|
||||
&DetailedProductVariant,
|
||||
)| { **(variant.price * item.quantity) },
|
||||
)
|
||||
.sum::<i32>() as i64;
|
||||
div![
|
||||
@ -390,16 +388,13 @@ fn products_body(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::M
|
||||
.cart
|
||||
.items
|
||||
.values()
|
||||
.filter_map(|item: &crate::shopping_cart::Item| {
|
||||
page.products
|
||||
.product_variants
|
||||
.get(&item.product_variant_id)
|
||||
.map(|product| (item, product))
|
||||
})
|
||||
.filter_map(|item: &crate::shopping_cart::Item| page.products.for_item(item))
|
||||
.map(
|
||||
|(item, product): (&crate::shopping_cart::Item, &model::api::v2::ProductVariant)| {
|
||||
item_view(model, item, product)
|
||||
},
|
||||
|(item, product, variant): (
|
||||
&crate::shopping_cart::Item,
|
||||
&DetailedProduct,
|
||||
&DetailedProductVariant,
|
||||
)| { item_view(model, item, product, variant) },
|
||||
);
|
||||
tbody![items]
|
||||
}
|
||||
@ -407,18 +402,20 @@ fn products_body(model: &crate::Model, page: &ShoppingCartPage) -> Node<crate::M
|
||||
fn item_view(
|
||||
model: &crate::Model,
|
||||
item: &crate::shopping_cart::Item,
|
||||
product: &model::api::v2::ProductVariant,
|
||||
product: &DetailedProduct,
|
||||
variant: &DetailedProductVariant,
|
||||
) -> Node<crate::Msg> {
|
||||
use rusty_money::Money;
|
||||
|
||||
let img = product
|
||||
let img = variant
|
||||
.photos
|
||||
.first()
|
||||
.map(|photo| photo.url.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
let product_variant_id = product.id;
|
||||
let quantity_unit = product.quantity_unit;
|
||||
let product_variant_id = variant.id;
|
||||
let product_id = product.id;
|
||||
let quantity_unit = variant.quantity_unit;
|
||||
let product_url = Urls::new(&model.url)
|
||||
.product()
|
||||
.add_path_part(product.id.to_string());
|
||||
@ -476,6 +473,7 @@ fn item_view(
|
||||
|
||||
Some(crate::Msg::from(CartMsg::ModifyItem {
|
||||
product_variant_id,
|
||||
product_id,
|
||||
quantity_unit,
|
||||
quantity,
|
||||
}))
|
||||
@ -488,7 +486,7 @@ fn item_view(
|
||||
C!["hidden text-right md:table-cell"],
|
||||
span![
|
||||
C!["text-sm lg:text-base font-medium"],
|
||||
Money::from_minor(**product.price as i64, model.config.currency).to_string()
|
||||
Money::from_minor(**variant.price as i64, model.config.currency).to_string()
|
||||
]
|
||||
],
|
||||
td![
|
||||
@ -496,7 +494,7 @@ fn item_view(
|
||||
span![
|
||||
C!["text-sm lg:text-base font-medium"],
|
||||
Money::from_minor(
|
||||
**(product.price * item.quantity) as i64,
|
||||
**(variant.price * item.quantity) as i64,
|
||||
model.config.currency
|
||||
)
|
||||
.to_string()
|
||||
|
@ -62,7 +62,7 @@ pub fn view(model: &crate::Model, page: &SignInPage) -> Node<crate::Msg> {
|
||||
]
|
||||
.map_msg(Into::into);
|
||||
|
||||
div![super::layout::view(model, content, None)]
|
||||
div![super::layout::view_without_categories(model, content)]
|
||||
}
|
||||
|
||||
fn sign_in_form(model: &crate::Model, _page: &SignInPage) -> Node<LogInMsg> {
|
||||
|
@ -114,7 +114,7 @@ pub fn view(model: &crate::Model, page: &SignUpPage) -> Node<crate::Msg> {
|
||||
]
|
||||
]
|
||||
.map_msg(Into::into);
|
||||
div![super::layout::view(model, content, None)]
|
||||
div![super::layout::view_without_categories(model, content)]
|
||||
}
|
||||
|
||||
fn sign_up_form(model: &crate::Model, _page: &SignUpPage) -> Node<RegisterMsg> {
|
||||
|
@ -108,36 +108,26 @@ pub mod public_navbar {
|
||||
}
|
||||
|
||||
pub mod cart_dropdown {
|
||||
use model::ProductId;
|
||||
use model::v2::{DetailedProduct, DetailedProductVariant};
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::shopping_cart::Item;
|
||||
use crate::{Model, Msg};
|
||||
|
||||
macro_rules! filter_products {
|
||||
($model: expr, $products: expr) => {
|
||||
$model
|
||||
.cart
|
||||
.items
|
||||
.values()
|
||||
.filter_map(|item: &Item| filter_product(item, $products, item.product_id))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! filter_product_variants {
|
||||
($model: expr, $products: expr) => {
|
||||
$model
|
||||
.cart
|
||||
.items
|
||||
.values()
|
||||
.filter_map(|item: &Item| filter_product(item, $products, item.product_variant_id))
|
||||
.filter_map(|item: &Item| $products.for_item(item))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn view(model: &Model, products: &crate::model::Products) -> Node<Msg> {
|
||||
let items =
|
||||
filter_product_variants!(model, products).map(|(it, product)| item(model, it, product));
|
||||
let items = filter_product_variants!(model, products)
|
||||
.map(|(it, product, variant)| item(model, it, product, variant));
|
||||
div![
|
||||
C![
|
||||
"absolute w-full rounded-b border-t-0 z-10",
|
||||
@ -151,15 +141,20 @@ pub mod cart_dropdown {
|
||||
]
|
||||
}
|
||||
|
||||
fn item(model: &Model, item: &Item, product: &model::api::Product) -> Node<Msg> {
|
||||
let img = product
|
||||
fn item(
|
||||
model: &Model,
|
||||
item: &Item,
|
||||
product: &DetailedProduct,
|
||||
variant: &DetailedProductVariant,
|
||||
) -> Node<Msg> {
|
||||
let img = variant
|
||||
.photos
|
||||
.first()
|
||||
.map(|photo| photo.url.as_str())
|
||||
.map(|photo| photo.unique_name.as_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
let price = rusty_money::Money::from_minor(
|
||||
**(product.price * item.quantity) as i64,
|
||||
**(variant.price * item.quantity) as i64,
|
||||
model.config.currency,
|
||||
)
|
||||
.to_string();
|
||||
@ -169,7 +164,7 @@ pub mod cart_dropdown {
|
||||
div![
|
||||
C!["flex-auto text-sm w-32"],
|
||||
div![C!["font-bold"], product.name.as_str()],
|
||||
div![C!["truncate"], product.short_description.as_str()],
|
||||
div![C!["truncate"], variant.short_description.as_str()],
|
||||
div![
|
||||
C!["text-gray-400"],
|
||||
model.i18n.t("Qty:"),
|
||||
@ -213,7 +208,11 @@ pub mod cart_dropdown {
|
||||
|
||||
fn checkout(model: &Model, products: &crate::model::Products) -> Node<Msg> {
|
||||
let sum: i32 = filter_product_variants!(model, products)
|
||||
.map(|(item, product): (&Item, &model::api::Product)| **item.quantity * **product.price)
|
||||
.map(
|
||||
|(item, _product, variant): (&Item, &DetailedProduct, &DetailedProductVariant)| {
|
||||
**item.quantity * **variant.price
|
||||
},
|
||||
)
|
||||
.sum();
|
||||
let sum = rusty_money::Money::from_minor(sum as i64, model.config.currency);
|
||||
|
||||
@ -230,15 +229,4 @@ pub mod cart_dropdown {
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
fn filter_product<'item, 'product>(
|
||||
item: &'item Item,
|
||||
products: &'product crate::model::Products,
|
||||
product_id: ProductId,
|
||||
) -> Option<(&'item Item, &'product model::api::Product)> {
|
||||
products
|
||||
.products
|
||||
.get(&product_id)
|
||||
.map(|product| (item, product))
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,13 @@ pub enum CartMsg {
|
||||
quantity: Quantity,
|
||||
quantity_unit: QuantityUnit,
|
||||
product_variant_id: ProductVariantId,
|
||||
product_id: ProductId,
|
||||
},
|
||||
ModifyItem {
|
||||
quantity: Quantity,
|
||||
quantity_unit: QuantityUnit,
|
||||
product_variant_id: ProductVariantId,
|
||||
product_id: ProductId,
|
||||
},
|
||||
Remove(ProductVariantId),
|
||||
Hover,
|
||||
@ -34,12 +36,14 @@ impl From<CartMsg> for Msg {
|
||||
}
|
||||
}
|
||||
|
||||
pub type Items = indexmap::IndexMap<ProductId, Item>;
|
||||
pub type Items = indexmap::IndexMap<ProductVariantId, Item>;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct Item {
|
||||
#[serde(rename = "i")]
|
||||
#[serde(rename = "v")]
|
||||
pub product_variant_id: ProductVariantId,
|
||||
#[serde(rename = "p")]
|
||||
pub product_id: ProductId,
|
||||
#[serde(rename = "q")]
|
||||
pub quantity: Quantity,
|
||||
#[serde(rename = "u")]
|
||||
@ -70,6 +74,7 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
quantity,
|
||||
quantity_unit,
|
||||
product_variant_id,
|
||||
product_id,
|
||||
} => {
|
||||
{
|
||||
let items: &mut Items = &mut model.cart.items;
|
||||
@ -77,6 +82,7 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
quantity: Quantity::from_u32(0),
|
||||
quantity_unit,
|
||||
product_variant_id,
|
||||
product_id,
|
||||
});
|
||||
entry.quantity = entry.quantity + quantity;
|
||||
entry.quantity_unit = quantity_unit;
|
||||
@ -85,6 +91,7 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
sync_cart(model, orders);
|
||||
}
|
||||
CartMsg::ModifyItem {
|
||||
product_id,
|
||||
product_variant_id,
|
||||
quantity_unit,
|
||||
quantity,
|
||||
@ -97,6 +104,7 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
quantity,
|
||||
quantity_unit,
|
||||
product_variant_id,
|
||||
product_id,
|
||||
});
|
||||
entry.quantity = quantity;
|
||||
entry.quantity_unit = quantity_unit;
|
||||
@ -127,6 +135,7 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
model::api::ShoppingCartItem {
|
||||
id: _,
|
||||
product_variant_id,
|
||||
product_id,
|
||||
shopping_cart_id: _,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
@ -135,6 +144,7 @@ pub fn update(msg: CartMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
product_variant_id,
|
||||
Item {
|
||||
product_variant_id,
|
||||
product_id,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user