...
This commit is contained in:
		@@ -14,15 +14,23 @@ The business models are implemented as Rust structs and enums with serialization
 | 
			
		||||
└─────────────┘     └─────────────┘     └──────┬──────┘
 | 
			
		||||
                          ▲                    │
 | 
			
		||||
                          │                    │
 | 
			
		||||
                    ┌─────┴───────┐            │
 | 
			
		||||
                    ┌─────┴──────────┐         │
 | 
			
		||||
                    │ProductComponent│         │
 | 
			
		||||
                    └─────────────┘            │
 | 
			
		||||
                    └────────────────┘         │
 | 
			
		||||
                                               ▼
 | 
			
		||||
                                        ┌─────────────┐
 | 
			
		||||
                                        │    Sale     │
 | 
			
		||||
                                        └─────────────┘
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Root Objects
 | 
			
		||||
 | 
			
		||||
- root objects are the one who are stored in the DB
 | 
			
		||||
- Root Objects are
 | 
			
		||||
  - currency
 | 
			
		||||
  - product
 | 
			
		||||
  - Sale
 | 
			
		||||
 | 
			
		||||
## Models
 | 
			
		||||
 | 
			
		||||
### Currency (Root Object)
 | 
			
		||||
@@ -33,8 +41,8 @@ Represents a monetary value with an amount and currency code.
 | 
			
		||||
- `amount`: f64 - The monetary amount
 | 
			
		||||
- `currency_code`: String - The currency code (e.g., "USD", "EUR")
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `new()` - Creates a new Currency instance
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `CurrencyBuilder` - Provides a fluent interface for creating Currency instances
 | 
			
		||||
 | 
			
		||||
### Product
 | 
			
		||||
 | 
			
		||||
@@ -59,8 +67,8 @@ Represents a component part of a product.
 | 
			
		||||
- `created_at`: DateTime<Utc> - Creation timestamp
 | 
			
		||||
- `updated_at`: DateTime<Utc> - Last update timestamp
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `new()` - Creates a new ProductComponent with default timestamps
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `ProductComponentBuilder` - Provides a fluent interface for creating ProductComponent instances
 | 
			
		||||
 | 
			
		||||
#### Product  (Root Object)
 | 
			
		||||
Represents a product or service offered.
 | 
			
		||||
@@ -81,13 +89,15 @@ Represents a product or service offered.
 | 
			
		||||
- `components`: Vec<ProductComponent> - List of product components
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `new()` - Creates a new Product with default timestamps
 | 
			
		||||
- `add_component()` - Adds a component to this product
 | 
			
		||||
- `set_purchase_period()` - Updates purchase availability timeframe
 | 
			
		||||
- `set_active_period()` - Updates active timeframe
 | 
			
		||||
- `is_purchasable()` - Checks if product is available for purchase
 | 
			
		||||
- `is_active()` - Checks if product is still active
 | 
			
		||||
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `ProductBuilder` - Provides a fluent interface for creating Product instances
 | 
			
		||||
 | 
			
		||||
**Database Implementation:**
 | 
			
		||||
- Implements `Storable` trait for serialization
 | 
			
		||||
- Implements `SledModel` trait with:
 | 
			
		||||
@@ -115,8 +125,8 @@ Represents an item within a sale.
 | 
			
		||||
- `subtotal`: Currency - Total price for this item (calculated)
 | 
			
		||||
- `active_till`: DateTime<Utc> - When item/service expires
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `new()` - Creates a new SaleItem with calculated subtotal
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `SaleItemBuilder` - Provides a fluent interface for creating SaleItem instances
 | 
			
		||||
 | 
			
		||||
#### Sale (Root Object)
 | 
			
		||||
Represents a complete sale transaction.
 | 
			
		||||
@@ -134,10 +144,12 @@ Represents a complete sale transaction.
 | 
			
		||||
- `items`: Vec<SaleItem> - List of items in the sale
 | 
			
		||||
 | 
			
		||||
**Methods:**
 | 
			
		||||
- `new()` - Creates a new Sale with default timestamps
 | 
			
		||||
