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