...
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│ │
|
||||
└─────────────┘ │
|
||||
└────────────────┘ │
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ 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 {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user