- `add_item()` - Adds an item to the sale and updates total
 | 
			
		||||
- `update_status()` - Updates the status of the sale
 | 
			
		||||
 | 
			
		||||
**Builder:**
 | 
			
		||||
- `SaleBuilder` - Provides a fluent interface for creating Sale instances
 | 
			
		||||
 | 
			
		||||
**Database Implementation:**
 | 
			
		||||
- Implements `Storable` trait for serialization
 | 
			
		||||
- Implements `SledModel` trait with:
 | 
			
		||||
@@ -146,62 +158,94 @@ Represents a complete sale transaction.
 | 
			
		||||
 | 
			
		||||
## Usage Examples
 | 
			
		||||
 | 
			
		||||
### Creating a Currency
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
let price = CurrencyBuilder::new()
 | 
			
		||||
    .amount(29.99)
 | 
			
		||||
    .currency_code("USD")
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build currency");
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Creating a Product
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create a currency
 | 
			
		||||
let price = Currency::new(29.99, "USD".to_string());
 | 
			
		||||
// Create a currency using the builder
 | 
			
		||||
let price = CurrencyBuilder::new()
 | 
			
		||||
    .amount(29.99)
 | 
			
		||||
    .currency_code("USD")
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build currency");
 | 
			
		||||
 | 
			
		||||
// Create a product
 | 
			
		||||
let mut product = Product::new(
 | 
			
		||||
    1,                        // id
 | 
			
		||||
    "Premium Service".to_string(),  // name
 | 
			
		||||
    "Our premium service offering".to_string(),  // description
 | 
			
		||||
    price,                    // price
 | 
			
		||||
    ProductType::Service,     // type
 | 
			
		||||
    "Services".to_string(),   // category
 | 
			
		||||
    ProductStatus::Available, // status
 | 
			
		||||
    100,                      // max_amount
 | 
			
		||||
    30,                       // validity_days (service valid for 30 days)
 | 
			
		||||
);
 | 
			
		||||
// Create a component using the builder
 | 
			
		||||
let component = ProductComponentBuilder::new()
 | 
			
		||||
    .id(1)
 | 
			
		||||
    .name("Basic Support")
 | 
			
		||||
    .description("24/7 email support")
 | 
			
		||||
    .quantity(1)
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build product component");
 | 
			
		||||
 | 
			
		||||
// Add a component
 | 
			
		||||
let component = ProductComponent::new(
 | 
			
		||||
    1,                          // id
 | 
			
		||||
    "Basic Support".to_string(),      // name
 | 
			
		||||
    "24/7 email support".to_string(), // description
 | 
			
		||||
    1,                          // quantity
 | 
			
		||||
);
 | 
			
		||||
product.add_component(component);
 | 
			
		||||
// Create a product using the builder
 | 
			
		||||
let product = ProductBuilder::new()
 | 
			
		||||
    .id(1)
 | 
			
		||||
    .name("Premium Service")
 | 
			
		||||
    .description("Our premium service offering")
 | 
			
		||||
    .price(price)
 | 
			
		||||
    .type_(ProductType::Service)
 | 
			
		||||
    .category("Services")
 | 
			
		||||
    .status(ProductStatus::Available)
 | 
			
		||||
    .max_amount(100)
 | 
			
		||||
    .validity_days(30)
 | 
			
		||||
    .add_component(component)
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build product");
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Creating a Sale
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create a new sale
 | 
			
		||||
