vanikam/src/inventory/domain/add_product_command.rs

384 lines
11 KiB
Rust
Raw Normal View History

// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::collections::HashSet;
use derive_builder::Builder;
use derive_getters::Getters;
use derive_more::{Display, Error};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::product_aggregate::{Price, Quantity};
#[derive(Debug, Error, Display, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum AddProductCommandError {
NameIsEmpty,
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 })
}
}
#[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,
quantity: Quantity,
price: Price,
adding_by: Uuid,
customizations: Vec<AddCustomization>,
}
#[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,
quantity: Quantity,
adding_by: Uuid,
customizations: Vec<AddCustomization>,
}
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);
}
let mut unique_customization_names = HashSet::new();
if !self
.customizations
.iter()
.all(|c| unique_customization_names.insert(c.name.clone()))
{
return Err(AddProductCommandError::DuplicateCustomizationName);
}
Ok(AddProductCommand {
name,
description,
image,
category_id: self.category_id,
sku_able: self.sku_able,
price: self.price,
quantity: self.quantity,
adding_by: self.adding_by,
customizations: self.customizations,
})
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::{
inventory::domain::product_aggregate::{
Currency, PriceBuilder, QuantityBuilder, QuantityUnit,
},
utils::uuid::tests::UUID,
};
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(),
]
}
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());
let customizations = get_customizations();
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)
.quantity(quantity)
.sku_able(sku_able)
.price(price.clone())
.customizations(customizations)
.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;
let customizations = get_customizations();
let price = PriceBuilder::default()
.minor(0)
.major(100)
.currency(Currency::INR)
.build()
.unwrap();
let quantity = QuantityBuilder::default()
.number(1)
.unit(QuantityUnit::DiscreteNumber)
.build()
.unwrap();
// 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)
.quantity(quantity.clone())
.sku_able(sku_able)
.price(price.clone())
.customizations(customizations.clone())
.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);
assert_eq!(cmd.quantity(), &quantity);
assert_eq!(cmd.customizations(), &customizations);
}
#[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());
let customizations = get_customizations();
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)
.quantity(quantity.clone())
2024-07-16 11:50:49 +05:30
.adding_by(adding_by)
.sku_able(sku_able)
.price(price.clone())
.customizations(customizations.clone())
.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);
assert_eq!(cmd.quantity(), &quantity);
assert_eq!(cmd.customizations(), &customizations);
}
#[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());
let customizations = get_customizations();
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("".into())
.description(description.clone())
.image(image.clone())
2024-07-16 11:50:49 +05:30
.category_id(category_id)
.adding_by(adding_by)
.quantity(quantity)
.sku_able(sku_able)
.customizations(customizations)
.price(price.clone())
.build()
.unwrap();
// AddProductCommandError::NameIsEmpty
assert_eq!(cmd.validate(), Err(AddProductCommandError::NameIsEmpty))
}
#[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)
.quantity(quantity)
.sku_able(sku_able)
.customizations(customizations)
.price(price.clone())
.build()
.unwrap();
// AddProductCommandError::DuplicateCustomizationName
assert_eq!(
cmd.validate(),
Err(AddProductCommandError::DuplicateCustomizationName)
)
}
}