2024-07-15 17:47:50 +05:30
|
|
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
|
2024-07-15 23:52:59 +05:30
|
|
|
use std::collections::HashSet;
|
|
|
|
|
2024-07-15 17:47:50 +05:30
|
|
|
use derive_builder::Builder;
|
|
|
|
use derive_getters::Getters;
|
|
|
|
use derive_more::{Display, Error};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
2024-07-15 19:50:39 +05:30
|
|
|
use super::product_aggregate::{Price, Quantity};
|
2024-07-15 17:47:50 +05:30
|
|
|
|
|
|
|
#[derive(Debug, Error, Display, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
pub enum AddProductCommandError {
|
|
|
|
NameIsEmpty,
|
2024-07-15 23:52:59 +05:30
|
|
|
CustomizationNameIsEmpty,
|
|
|
|
DuplicateCustomizationName,
|
|
|
|
}
|
|
|
|
#[derive(
|
|
|
|
Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder,
|
|
|
|
)]
|
|
|
|
pub struct UnvalidatedAddCustomization {
|
|
|
|
name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters)]
|
|
|
|
pub struct AddCustomization {
|
|
|
|
name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl UnvalidatedAddCustomization {
|
|
|
|
pub fn validate(self) -> Result<AddCustomization, AddProductCommandError> {
|
|
|
|
let name = self.name.trim().to_owned();
|
|
|
|
if name.is_empty() {
|
|
|
|
return Err(AddProductCommandError::CustomizationNameIsEmpty);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(AddCustomization { name })
|
|
|
|
}
|
2024-07-15 17:47:50 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(
|
|
|
|
Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder,
|
|
|
|
)]
|
|
|
|
pub struct UnvalidatedAddProductCommand {
|
|
|
|
name: String,
|
|
|
|
description: Option<String>,
|
|
|
|
image: Option<String>,
|
|
|
|
category_id: Uuid,
|
|
|
|
sku_able: bool,
|
2024-07-15 19:50:39 +05:30
|
|
|
quantity: Quantity,
|
2024-07-15 17:47:50 +05:30
|
|
|
price: Price,
|
|
|
|
adding_by: Uuid,
|
2024-07-15 23:52:59 +05:30
|
|
|
customizations: Vec<AddCustomization>,
|
2024-07-15 17:47:50 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters)]
|
|
|
|
pub struct AddProductCommand {
|
|
|
|
name: String,
|
|
|
|
description: Option<String>,
|
|
|
|
image: Option<String>,
|
|
|
|
category_id: Uuid,
|
|
|
|
sku_able: bool,
|
|
|
|
price: Price,
|
2024-07-15 19:50:39 +05:30
|
|
|
quantity: Quantity,
|
2024-07-15 17:47:50 +05:30
|
|
|
adding_by: Uuid,
|
2024-07-15 23:52:59 +05:30
|
|
|
customizations: Vec<AddCustomization>,
|
2024-07-15 17:47:50 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
impl UnvalidatedAddProductCommand {
|
|
|
|
pub fn validate(self) -> Result<AddProductCommand, AddProductCommandError> {
|
|
|
|
let description: Option<String> = if let Some(description) = self.description {
|
|
|
|
let description = description.trim();
|
|
|
|
if description.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(description.to_owned())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let image: Option<String> = if let Some(image) = self.image {
|
|
|
|
let image = image.trim();
|
|
|
|
if image.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(image.to_owned())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let name = self.name.trim().to_owned();
|
|
|
|
if name.is_empty() {
|
|
|
|
return Err(AddProductCommandError::NameIsEmpty);
|
|
|
|
}
|
|
|
|
|
2024-07-15 23:52:59 +05:30
|
|
|
let mut unique_customization_names = HashSet::new();
|
|
|
|
if !self
|
|
|
|
.customizations
|
|
|
|
.iter()
|
|
|
|
.all(|c| unique_customization_names.insert(c.name.clone()))
|
|
|
|
{
|
|
|
|
return Err(AddProductCommandError::DuplicateCustomizationName);
|
|
|
|
}
|
|
|
|
|
2024-07-15 17:47:50 +05:30
|
|
|
Ok(AddProductCommand {
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
image,
|
|
|
|
category_id: self.category_id,
|
|
|
|
sku_able: self.sku_able,
|
|
|
|
price: self.price,
|
2024-07-15 19:50:39 +05:30
|
|
|
quantity: self.quantity,
|
2024-07-15 17:47:50 +05:30
|
|
|
adding_by: self.adding_by,
|
2024-07-15 23:52:59 +05:30
|
|
|
customizations: self.customizations,
|
2024-07-15 17:47:50 +05:30
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
pub mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use crate::{
|
2024-07-15 19:50:39 +05:30
|
|
|
inventory::domain::product_aggregate::{
|
|
|
|
Currency, PriceBuilder, QuantityBuilder, QuantityUnit,
|
|
|
|
},
|
2024-07-15 17:47:50 +05:30
|
|
|
utils::uuid::tests::UUID,
|
|
|
|
};
|
|
|
|
|
2024-07-15 23:52:59 +05:30
|
|
|
pub fn get_customizations() -> Vec<AddCustomization> {
|
|
|
|
vec![
|
|
|
|
UnvalidatedAddCustomizationBuilder::default()
|
|
|
|
.name("foo".into())
|
|
|
|
.build()
|
|
|
|
.unwrap()
|
|
|
|
.validate()
|
|
|
|
.unwrap(),
|
|
|
|
UnvalidatedAddCustomizationBuilder::default()
|
|
|
|
.name("bar".into())
|
|
|
|
.build()
|
|
|
|
.unwrap()
|
|
|
|
.validate()
|
|
|
|
.unwrap(),
|
|
|
|
UnvalidatedAddCustomizationBuilder::default()
|
|
|
|
.name("baz".into())
|
|
|
|
.build()
|
|
|
|
.unwrap()
|
|
|
|
.validate()
|
|
|
|
.unwrap(),
|
|
|
|
]
|
|
|
|
}
|
2024-07-15 17:47:50 +05:30
|
|
|
pub fn get_command() -> AddProductCommand {
|
|
|
|
let name = "foo";
|
|
|
|
let adding_by = UUID;
|
|
|
|
let category_id = Uuid::new_v4();
|
|
|
|
let sku_able = false;
|
|
|
|
let image = Some("image".to_string());
|
|
|
|
let description = Some("description".to_string());
|
|
|
|
|
2024-07-15 23:52:59 +05:30
|
|
|
let customizations = get_customizations();
|
|
|
|
|
2024-07-15 17:47:50 +05:30
|
|
|
let price = PriceBuilder::default()
|
|
|
|
.minor(0)
|
|
|
|
.major(100)
|
|
|
|
.currency(Currency::INR)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
2024-07-15 19:50:39 +05:30
|
|
|
let quantity = QuantityBuilder::default()
|
|
|
|
.number(1)
|
|
|
|
.unit(QuantityUnit::DiscreteNumber)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
2024-07-15 17:47:50 +05:30
|
|
|
let cmd = UnvalidatedAddProductCommandBuilder::default()
|
|
|
|
.name(name.into())
|
|
|
|
.description(description.clone())
|
|
|
|
.image(image.clone())
|
2024-07-16 11:50:49 +05:30
|
|
|
.category_id(category_id)
|
|
|
|
.adding_by(adding_by)
|
2024-07-15 19:50:39 +05:30
|
|
|
.quantity(quantity)
|
2024-07-15 17:47:50 +05:30
|
|
|
.sku_able(sku_able)
|
|
|
|
.price(price.clone())
|
2024-07-15 23:52:59 +05:30
|
|
|
.customizations(customizations)
|
2024-07-15 17:47:50 +05:30
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
cmd.validate().unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_description_and_image_none() {
|
|
|
|
let name = "foo";
|
|
|
|
let adding_by = UUID;
|
|
|
|
let category_id = Uuid::new_v4();
|
|
|
|
let sku_able = false;
|
|
|
|
|
2024-07-15 23:52:59 +05:30
|
|
|
let customizations = get_customizations();
|
2024-07-15 17:47:50 +05:30
|
|
|
let price = PriceBuilder::default()
|
|
|
|
.minor(0)
|
|
|
|
.major(100)
|
|
|
|
.currency(Currency::INR)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
2024-07-15 19:50:39 +05:30
|
|
|
let quantity = QuantityBuilder::default()
|
|
|
|
.number(1)
|
|
|
|
.unit(QuantityUnit::DiscreteNumber)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
2024-07-15 17:47:50 +05:30
|
|
|
|
|
|
|
// description = None
|
|
|
|
let cmd = UnvalidatedAddProductCommandBuilder::default()
|
|
|
|
.name(name.into())
|
|
|
|
.description(None)
|
|
|
|
.image(None)
|
2024-07-16 11:50:49 +05:30
|
|
|
.category_id(category_id)
|
|
|
|
.adding_by(adding_by)
|
2024-07-15 19:50:39 +05:30
|
|
|
.quantity(quantity.clone())
|
2024-07-15 17:47:50 +05:30
|
|
|
.sku_able(sku_able)
|
|
|
|
.price(price.clone())
|
2024-07-15 23:52:59 +05:30
|
|
|
.customizations(customizations.clone())
|
2024-07-15 17:47:50 +05:30
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let cmd = cmd.validate().unwrap();
|
|
|
|
|
|
|
|
assert_eq!(cmd.name(), name);
|
|
|
|
assert_eq!(cmd.description(), &None);
|
|
|
|
assert_eq!(cmd.adding_by(), &adding_by);
|
|
|
|
assert_eq!(cmd.category_id(), &category_id);
|
|
|
|
assert_eq!(cmd.image(), &None);
|
|
|
|
assert_eq!(cmd.sku_able(), &sku_able);
|
|
|
|
assert_eq!(cmd.price(), &price);
|
2024-07-15 19:50:39 +05:30
|
|
|
assert_eq!(cmd.quantity(), &quantity);
|
2024-07-15 23:52:59 +05:30
|
|
|
assert_eq!(cmd.customizations(), &customizations);
|
2024-07-15 17:47:50 +05:30
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_description_some() {
|
|
|
|
let name = "foo";
|
|
|
|
let adding_by = UUID;
|
|
|
|
let category_id = Uuid::new_v4();
|
|
|
|
let sku_able = false;
|
|
|
|
let image = Some("image".to_string());
|
|
|
|
let description = Some("description".to_string());
|
|
|
|
|
2024-07-15 23:52:59 +05:30
|
|
|
let customizations = get_customizations();
|
2024-07-15 17:47:50 +05:30
|
|
|
let price = PriceBuilder::default()
|
|
|
|
.minor(0)
|
|
|
|
.major(100)
|
|
|
|
.currency(Currency::INR)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
2024-07-15 19:50:39 +05:30
|
|
|
let quantity = QuantityBuilder::default()
|
|
|
|
.number(1)
|
|
|
|
.unit(QuantityUnit::DiscreteNumber)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
2024-07-15 17:47:50 +05:30
|
|
|
|
|
|
|
let cmd = UnvalidatedAddProductCommandBuilder::default()
|
|
|
|
.name(name.into())
|
|
|
|
.description(description.clone())
|
|
|
|
.image(image.clone())
|
2024-07-16 11:50:49 +05:30
|
|
|
.category_id(category_id)
|
2024-07-15 19:50:39 +05:30
|
|
|
.quantity(quantity.clone())
|
2024-07-16 11:50:49 +05:30
|
|
|
.adding_by(adding_by)
|
2024-07-15 17:47:50 +05:30
|
|
|
.sku_able(sku_able)
|
|
|
|
.price(price.clone())
|
2024-07-15 23:52:59 +05:30
|
|
|
.customizations(customizations.clone())
|
2024-07-15 17:47:50 +05:30
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let cmd = cmd.validate().unwrap();
|
|
|
|
|
|
|
|
assert_eq!(cmd.name(), name);
|
|
|
|
assert_eq!(cmd.description(), &description);
|
|
|
|
assert_eq!(cmd.adding_by(), &adding_by);
|
|
|
|
assert_eq!(cmd.category_id(), &category_id);
|
|
|
|
assert_eq!(cmd.image(), &image);
|
|
|
|
assert_eq!(cmd.sku_able(), &sku_able);
|
|
|
|
assert_eq!(cmd.price(), &price);
|
2024-07-15 19:50:39 +05:30
|
|
|
assert_eq!(cmd.quantity(), &quantity);
|
2024-07-15 23:52:59 +05:30
|
|
|
assert_eq!(cmd.customizations(), &customizations);
|
2024-07-15 17:47:50 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_name_is_empty() {
|
|
|
|
let adding_by = UUID;
|
|
|
|
let category_id = Uuid::new_v4();
|
|
|
|
let sku_able = false;
|
|
|
|
let image = Some("image".to_string());
|
|
|
|
let description = Some("description".to_string());
|
|
|
|
|
2024-07-15 23:52:59 +05:30
|
|
|
let customizations = get_customizations();
|
2024-07-15 17:47:50 +05:30
|
|
|
let price = PriceBuilder::default()
|
|
|
|
.minor(0)
|
|
|
|
.major(100)
|
|
|
|
.currency(Currency::INR)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
2024-07-15 19:50:39 +05:30
|
|
|
let quantity = QuantityBuilder::default()
|
|
|
|
.number(1)
|
|
|
|
.unit(QuantityUnit::DiscreteNumber)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
2024-07-15 17:47:50 +05:30
|
|
|
|
|
|
|
let cmd = UnvalidatedAddProductCommandBuilder::default()
|
|
|
|
.name("".into())
|
|
|
|
.description(description.clone())
|
|
|
|
.image(image.clone())
|
2024-07-16 11:50:49 +05:30
|
|
|
.category_id(category_id)
|
|
|
|
.adding_by(adding_by)
|
2024-07-15 19:50:39 +05:30
|
|
|
.quantity(quantity)
|
2024-07-15 17:47:50 +05:30
|
|
|
.sku_able(sku_able)
|
2024-07-15 23:52:59 +05:30
|
|
|
.customizations(customizations)
|
2024-07-15 17:47:50 +05:30
|
|
|
.price(price.clone())
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// AddProductCommandError::NameIsEmpty
|
|
|
|
assert_eq!(cmd.validate(), Err(AddProductCommandError::NameIsEmpty))
|
|
|
|
}
|
2024-07-15 23:52:59 +05:30
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_customization_name_is_empty() {
|
|
|
|
assert_eq!(
|
|
|
|
UnvalidatedAddCustomizationBuilder::default()
|
|
|
|
.name("".into())
|
|
|
|
.build()
|
|
|
|
.unwrap()
|
|
|
|
.validate(),
|
|
|
|
Err(AddProductCommandError::CustomizationNameIsEmpty)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_duplicate_customization() {
|
|
|
|
let name = "foo";
|
|
|
|
let adding_by = UUID;
|
|
|
|
let category_id = Uuid::new_v4();
|
|
|
|
let sku_able = false;
|
|
|
|
let image = Some("image".to_string());
|
|
|
|
let description = Some("description".to_string());
|
|
|
|
|
|
|
|
let mut customizations = get_customizations();
|
|
|
|
customizations.push(customizations.first().unwrap().to_owned());
|
|
|
|
|
|
|
|
let price = PriceBuilder::default()
|
|
|
|
.minor(0)
|
|
|
|
.major(100)
|
|
|
|
.currency(Currency::INR)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
let quantity = QuantityBuilder::default()
|
|
|
|
.number(1)
|
|
|
|
.unit(QuantityUnit::DiscreteNumber)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let cmd = UnvalidatedAddProductCommandBuilder::default()
|
|
|
|
.name(name.into())
|
|
|
|
.description(description.clone())
|
|
|
|
.image(image.clone())
|
2024-07-16 11:50:49 +05:30
|
|
|
.category_id(category_id)
|
|
|
|
.adding_by(adding_by)
|
2024-07-15 23:52:59 +05:30
|
|
|
.quantity(quantity)
|
|
|
|
.sku_able(sku_able)
|
|
|
|
.customizations(customizations)
|
|
|
|
.price(price.clone())
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// AddProductCommandError::DuplicateCustomizationName
|
|
|
|
assert_eq!(
|
|
|
|
cmd.validate(),
|
|
|
|
Err(AddProductCommandError::DuplicateCustomizationName)
|
|
|
|
)
|
|
|
|
}
|
2024-07-15 17:47:50 +05:30
|
|
|
}
|