let mut sale = Sale::new(
 | 
			
		||||
    1,                           // id
 | 
			
		||||
    101,                         // company_id
 | 
			
		||||
    "John Doe".to_string(),      // buyer_name
 | 
			
		||||
    "john.doe@example.com".to_string(),  // buyer_email
 | 
			
		||||
    "USD".to_string(),           // currency_code
 | 
			
		||||
    SaleStatus::Pending,         // status
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Create a sale item
 | 
			
		||||
let now = Utc::now();
 | 
			
		||||
let item = SaleItem::new(
 | 
			
		||||
    1,                           // id
 | 
			
		||||
    1,                           // sale_id
 | 
			
		||||
    1,                           // product_id
 | 
			
		||||
    "Premium Service".to_string(),     // name
 | 
			
		||||
    1,                           // quantity
 | 
			
		||||
    Currency::new(29.99, "USD".to_string()),  // unit_price
 | 
			
		||||
    now + Duration::days(30),    // active_till
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Add the item to the sale
 | 
			
		||||
sale.add_item(item);
 | 
			
		||||
// Create a currency using the builder
 | 
			
		||||
let unit_price = CurrencyBuilder::new()
 | 
			
		||||
    .amount(29.99)
 | 
			
		||||
    .currency_code("USD")
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build currency");
 | 
			
		||||
 | 
			
		||||
// Complete the sale
 | 
			
		||||
sale.update_status(SaleStatus::Completed);
 | 
			
		||||
// Create a sale item using the builder
 | 
			
		||||
let item = SaleItemBuilder::new()
 | 
			
		||||
    .id(1)
 | 
			
		||||
    .sale_id(1)
 | 
			
		||||
    .product_id(1)
 | 
			
		||||
    .name("Premium Service")
 | 
			
		||||
    .quantity(1)
 | 
			
		||||
    .unit_price(unit_price)
 | 
			
		||||
    .active_till(now + Duration::days(30))
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build sale item");
 | 
			
		||||
 | 
			
		||||
// Create a sale using the builder
 | 
			
		||||
let mut sale = SaleBuilder::new()
 | 
			
		||||
    .id(1)
 | 
			
		||||
    .company_id(101)
 | 
			
		||||
    .buyer_name("John Doe")
 | 
			
		||||
    .buyer_email("john.doe@example.com")
 | 
			
		||||
    .currency_code("USD")
 | 
			
		||||
    .status(SaleStatus::Pending)
 | 
			
		||||
    .add_item(item)
 | 
			
		||||
    .build()
 | 
			
		||||
    .expect("Failed to build sale");
 | 
			
		||||
 | 
			
		||||
// Update the sale status
 | 
			
		||||
sale.update_status(SaleStatus::Completed);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Benefits of the Builder Pattern
 | 
			
		||||
 | 
			
		||||
- You don't need to remember the order of parameters.
 | 
			
		||||
- You get readable, self-documenting code.
 | 
			
		||||
- It's easier to provide defaults or optional values.
 | 
			
		||||
- Validation happens at build time, ensuring all required fields are provided.
 | 
			
		||||
@@ -18,3 +18,39 @@ impl Currency {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Currency
 | 
			
		||||
pub struct CurrencyBuilder {
 | 
			
		||||
    amount: Option<f64>,
 | 
			
		||||
    currency_code: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CurrencyBuilder {
 | 
			
		||||
    /// Create a new CurrencyBuilder with all fields set to None
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            amount: None,
 | 
			
		||||
            currency_code: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the amount
 | 
			
		||||
    pub fn amount(mut self, amount: f64) -> Self {
 | 
			
		||||
        self.amount = Some(amount);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the currency code
 | 
			
		||||
    pub fn currency_code<S: Into<String>>(mut self, currency_code: S) -> Self {
 | 
			
		||||
        self.currency_code = Some(currency_code.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the Currency object
 | 
			
		||||
    pub fn build(self) -> Result<Currency, &'static str> {
 | 
			
		||||
        Ok(Currency {
 | 
			
		||||
            amount: self.amount.ok_or("amount is required")?,
 | 
			
		||||
            currency_code: self.currency_code.ok_or("currency_code is required")?,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,10 +13,18 @@ pub use user::User;
 | 
			
		||||
pub use vote::{Vote, VoteOption, Ballot, VoteStatus};
 | 
			
		||||
pub use company::{Company, CompanyStatus, BusinessType};
 | 
			
		||||
pub use meeting::Meeting;
 | 
			
		||||
pub use product::{Product, Currency, ProductComponent, ProductType, ProductStatus};
 | 
			
		||||
pub use product::{Product, ProductComponent, ProductType, ProductStatus};
 | 
			
		||||
pub use sale::Sale;
 | 
			
		||||
pub use shareholder::Shareholder;
 | 
			
		||||
 | 
			
		||||
// Re-export builder types
 | 
			
		||||
pub use product::{ProductBuilder, ProductComponentBuilder};
 | 
			
		||||
pub use sale::{SaleBuilder, SaleItemBuilder};
 | 
			
		||||
 | 
			
		||||
// Re-export Currency and its builder
 | 
			
		||||
pub use product::Currency;
 | 
			
		||||
pub use currency::CurrencyBuilder;
 | 
			
		||||
 | 
			
		||||
// Re-export database components
 | 
			
		||||
// Re-export from core module
 | 
			
		||||
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,79 @@ impl ProductComponent {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for ProductComponent
 | 
			
		||||
pub struct ProductComponentBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    description: Option<String>,
 | 
			
		||||
    quantity: Option<i32>,
 | 
			
		||||
    created_at: Option<DateTime<Utc>>,
 | 
			
		||||
    updated_at: Option<DateTime<Utc>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ProductComponentBuilder {
 | 
			
		||||
    /// Create a new ProductComponentBuilder with all fields set to None
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: None,
 | 
			
		||||
            name: None,
 | 
			
		||||
            description: None,
 | 
			
		||||
            quantity: None,
 | 
			
		||||
            created_at: None,
 | 
			
		||||
            updated_at: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the id
 | 
			
		||||
    pub fn id(mut self, id: u32) -> Self {
 | 
			
		||||
        self.id = Some(id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the name
 | 
			
		||||
    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
 | 
			
		||||
        self.name = Some(name.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the description
 | 
			
		||||
    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
 | 
			
		||||
        self.description = Some(description.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the quantity
 | 
			
		||||
    pub fn quantity(mut self, quantity: i32) -> Self {
 | 
			
		||||
        self.quantity = Some(quantity);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the created_at timestamp
 | 
			
		||||
    pub fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
 | 
			
		||||
        self.created_at = Some(created_at);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the updated_at timestamp
 | 
			
		||||
    pub fn updated_at(mut self, updated_at: DateTime<Utc>) -> Self {
 | 
			
		||||
        self.updated_at = Some(updated_at);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the ProductComponent object
 | 
			
		||||
    pub fn build(self) -> Result<ProductComponent, &'static str> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        Ok(ProductComponent {
 | 
			
		||||
            id: self.id.ok_or("id is required")?,
 | 
			
		||||
            name: self.name.ok_or("name is required")?,
 | 
			
		||||
            description: self.description.ok_or("description is required")?,
 | 
			
		||||
            quantity: self.quantity.ok_or("quantity is required")?,
 | 
			
		||||
            created_at: self.created_at.unwrap_or(now),
 | 
			
		||||
            updated_at: self.updated_at.unwrap_or(now),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Product represents a product or service offered by the Freezone
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Product {
 | 
			
		||||
@@ -124,6 +197,149 @@ impl Product {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Product
 | 
			
		||||
pub struct ProductBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    description: Option<String>,
 | 
			
		||||
    price: Option<Currency>,
 | 
			
		||||
    type_: Option<ProductType>,
 | 
			
		||||
    category: Option<String>,
 | 
			
		||||
    status: Option<ProductStatus>,
 | 
			
		||||
    created_at: Option<DateTime<Utc>>,
 | 
			
		||||
    updated_at: Option<DateTime<Utc>>,
 | 
			
		||||
    max_amount: Option<u16>,
 | 
			
		||||
    purchase_till: Option<DateTime<Utc>>,
 | 
			
		||||
    active_till: Option<DateTime<Utc>>,
 | 
			
		||||
    components: Vec<ProductComponent>,
 | 
			
		||||
    validity_days: Option<i64>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ProductBuilder {
 | 
			
		||||
    /// Create a new ProductBuilder with all fields set to None
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: None,
 | 
			
		||||
            name: None,
 | 
			
		||||
            description: None,
 | 
			
		||||
            price: None,
 | 
			
		||||
            type_: None,
 | 
			
		||||
            category: None,
 | 
			
		||||
            status: None,
 | 
			
		||||
            created_at: None,
 | 
			
		||||
            updated_at: None,
 | 
			
		||||
            max_amount: None,
 | 
			
		||||
            purchase_till: None,
 | 
			
		||||
            active_till: None,
 | 
			
		||||
            components: Vec::new(),
 | 
			
		||||
            validity_days: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the id
 | 
			
		||||
    pub fn id(mut self, id: u32) -> Self {
 | 
			
		||||
        self.id = Some(id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the name
 | 
			
		||||
    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
 | 
			
		||||
        self.name = Some(name.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the description
 | 
			
		||||
    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
 | 
			
		||||
        self.description = Some(description.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the price
 | 
			
		||||
    pub fn price(mut self, price: Currency) -> Self {
 | 
			
		||||
        self.price = Some(price);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the product type
 | 
			
		||||
    pub fn type_(mut self, type_: ProductType) -> Self {
 | 
			
		||||
        self.type_ = Some(type_);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the category
 | 
			
		||||
    pub fn category<S: Into<String>>(mut self, category: S) -> Self {
 | 
			
		||||
        self.category = Some(category.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the status
 | 
			
		||||
    pub fn status(mut self, status: ProductStatus) -> Self {
 | 
			
		||||
        self.status = Some(status);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the max amount
 | 
			
		||||
    pub fn max_amount(mut self, max_amount: u16) -> Self {
 | 
			
		||||
        self.max_amount = Some(max_amount);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the validity days
 | 
			
		||||
    pub fn validity_days(mut self, validity_days: i64) -> Self {
 | 
			
		||||
        self.validity_days = Some(validity_days);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the purchase_till date directly
 | 
			
		||||
    pub fn purchase_till(mut self, purchase_till: DateTime<Utc>) -> Self {
 | 
			
		||||
        self.purchase_till = Some(purchase_till);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the active_till date directly
 | 
			
		||||
    pub fn active_till(mut self, active_till: DateTime<Utc>) -> Self {
 | 
			
		||||
        self.active_till = Some(active_till);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a component to the product
 | 
			
		||||
    pub fn add_component(mut self, component: ProductComponent) -> Self {
 | 
			
		||||
        self.components.push(component);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the Product object
 | 
			
		||||
    pub fn build(self) -> Result<Product, &'static str> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        let created_at = self.created_at.unwrap_or(now);
 | 
			
		||||
        let updated_at = self.updated_at.unwrap_or(now);
 | 
			
		||||
        
 | 
			
		||||
        // Calculate purchase_till and active_till based on validity_days if not set directly
 | 
			
		||||
        let purchase_till = self.purchase_till.unwrap_or(now + Duration::days(365));
 | 
			
		||||
        let active_till = if let Some(validity_days) = self.validity_days {
 | 
			
		||||
            self.active_till.unwrap_or(now + Duration::days(validity_days))
 | 
			
		||||
        } else {
 | 
			
		||||
            self.active_till.ok_or("Either active_till or validity_days must be provided")?
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(Product {
 | 
			
		||||
            id: self.id.ok_or("id is required")?,
 | 
			
		||||
            name: self.name.ok_or("name is required")?,
 | 
			
		||||
            description: self.description.ok_or("description is required")?,
 | 
			
		||||
            price: self.price.ok_or("price is required")?,
 | 
			
		||||
            type_: self.type_.ok_or("type_ is required")?,
 | 
			
		||||
            category: self.category.ok_or("category is required")?,
 | 
			
		||||
            status: self.status.ok_or("status is required")?,
 | 
			
		||||
            created_at,
 | 
			
		||||
            updated_at,
 | 
			
		||||
            max_amount: self.max_amount.ok_or("max_amount is required")?,
 | 
			
		||||
            purchase_till,
 | 
			
		||||
            active_till,
 | 
			
		||||
            components: self.components,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Implement Storable trait (provides default dump/load)
 | 
			
		||||
impl Storable for Product {}
 | 
			
		||||
 | 
			
		||||
@@ -137,3 +353,6 @@ impl SledModel for Product {
 | 
			
		||||
        "product"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Import Currency from the currency module
 | 
			
		||||
use super::Currency;
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,100 @@ impl SaleItem {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for SaleItem
 | 
			
		||||
pub struct SaleItemBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    sale_id: Option<u32>,
 | 
			
		||||
    product_id: Option<u32>,
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    quantity: Option<i32>,
 | 
			
		||||
    unit_price: Option<Currency>,
 | 
			
		||||
    subtotal: Option<Currency>,
 | 
			
		||||
    active_till: Option<DateTime<Utc>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SaleItemBuilder {
 | 
			
		||||
    /// Create a new SaleItemBuilder with all fields set to None
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: None,
 | 
			
		||||
            sale_id: None,
 | 
			
		||||
            product_id: None,
 | 
			
		||||
            name: None,
 | 
			
		||||
            quantity: None,
 | 
			
		||||
            unit_price: None,
 | 
			
		||||
            subtotal: None,
 | 
			
		||||
            active_till: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the id
 | 
			
		||||
    pub fn id(mut self, id: u32) -> Self {
 | 
			
		||||
        self.id = Some(id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the sale_id
 | 
			
		||||
    pub fn sale_id(mut self, sale_id: u32) -> Self {
 | 
			
		||||
        self.sale_id = Some(sale_id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the product_id
 | 
			
		||||
    pub fn product_id(mut self, product_id: u32) -> Self {
 | 
			
		||||
        self.product_id = Some(product_id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the name
 | 
			
		||||
    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
 | 
			
		||||
        self.name = Some(name.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the quantity
 | 
			
		||||
    pub fn quantity(mut self, quantity: i32) -> Self {
 | 
			
		||||
        self.quantity = Some(quantity);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the unit_price
 | 
			
		||||
    pub fn unit_price(mut self, unit_price: Currency) -> Self {
 | 
			
		||||
        self.unit_price = Some(unit_price);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the active_till
 | 
			
		||||
    pub fn active_till(mut self, active_till: DateTime<Utc>) -> Self {
 | 
			
		||||
        self.active_till = Some(active_till);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the SaleItem object
 | 
			
		||||
    pub fn build(self) -> Result<SaleItem, &'static str> {
 | 
			
		||||
        let unit_price = self.unit_price.ok_or("unit_price is required")?;
 | 
			
		||||
        let quantity = self.quantity.ok_or("quantity is required")?;
 | 
			
		||||
        
 | 
			
		||||
        // Calculate subtotal
 | 
			
		||||
        let amount = unit_price.amount * quantity as f64;
 | 
			
		||||
        let subtotal = Currency {
 | 
			
		||||
            amount,
 | 
			
		||||
            currency_code: unit_price.currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(SaleItem {
 | 
			
		||||
            id: self.id.ok_or("id is required")?,
 | 
			
		||||
            sale_id: self.sale_id.ok_or("sale_id is required")?,
 | 
			
		||||
            product_id: self.product_id.ok_or("product_id is required")?,
 | 
			
		||||
            name: self.name.ok_or("name is required")?,
 | 
			
		||||
            quantity,
 | 
			
		||||
            unit_price,
 | 
			
		||||
            subtotal,
 | 
			
		||||
            active_till: self.active_till.ok_or("active_till is required")?,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Sale represents a sale of products or services
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Sale {
 | 
			
		||||
@@ -131,6 +225,134 @@ impl Sale {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Sale
 | 
			
		||||
pub struct SaleBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    company_id: Option<u32>,
 | 
			
		||||
    buyer_name: Option<String>,
 | 
			
		||||
    buyer_email: Option<String>,
 | 
			
		||||
    total_amount: Option<Currency>,
 | 
			
		||||
    status: Option<SaleStatus>,
 | 
			
		||||
    sale_date: Option<DateTime<Utc>>,
 | 
			
		||||
    created_at: Option<DateTime<Utc>>,
 | 
			
		||||
    updated_at: Option<DateTime<Utc>>,
 | 
			
		||||
    items: Vec<SaleItem>,
 | 
			
		||||
    currency_code: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SaleBuilder {
 | 
			
		||||
    /// Create a new SaleBuilder with all fields set to None
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: None,
 | 
			
		||||
            company_id: None,
 | 
			
		||||
            buyer_name: None,
 | 
			
		||||
            buyer_email: None,
 | 
			
		||||
            total_amount: None,
 | 
			
		||||
            status: None,
 | 
			
		||||
            sale_date: None,
 | 
			
		||||
            created_at: None,
 | 
			
		||||
            updated_at: None,
 | 
			
		||||
            items: Vec::new(),
 | 
			
		||||
            currency_code: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the id
 | 
			
		||||
    pub fn id(mut self, id: u32) -> Self {
 | 
			
		||||
        self.id = Some(id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the company_id
 | 
			
		||||
    pub fn company_id(mut self, company_id: u32) -> Self {
 | 
			
		||||
        self.company_id = Some(company_id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the buyer_name
 | 
			
		||||
    pub fn buyer_name<S: Into<String>>(mut self, buyer_name: S) -> Self {
 | 
			
		||||
        self.buyer_name = Some(buyer_name.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the buyer_email
 | 
			
		||||
    pub fn buyer_email<S: Into<String>>(mut self, buyer_email: S) -> Self {
 | 
			
		||||
        self.buyer_email = Some(buyer_email.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the currency_code
 | 
			
		||||
    pub fn currency_code<S: Into<String>>(mut self, currency_code: S) -> Self {
 | 
			
		||||
        self.currency_code = Some(currency_code.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the status
 | 
			
		||||
    pub fn status(mut self, status: SaleStatus) -> Self {
 | 
			
		||||
        self.status = Some(status);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the sale_date
 | 
			
		||||
    pub fn sale_date(mut self, sale_date: DateTime<Utc>) -> Self {
 | 
			
		||||
        self.sale_date = Some(sale_date);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add an item to the sale
 | 
			
		||||
    pub fn add_item(mut self, item: SaleItem) -> Self {
 | 
			
		||||
        self.items.push(item);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the Sale object
 | 
			
		||||
    pub fn build(self) -> Result<Sale, &'static str> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        let id = self.id.ok_or("id is required")?;
 | 
			
		||||
        let currency_code = self.currency_code.ok_or("currency_code is required")?;
 | 
			
		||||
        
 | 
			
		||||
        // Initialize with empty total amount
 | 
			
		||||
        let mut total_amount = Currency {
 | 
			
		||||
            amount: 0.0,
 | 
			
		||||
            currency_code: currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Calculate total amount from items
 | 
			
		||||
        for item in &self.items {
 | 
			
		||||
            // Make sure the item's sale_id matches this sale
 | 
			
		||||
            if item.sale_id != id {
 | 
			
		||||
                return Err("Item sale_id must match sale id");
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if total_amount.amount == 0.0 {
 | 
			
		||||
                // First item, initialize the total amount with the same currency
 | 
			
		||||
                total_amount = Currency {
 | 
			
		||||
                    amount: item.subtotal.amount,
 | 
			
		||||
                    currency_code: item.subtotal.currency_code.clone(),
 | 
			
		||||
                };
 | 
			
		||||
            } else {
 | 
			
		||||
                // Add to the existing total
 | 
			
		||||
                // (Assumes all items have the same currency)
 | 
			
		||||
                total_amount.amount += item.subtotal.amount;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(Sale {
 | 
			
		||||
            id,
 | 
			
		||||
            company_id: self.company_id.ok_or("company_id is required")?,
 | 
			
		||||
            buyer_name: self.buyer_name.ok_or("buyer_name is required")?,
 | 
			
		||||
            buyer_email: self.buyer_email.ok_or("buyer_email is required")?,
 | 
			
		||||
            total_amount: self.total_amount.unwrap_or(total_amount),
 | 
			
		||||
            status: self.status.ok_or("status is required")?,
 | 
			
		||||
            sale_date: self.sale_date.unwrap_or(now),
 | 
			
		||||
            created_at: self.created_at.unwrap_or(now),
 | 
			
		||||
            updated_at: self.updated_at.unwrap_or(now),
 | 
			
		||||
            items: self.items,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Implement Storable trait (provides default dump/load)
 | 
			
		||||
impl Storable for Sale {}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user