...
This commit is contained in:
parent
32f6d87454
commit
8a5e41a265
156
herodb/aiprompts/builderparadigm.md
Normal file
156
herodb/aiprompts/builderparadigm.md
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
|
||||||
|
|
||||||
|
please refactor each of the objects in the the chosen folder to use builder paradigm, see below for an example
|
||||||
|
we always start from root object, each file e.g. product.rs corresponds to a root object, the rootobject is what is stored in the DB, the rest are sub objects which are children of the root object
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ Step 1: Define your struct
|
||||||
|
```rust
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ProductType {
|
||||||
|
Service,
|
||||||
|
// Other variants...
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ProductStatus {
|
||||||
|
Available,
|
||||||
|
Unavailable,
|
||||||
|
// Other variants...
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Product {
|
||||||
|
id: u32,
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
price: f64,
|
||||||
|
product_type: ProductType,
|
||||||
|
category: String,
|
||||||
|
status: ProductStatus,
|
||||||
|
max_amount: u32,
|
||||||
|
validity_days: u32,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ Step 2: Create a builder
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct ProductBuilder {
|
||||||
|
id: Option<u32>,
|
||||||
|
name: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
price: Option<f64>,
|
||||||
|
product_type: Option<ProductType>,
|
||||||
|
category: Option<String>,
|
||||||
|
status: Option<ProductStatus>,
|
||||||
|
max_amount: Option<u32>,
|
||||||
|
validity_days: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProductBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
id: None,
|
||||||
|
name: None,
|
||||||
|
description: None,
|
||||||
|
price: None,
|
||||||
|
product_type: None,
|
||||||
|
category: None,
|
||||||
|
status: None,
|
||||||
|
max_amount: None,
|
||||||
|
validity_days: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(mut self, id: u32) -> Self {
|
||||||
|
self.id = Some(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name<S: Into<String>>(mut self, name: S) -> Self {
|
||||||
|
self.name = Some(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
|
||||||
|
self.description = Some(description.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn price(mut self, price: f64) -> Self {
|
||||||
|
self.price = Some(price);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn product_type(mut self, product_type: ProductType) -> Self {
|
||||||
|
self.product_type = Some(product_type);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn category<S: Into<String>>(mut self, category: S) -> Self {
|
||||||
|
self.category = Some(category.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(mut self, status: ProductStatus) -> Self {
|
||||||
|
self.status = Some(status);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_amount(mut self, max_amount: u32) -> Self {
|
||||||
|
self.max_amount = Some(max_amount);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validity_days(mut self, validity_days: u32) -> Self {
|
||||||
|
self.validity_days = Some(validity_days);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Result<Product, &'static str> {
|
||||||
|
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")?,
|
||||||
|
product_type: self.product_type.ok_or("type is required")?,
|
||||||
|
category: self.category.ok_or("category is required")?,
|
||||||
|
status: self.status.ok_or("status is required")?,
|
||||||
|
max_amount: self.max_amount.ok_or("max_amount is required")?,
|
||||||
|
validity_days: self.validity_days.ok_or("validity_days is required")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ Step 3: Use it like this
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let product = ProductBuilder::new()
|
||||||
|
.id(1)
|
||||||
|
.name("Premium Service")
|
||||||
|
.description("Our premium service offering")
|
||||||
|
.price(99.99)
|
||||||
|
.product_type(ProductType::Service)
|
||||||
|
.category("Services")
|
||||||
|
.status(ProductStatus::Available)
|
||||||
|
.max_amount(100)
|
||||||
|
.validity_days(30)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build product");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This way:
|
||||||
|
- 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 if you want later.
|
||||||
|
|
||||||
|
Want help generating this automatically via a macro or just want it shorter? I can show you a derive macro to do that too.
|
9
herodb/aiprompts/moduledocu.md
Normal file
9
herodb/aiprompts/moduledocu.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
make a readme for the chosen folder (module)
|
||||||
|
|
||||||
|
make a dense representation of the objects and how to use them
|
||||||
|
explain what the rootobjects are
|
||||||
|
|
||||||
|
we always start from root object, each file e.g. product.rs corresponds to a root object, the rootobject is what is stored in the DB, the rest are sub objects which are children of the root object
|
||||||
|
|
||||||
|
don't explain the low level implementation details like sled, ...
|
@ -14,15 +14,23 @@ The business models are implemented as Rust structs and enums with serialization
|
|||||||
└─────────────┘ └─────────────┘ └──────┬──────┘
|
└─────────────┘ └─────────────┘ └──────┬──────┘
|
||||||
▲ │
|
▲ │
|
||||||
│ │
|
│ │
|
||||||
┌─────┴───────┐ │
|
┌─────┴──────────┐ │
|
||||||
│ProductComponent│ │
|
│ProductComponent│ │
|
||||||
└─────────────┘ │
|
└────────────────┘ │
|
||||||
▼
|
▼
|
||||||
┌─────────────┐
|
┌─────────────┐
|
||||||
│ Sale │
|
│ Sale │
|
||||||
└─────────────┘
|
└─────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Root Objects
|
||||||
|
|
||||||
|
- root objects are the one who are stored in the DB
|
||||||
|
- Root Objects are
|
||||||
|
- currency
|
||||||
|
- product
|
||||||
|
- Sale
|
||||||
|
|
||||||
## Models
|
## Models
|
||||||
|
|
||||||
### Currency (Root Object)
|
### Currency (Root Object)
|
||||||
@ -33,8 +41,8 @@ Represents a monetary value with an amount and currency code.
|
|||||||
- `amount`: f64 - The monetary amount
|
- `amount`: f64 - The monetary amount
|
||||||
- `currency_code`: String - The currency code (e.g., "USD", "EUR")
|
- `currency_code`: String - The currency code (e.g., "USD", "EUR")
|
||||||
|
|
||||||
**Methods:**
|
**Builder:**
|
||||||
- `new()` - Creates a new Currency instance
|
- `CurrencyBuilder` - Provides a fluent interface for creating Currency instances
|
||||||
|
|
||||||
### Product
|
### Product
|
||||||
|
|
||||||
@ -59,8 +67,8 @@ Represents a component part of a product.
|
|||||||
- `created_at`: DateTime<Utc> - Creation timestamp
|
- `created_at`: DateTime<Utc> - Creation timestamp
|
||||||
- `updated_at`: DateTime<Utc> - Last update timestamp
|
- `updated_at`: DateTime<Utc> - Last update timestamp
|
||||||
|
|
||||||
**Methods:**
|
**Builder:**
|
||||||
- `new()` - Creates a new ProductComponent with default timestamps
|
- `ProductComponentBuilder` - Provides a fluent interface for creating ProductComponent instances
|
||||||
|
|
||||||
#### Product (Root Object)
|
#### Product (Root Object)
|
||||||
Represents a product or service offered.
|
Represents a product or service offered.
|
||||||
@ -81,13 +89,15 @@ Represents a product or service offered.
|
|||||||
- `components`: Vec<ProductComponent> - List of product components
|
- `components`: Vec<ProductComponent> - List of product components
|
||||||
|
|
||||||
**Methods:**
|
**Methods:**
|
||||||
- `new()` - Creates a new Product with default timestamps
|
|
||||||
- `add_component()` - Adds a component to this product
|
- `add_component()` - Adds a component to this product
|
||||||
- `set_purchase_period()` - Updates purchase availability timeframe
|
- `set_purchase_period()` - Updates purchase availability timeframe
|
||||||
- `set_active_period()` - Updates active timeframe
|
- `set_active_period()` - Updates active timeframe
|
||||||
- `is_purchasable()` - Checks if product is available for purchase
|
- `is_purchasable()` - Checks if product is available for purchase
|
||||||
- `is_active()` - Checks if product is still active
|
- `is_active()` - Checks if product is still active
|
||||||
|
|
||||||
|
**Builder:**
|
||||||
|
- `ProductBuilder` - Provides a fluent interface for creating Product instances
|
||||||
|
|
||||||
**Database Implementation:**
|
**Database Implementation:**
|
||||||
- Implements `Storable` trait for serialization
|
- Implements `Storable` trait for serialization
|
||||||
- Implements `SledModel` trait with:
|
- Implements `SledModel` trait with:
|
||||||
@ -115,8 +125,8 @@ Represents an item within a sale.
|
|||||||
- `subtotal`: Currency - Total price for this item (calculated)
|
- `subtotal`: Currency - Total price for this item (calculated)
|
||||||
- `active_till`: DateTime<Utc> - When item/service expires
|
- `active_till`: DateTime<Utc> - When item/service expires
|
||||||
|
|
||||||
**Methods:**
|
**Builder:**
|
||||||
- `new()` - Creates a new SaleItem with calculated subtotal
|
- `SaleItemBuilder` - Provides a fluent interface for creating SaleItem instances
|
||||||
|
|
||||||
#### Sale (Root Object)
|
#### Sale (Root Object)
|
||||||
Represents a complete sale transaction.
|
Represents a complete sale transaction.
|
||||||
@ -134,10 +144,12 @@ Represents a complete sale transaction.
|
|||||||
- `items`: Vec<SaleItem> - List of items in the sale
|
- `items`: Vec<SaleItem> - List of items in the sale
|
||||||
|
|
||||||
**Methods:**
|
**Methods:**
|
||||||
- `new()` - Creates a new Sale with default timestamps
|
|
||||||
- `add_item()` - Adds an item to the sale and updates total
|
- `add_item()` - Adds an item to the sale and updates total
|
||||||
- `update_status()` - Updates the status of the sale
|
- `update_status()` - Updates the status of the sale
|
||||||
|
|
||||||
|
**Builder:**
|
||||||
|
- `SaleBuilder` - Provides a fluent interface for creating Sale instances
|
||||||
|
|
||||||
**Database Implementation:**
|
**Database Implementation:**
|
||||||
- Implements `Storable` trait for serialization
|
- Implements `Storable` trait for serialization
|
||||||
- Implements `SledModel` trait with:
|
- Implements `SledModel` trait with:
|
||||||
@ -146,62 +158,94 @@ Represents a complete sale transaction.
|
|||||||
|
|
||||||
## Usage Examples
|
## 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
|
### Creating a Product
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Create a currency
|
// Create a currency using the builder
|
||||||
let price = Currency::new(29.99, "USD".to_string());
|
let price = CurrencyBuilder::new()
|
||||||
|
.amount(29.99)
|
||||||
|
.currency_code("USD")
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build currency");
|
||||||
|
|
||||||
// Create a product
|
// Create a component using the builder
|
||||||
let mut product = Product::new(
|
let component = ProductComponentBuilder::new()
|
||||||
1, // id
|
.id(1)
|
||||||
"Premium Service".to_string(), // name
|
.name("Basic Support")
|
||||||
"Our premium service offering".to_string(), // description
|
.description("24/7 email support")
|
||||||
price, // price
|
.quantity(1)
|
||||||
ProductType::Service, // type
|
.build()
|
||||||
"Services".to_string(), // category
|
.expect("Failed to build product component");
|
||||||
ProductStatus::Available, // status
|
|
||||||
100, // max_amount
|
|
||||||
30, // validity_days (service valid for 30 days)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a component
|
// Create a product using the builder
|
||||||
let component = ProductComponent::new(
|
let product = ProductBuilder::new()
|
||||||
1, // id
|
.id(1)
|
||||||
"Basic Support".to_string(), // name
|
.name("Premium Service")
|
||||||
"24/7 email support".to_string(), // description
|
.description("Our premium service offering")
|
||||||
1, // quantity
|
.price(price)
|
||||||
);
|
.type_(ProductType::Service)
|
||||||
product.add_component(component);
|
.category("Services")
|
||||||
|
.status(ProductStatus::Available)
|
||||||
|
.max_amount(100)
|
||||||
|
.validity_days(30)
|
||||||
|
.add_component(component)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build product");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creating a Sale
|
### Creating a Sale
|
||||||
|
|
||||||
```rust
|
```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 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
|
// Create a currency using the builder
|
||||||
sale.add_item(item);
|
let unit_price = CurrencyBuilder::new()
|
||||||
|
.amount(29.99)
|
||||||
|
.currency_code("USD")
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build currency");
|
||||||
|
|
||||||
// Complete the sale
|
// Create a sale item using the builder
|
||||||
sale.update_status(SaleStatus::Completed);
|
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 vote::{Vote, VoteOption, Ballot, VoteStatus};
|
||||||
pub use company::{Company, CompanyStatus, BusinessType};
|
pub use company::{Company, CompanyStatus, BusinessType};
|
||||||
pub use meeting::Meeting;
|
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 sale::Sale;
|
||||||
pub use shareholder::Shareholder;
|
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 database components
|
||||||
// Re-export from core module
|
// Re-export from core module
|
||||||
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
|
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
|
/// Product represents a product or service offered by the Freezone
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Product {
|
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)
|
// Implement Storable trait (provides default dump/load)
|
||||||
impl Storable for Product {}
|
impl Storable for Product {}
|
||||||
|
|
||||||
@ -137,3 +353,6 @@ impl SledModel for Product {
|
|||||||
"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
|
/// Sale represents a sale of products or services
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Sale {
|
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)
|
// Implement Storable trait (provides default dump/load)
|
||||||
impl Storable for Sale {}
|
impl Storable for Sale {}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user