...
This commit is contained in:
		@@ -1,10 +1,11 @@
 | 
			
		||||
use chrono::{DateTime, Utc, Duration};
 | 
			
		||||
use herodb::db::{DB, DBBuilder};
 | 
			
		||||
use chrono::{Utc, Duration};
 | 
			
		||||
use herodb::db::DBBuilder;
 | 
			
		||||
use herodb::models::biz::{
 | 
			
		||||
    Currency, CurrencyBuilder,
 | 
			
		||||
    Product, ProductBuilder, ProductComponent, ProductComponentBuilder,
 | 
			
		||||
    Product, ProductBuilder, ProductComponentBuilder,
 | 
			
		||||
    ProductType, ProductStatus,
 | 
			
		||||
    Sale, SaleBuilder, SaleItem, SaleItemBuilder, SaleStatus
 | 
			
		||||
    Sale, SaleBuilder, SaleItemBuilder, SaleStatus,
 | 
			
		||||
    ExchangeRate, ExchangeRateBuilder, EXCHANGE_RATE_SERVICE
 | 
			
		||||
};
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use std::fs;
 | 
			
		||||
@@ -26,6 +27,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        .register_model::<Product>()
 | 
			
		||||
        .register_model::<Currency>()
 | 
			
		||||
        .register_model::<Sale>()
 | 
			
		||||
        .register_model::<ExchangeRate>()
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    println!("\n1. Creating Products with Builder Pattern");
 | 
			
		||||
@@ -39,14 +41,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
 | 
			
		||||
    // Insert the currency
 | 
			
		||||
    db.insert_currency(&usd)?;
 | 
			
		||||
    println!("Currency created: ${:.2} {}", usd.amount, usd.currency_code);
 | 
			
		||||
    println!("Currency created: ${} {}", usd.amount, usd.currency_code);
 | 
			
		||||
 | 
			
		||||
    // Create product components using the builder
 | 
			
		||||
    // Create product components using the builder with energy usage and cost
 | 
			
		||||
    let component1 = ProductComponentBuilder::new()
 | 
			
		||||
        .id(101)
 | 
			
		||||
        .name("Basic Support")
 | 
			
		||||
        .description("24/7 email support")
 | 
			
		||||
        .quantity(1)
 | 
			
		||||
        .energy_usage(5.0) // 5 watts
 | 
			
		||||
        .cost(CurrencyBuilder::new()
 | 
			
		||||
            .amount(5.0)
 | 
			
		||||
            .currency_code("USD")
 | 
			
		||||
            .build()?)
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    let component2 = ProductComponentBuilder::new()
 | 
			
		||||
@@ -54,6 +61,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        .name("Premium Support")
 | 
			
		||||
        .description("24/7 phone and email support")
 | 
			
		||||
        .quantity(1)
 | 
			
		||||
        .energy_usage(10.0) // 10 watts
 | 
			
		||||
        .cost(CurrencyBuilder::new()
 | 
			
		||||
            .amount(15.0)
 | 
			
		||||
            .currency_code("USD")
 | 
			
		||||
            .build()?)
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    // Create products using the builder
 | 
			
		||||
@@ -67,7 +79,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
            .build()?)
 | 
			
		||||
        .type_(ProductType::Service)
 | 
			
		||||
        .category("Subscription")
 | 
			
		||||
        .status(ProductStatus::Available)
 | 
			
		||||
        .status(ProductStatus::Active)
 | 
			
		||||
        .max_amount(1000)
 | 
			
		||||
        .validity_days(30)
 | 
			
		||||
        .add_component(component1)
 | 
			
		||||
@@ -83,7 +95,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
            .build()?)
 | 
			
		||||
        .type_(ProductType::Service)
 | 
			
		||||
        .category("Subscription")
 | 
			
		||||
        .status(ProductStatus::Available)
 | 
			
		||||
        .status(ProductStatus::Active)
 | 
			
		||||
        .max_amount(500)
 | 
			
		||||
        .validity_days(30)
 | 
			
		||||
        .add_component(component2)
 | 
			
		||||
@@ -93,18 +105,32 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    db.insert_product(&product1)?;
 | 
			
		||||
    db.insert_product(&product2)?;
 | 
			
		||||
 | 
			
		||||
    println!("Product created: {} (${:.2})", product1.name, product1.price.amount);
 | 
			
		||||
    println!("Product created: {} (${:.2})", product2.name, product2.price.amount);
 | 
			
		||||
    println!("Product created: {} (${}) USD", product1.name, product1.price.amount);
 | 
			
		||||
    println!("Product created: {} (${}) USD", product2.name, product2.price.amount);
 | 
			
		||||
 | 
			
		||||
    println!("\n2. Retrieving Products");
 | 
			
		||||
    println!("--------------------");
 | 
			
		||||
 | 
			
		||||
    // Retrieve products using model-specific methods
 | 
			
		||||
    let retrieved_product1 = db.get_product(1)?;
 | 
			
		||||
    println!("Retrieved: {} (${:.2})", retrieved_product1.name, retrieved_product1.price.amount);
 | 
			
		||||
    println!("Retrieved: {} (${}) USD", retrieved_product1.name, retrieved_product1.price.amount);
 | 
			
		||||
    println!("Components:");
 | 
			
		||||
    for component in &retrieved_product1.components {
 | 
			
		||||
        println!("  - {} ({})", component.name, component.description);
 | 
			
		||||
        println!("  - {} ({}, Energy: {}W, Cost: ${} USD)", 
 | 
			
		||||
            component.name, 
 | 
			
		||||
            component.description,
 | 
			
		||||
            component.energy_usage,
 | 
			
		||||
            component.cost.amount
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate total energy usage
 | 
			
		||||
    let total_energy = retrieved_product1.total_energy_usage();
 | 
			
		||||
    println!("Total energy usage: {}W", total_energy);
 | 
			
		||||
 | 
			
		||||
    // Calculate components cost
 | 
			
		||||
    if let Some(components_cost) = retrieved_product1.components_cost_in_usd() {
 | 
			
		||||
        println!("Total components cost: ${} USD", components_cost.amount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    println!("\n3. Listing All Products");
 | 
			
		||||
@@ -114,7 +140,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    let all_products = db.list_products()?;
 | 
			
		||||
    println!("Found {} products:", all_products.len());
 | 
			
		||||
    for product in all_products {
 | 
			
		||||
        println!("  - {} (${:.2}, {})", 
 | 
			
		||||
        println!("  - {} (${} USD, {})", 
 | 
			
		||||
            product.name, 
 | 
			
		||||
            product.price.amount,
 | 
			
		||||
            if product.is_purchasable() { "Available" } else { "Unavailable" }
 | 
			
		||||
@@ -152,7 +178,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
 | 
			
		||||
    // Insert the sale using model-specific methods
 | 
			
		||||
    db.insert_sale(&sale)?;
 | 
			
		||||
    println!("Sale created: #{} for {} (${:.2})", 
 | 
			
		||||
    println!("Sale created: #{} for {} (${} USD)", 
 | 
			
		||||
        sale.id, 
 | 
			
		||||
        sale.buyer_name,
 | 
			
		||||
        sale.total_amount.amount
 | 
			
		||||
@@ -171,7 +197,77 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    
 | 
			
		||||
    println!("Updated sale status to {:?}", retrieved_sale.status);
 | 
			
		||||
 | 
			
		||||
    println!("\n6. Deleting Objects");
 | 
			
		||||
    println!("\n6. Working with Exchange Rates");
 | 
			
		||||
    println!("----------------------------");
 | 
			
		||||
 | 
			
		||||
    // Create and set exchange rates using the builder
 | 
			
		||||
    let eur_rate = ExchangeRateBuilder::new()
 | 
			
		||||
        .base_currency("EUR")
 | 
			
		||||
        .target_currency("USD")
 | 
			
		||||
        .rate(1.18)
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    let gbp_rate = ExchangeRateBuilder::new()
 | 
			
		||||
        .base_currency("GBP")
 | 
			
		||||
        .target_currency("USD")
 | 
			
		||||
        .rate(1.38)
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    // Insert exchange rates into the database
 | 
			
		||||
    db.insert_exchange_rate(&eur_rate)?;
 | 
			
		||||
    db.insert_exchange_rate(&gbp_rate)?;
 | 
			
		||||
 | 
			
		||||
    // Set the exchange rates in the service
 | 
			
		||||
    EXCHANGE_RATE_SERVICE.set_rate(eur_rate.clone());
 | 
			
		||||
    EXCHANGE_RATE_SERVICE.set_rate(gbp_rate.clone());
 | 
			
		||||
 | 
			
		||||
    println!("Exchange rates set:");
 | 
			
		||||
    println!("  - 1 EUR = {} USD", eur_rate.rate);
 | 
			
		||||
    println!("  - 1 GBP = {} USD", gbp_rate.rate);
 | 
			
		||||
 | 
			
		||||
    // Create currencies in different denominations
 | 
			
		||||
    let eur_price = CurrencyBuilder::new()
 | 
			
		||||
        .amount(100.0)
 | 
			
		||||
        .currency_code("EUR")
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    let gbp_price = CurrencyBuilder::new()
 | 
			
		||||
        .amount(85.0)
 | 
			
		||||
        .currency_code("GBP")
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    // Convert to USD
 | 
			
		||||
    if let Some(eur_in_usd) = eur_price.to_usd() {
 | 
			
		||||
        println!("{} EUR = {} USD", eur_price.amount, eur_in_usd.amount);
 | 
			
		||||
    } else {
 | 
			
		||||
        println!("Could not convert EUR to USD");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(gbp_in_usd) = gbp_price.to_usd() {
 | 
			
		||||
        println!("{} GBP = {} USD", gbp_price.amount, gbp_in_usd.amount);
 | 
			
		||||
    } else {
 | 
			
		||||
        println!("Could not convert GBP to USD");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Convert between currencies
 | 
			
		||||
    if let Some(eur_in_gbp) = eur_price.to_currency("GBP") {
 | 
			
		||||
        println!("{} EUR = {} GBP", eur_price.amount, eur_in_gbp.amount);
 | 
			
		||||
    } else {
 | 
			
		||||
        println!("Could not convert EUR to GBP");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test product price conversion
 | 
			
		||||
    let retrieved_product2 = db.get_product(2)?;
 | 
			
		||||
    
 | 
			
		||||
    if let Some(price_in_eur) = retrieved_product2.cost_in_currency("EUR") {
 | 
			
		||||
        println!("Product '{}' price: ${} USD = {} EUR", 
 | 
			
		||||
            retrieved_product2.name,
 | 
			
		||||
            retrieved_product2.price.amount,
 | 
			
		||||
            price_in_eur.amount
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    println!("\n7. Deleting Objects");
 | 
			
		||||
    println!("------------------");
 | 
			
		||||
 | 
			
		||||
    // Delete a product
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use crate::db::db::DB;
 | 
			
		||||
use crate::db::base::{SledDBResult, SledModel};
 | 
			
		||||
use crate::impl_model_methods;
 | 
			
		||||
use crate::models::biz::{Product, Sale, Currency};
 | 
			
		||||
use crate::models::biz::{Product, Sale, Currency, ExchangeRate};
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Product
 | 
			
		||||
impl_model_methods!(Product, product, products);
 | 
			
		||||
@@ -10,4 +10,7 @@ impl_model_methods!(Product, product, products);
 | 
			
		||||
impl_model_methods!(Sale, sale, sales);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for Currency
 | 
			
		||||
impl_model_methods!(Currency, currency, currencies);
 | 
			
		||||
impl_model_methods!(Currency, currency, currencies);
 | 
			
		||||
 | 
			
		||||
// Implement model-specific methods for ExchangeRate
 | 
			
		||||
impl_model_methods!(ExchangeRate, exchange_rate, exchange_rates);
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
use chrono::{DateTime, Utc, Duration};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
 | 
			
		||||
use crate::models::biz::exchange_rate::EXCHANGE_RATE_SERVICE;
 | 
			
		||||
 | 
			
		||||
/// Currency represents a monetary value with amount and currency code
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
@@ -17,6 +18,26 @@ impl Currency {
 | 
			
		||||
            currency_code,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Convert the currency to USD
 | 
			
		||||
    pub fn to_usd(&self) -> Option<Currency> {
 | 
			
		||||
        if self.currency_code == "USD" {
 | 
			
		||||
            return Some(self.clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        EXCHANGE_RATE_SERVICE.convert(self.amount, &self.currency_code, "USD")
 | 
			
		||||
            .map(|amount| Currency::new(amount, "USD".to_string()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Convert the currency to another currency
 | 
			
		||||
    pub fn to_currency(&self, target_currency: &str) -> Option<Currency> {
 | 
			
		||||
        if self.currency_code == target_currency {
 | 
			
		||||
            return Some(self.clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        EXCHANGE_RATE_SERVICE.convert(self.amount, &self.currency_code, target_currency)
 | 
			
		||||
            .map(|amount| Currency::new(amount, target_currency.to_string()))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Currency
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										167
									
								
								herodb/src/models/biz/exchange_rate.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								herodb/src/models/biz/exchange_rate.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::base::{SledModel, Storable};
 | 
			
		||||
 | 
			
		||||
/// ExchangeRate represents an exchange rate between two currencies
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct ExchangeRate {
 | 
			
		||||
    pub base_currency: String,
 | 
			
		||||
    pub target_currency: String,
 | 
			
		||||
    pub rate: f64,
 | 
			
		||||
    pub timestamp: DateTime<Utc>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ExchangeRate {
 | 
			
		||||
    /// Create a new exchange rate
 | 
			
		||||
    pub fn new(base_currency: String, target_currency: String, rate: f64) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            base_currency,
 | 
			
		||||
            target_currency,
 | 
			
		||||
            rate,
 | 
			
		||||
            timestamp: Utc::now(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for ExchangeRate
 | 
			
		||||
pub struct ExchangeRateBuilder {
 | 
			
		||||
    base_currency: Option<String>,
 | 
			
		||||
    target_currency: Option<String>,
 | 
			
		||||
    rate: Option<f64>,
 | 
			
		||||
    timestamp: Option<DateTime<Utc>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ExchangeRateBuilder {
 | 
			
		||||
    /// Create a new ExchangeRateBuilder with all fields set to None
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            base_currency: None,
 | 
			
		||||
            target_currency: None,
 | 
			
		||||
            rate: None,
 | 
			
		||||
            timestamp: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the base currency
 | 
			
		||||
    pub fn base_currency<S: Into<String>>(mut self, base_currency: S) -> Self {
 | 
			
		||||
        self.base_currency = Some(base_currency.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the target currency
 | 
			
		||||
    pub fn target_currency<S: Into<String>>(mut self, target_currency: S) -> Self {
 | 
			
		||||
        self.target_currency = Some(target_currency.into());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the rate
 | 
			
		||||
    pub fn rate(mut self, rate: f64) -> Self {
 | 
			
		||||
        self.rate = Some(rate);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the timestamp
 | 
			
		||||
    pub fn timestamp(mut self, timestamp: DateTime<Utc>) -> Self {
 | 
			
		||||
        self.timestamp = Some(timestamp);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the ExchangeRate object
 | 
			
		||||
    pub fn build(self) -> Result<ExchangeRate, &'static str> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        Ok(ExchangeRate {
 | 
			
		||||
            base_currency: self.base_currency.ok_or("base_currency is required")?,
 | 
			
		||||
            target_currency: self.target_currency.ok_or("target_currency is required")?,
 | 
			
		||||
            rate: self.rate.ok_or("rate is required")?,
 | 
			
		||||
            timestamp: self.timestamp.unwrap_or(now),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Implement Storable trait (provides default dump/load)
 | 
			
		||||
impl Storable for ExchangeRate {}
 | 
			
		||||
 | 
			
		||||
// Implement SledModel trait
 | 
			
		||||
impl SledModel for ExchangeRate {
 | 
			
		||||
    fn get_id(&self) -> String {
 | 
			
		||||
        format!("{}_{}", self.base_currency, self.target_currency)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn db_prefix() -> &'static str {
 | 
			
		||||
        "exchange_rate"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ExchangeRateService provides methods to get and set exchange rates
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct ExchangeRateService {
 | 
			
		||||
    rates: Arc<Mutex<HashMap<String, ExchangeRate>>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ExchangeRateService {
 | 
			
		||||
    /// Create a new exchange rate service
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            rates: Arc::new(Mutex::new(HashMap::new())),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set an exchange rate
 | 
			
		||||
    pub fn set_rate(&self, exchange_rate: ExchangeRate) {
 | 
			
		||||
        let key = format!("{}_{}", exchange_rate.base_currency, exchange_rate.target_currency);
 | 
			
		||||
        let mut rates = self.rates.lock().unwrap();
 | 
			
		||||
        rates.insert(key, exchange_rate);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get an exchange rate
 | 
			
		||||
    pub fn get_rate(&self, base_currency: &str, target_currency: &str) -> Option<ExchangeRate> {
 | 
			
		||||
        let key = format!("{}_{}", base_currency, target_currency);
 | 
			
		||||
        let rates = self.rates.lock().unwrap();
 | 
			
		||||
        rates.get(&key).cloned()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Convert an amount from one currency to another
 | 
			
		||||
    pub fn convert(&self, amount: f64, from_currency: &str, to_currency: &str) -> Option<f64> {
 | 
			
		||||
        // If the currencies are the same, return the amount
 | 
			
		||||
        if from_currency == to_currency {
 | 
			
		||||
            return Some(amount);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Try to get the direct exchange rate
 | 
			
		||||
        if let Some(rate) = self.get_rate(from_currency, to_currency) {
 | 
			
		||||
            return Some(amount * rate.rate);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Try to get the inverse exchange rate
 | 
			
		||||
        if let Some(rate) = self.get_rate(to_currency, from_currency) {
 | 
			
		||||
            return Some(amount / rate.rate);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Try to convert via USD
 | 
			
		||||
        if from_currency != "USD" && to_currency != "USD" {
 | 
			
		||||
            if let Some(from_to_usd) = self.convert(amount, from_currency, "USD") {
 | 
			
		||||
                return self.convert(from_to_usd, "USD", to_currency);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Create a global instance of the exchange rate service
 | 
			
		||||
lazy_static::lazy_static! {
 | 
			
		||||
    pub static ref EXCHANGE_RATE_SERVICE: ExchangeRateService = {
 | 
			
		||||
        let service = ExchangeRateService::new();
 | 
			
		||||
        
 | 
			
		||||
        // Set some default exchange rates
 | 
			
		||||
        service.set_rate(ExchangeRate::new("USD".to_string(), "EUR".to_string(), 0.85));
 | 
			
		||||
        service.set_rate(ExchangeRate::new("USD".to_string(), "GBP".to_string(), 0.75));
 | 
			
		||||
        service.set_rate(ExchangeRate::new("USD".to_string(), "JPY".to_string(), 110.0));
 | 
			
		||||
        service.set_rate(ExchangeRate::new("USD".to_string(), "CAD".to_string(), 1.25));
 | 
			
		||||
        service.set_rate(ExchangeRate::new("USD".to_string(), "AUD".to_string(), 1.35));
 | 
			
		||||
        
 | 
			
		||||
        service
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,16 @@
 | 
			
		||||
pub mod currency;
 | 
			
		||||
pub mod product;
 | 
			
		||||
pub mod sale;
 | 
			
		||||
pub mod exchange_rate;
 | 
			
		||||
 | 
			
		||||
// Re-export all model types for convenience
 | 
			
		||||
pub use product::{Product, ProductComponent, ProductType, ProductStatus};
 | 
			
		||||
pub use sale::{Sale, SaleItem, SaleStatus};
 | 
			
		||||
pub use currency::Currency;
 | 
			
		||||
pub use exchange_rate::{ExchangeRate, ExchangeRateService, EXCHANGE_RATE_SERVICE};
 | 
			
		||||
 | 
			
		||||
// Re-export builder types
 | 
			
		||||
pub use product::{ProductBuilder, ProductComponentBuilder};
 | 
			
		||||
pub use sale::{SaleBuilder, SaleItemBuilder};
 | 
			
		||||
pub use currency::CurrencyBuilder;
 | 
			
		||||
pub use currency::CurrencyBuilder;
 | 
			
		||||
pub use exchange_rate::ExchangeRateBuilder;
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use chrono::{DateTime, Utc, Duration};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
 | 
			
		||||
 | 
			
		||||
use crate::models::biz::exchange_rate::EXCHANGE_RATE_SERVICE;
 | 
			
		||||
 | 
			
		||||
/// ProductType represents the type of a product
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
 | 
			
		||||
@@ -13,6 +13,10 @@ pub enum ProductType {
 | 
			
		||||
/// ProductStatus represents the status of a product
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
 | 
			
		||||
pub enum ProductStatus {
 | 
			
		||||
    Active,
 | 
			
		||||
    Error,
 | 
			
		||||
    EndOfLife,
 | 
			
		||||
    Paused,
 | 
			
		||||
    Available,
 | 
			
		||||
    Unavailable,
 | 
			
		||||
}
 | 
			
		||||
@@ -26,6 +30,8 @@ pub struct ProductComponent {
 | 
			
		||||
    pub quantity: i32,
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
    pub energy_usage: f64, // Energy usage in watts
 | 
			
		||||
    pub cost: Currency,    // Cost of the component
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ProductComponent {
 | 
			
		||||
@@ -39,8 +45,20 @@ impl ProductComponent {
 | 
			
		||||
            quantity,
 | 
			
		||||
            created_at: now,
 | 
			
		||||
            updated_at: now,
 | 
			
		||||
            energy_usage: 0.0,
 | 
			
		||||
            cost: Currency::new(0.0, "USD".to_string()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the total energy usage for this component (energy_usage * quantity)
 | 
			
		||||
    pub fn total_energy_usage(&self) -> f64 {
 | 
			
		||||
        self.energy_usage * self.quantity as f64
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the total cost for this component (cost * quantity)
 | 
			
		||||
    pub fn total_cost(&self) -> Currency {
 | 
			
		||||
        Currency::new(self.cost.amount * self.quantity as f64, self.cost.currency_code.clone())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for ProductComponent
 | 
			
		||||
@@ -51,6 +69,8 @@ pub struct ProductComponentBuilder {
 | 
			
		||||
    quantity: Option<i32>,
 | 
			
		||||
    created_at: Option<DateTime<Utc>>,
 | 
			
		||||
    updated_at: Option<DateTime<Utc>>,
 | 
			
		||||
    energy_usage: Option<f64>,
 | 
			
		||||
    cost: Option<Currency>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ProductComponentBuilder {
 | 
			
		||||
@@ -63,6 +83,8 @@ impl ProductComponentBuilder {
 | 
			
		||||
            quantity: None,
 | 
			
		||||
            created_at: None,
 | 
			
		||||
            updated_at: None,
 | 
			
		||||
            energy_usage: None,
 | 
			
		||||
            cost: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -102,6 +124,18 @@ impl ProductComponentBuilder {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the energy usage in watts
 | 
			
		||||
    pub fn energy_usage(mut self, energy_usage: f64) -> Self {
 | 
			
		||||
        self.energy_usage = Some(energy_usage);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the cost
 | 
			
		||||
    pub fn cost(mut self, cost: Currency) -> Self {
 | 
			
		||||
        self.cost = Some(cost);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the ProductComponent object
 | 
			
		||||
    pub fn build(self) -> Result<ProductComponent, &'static str> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
@@ -112,6 +146,8 @@ impl ProductComponentBuilder {
 | 
			
		||||
            quantity: self.quantity.ok_or("quantity is required")?,
 | 
			
		||||
            created_at: self.created_at.unwrap_or(now),
 | 
			
		||||
            updated_at: self.updated_at.unwrap_or(now),
 | 
			
		||||
            energy_usage: self.energy_usage.unwrap_or(0.0),
 | 
			
		||||
            cost: self.cost.unwrap_or_else(|| Currency::new(0.0, "USD".to_string())),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -188,13 +224,60 @@ impl Product {
 | 
			
		||||
    
 | 
			
		||||
    /// Check if the product is available for purchase
 | 
			
		||||
    pub fn is_purchasable(&self) -> bool {
 | 
			
		||||
        self.status == ProductStatus::Available && Utc::now() <= self.purchase_till
 | 
			
		||||
        (self.status == ProductStatus::Available || self.status == ProductStatus::Active) 
 | 
			
		||||
            && Utc::now() <= self.purchase_till
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Check if the product is still active (for services)
 | 
			
		||||
    pub fn is_active(&self) -> bool {
 | 
			
		||||
        Utc::now() <= self.active_till
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculate the total cost in the specified currency
 | 
			
		||||
    pub fn cost_in_currency(&self, currency_code: &str) -> Option<Currency> {
 | 
			
		||||
        // If the price is already in the requested currency, return it
 | 
			
		||||
        if self.price.currency_code == currency_code {
 | 
			
		||||
            return Some(self.price.clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Convert the price to the requested currency
 | 
			
		||||
        self.price.to_currency(currency_code)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculate the total cost in USD
 | 
			
		||||
    pub fn cost_in_usd(&self) -> Option<Currency> {
 | 
			
		||||
        self.cost_in_currency("USD")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculate the total energy usage of the product (sum of all components)
 | 
			
		||||
    pub fn total_energy_usage(&self) -> f64 {
 | 
			
		||||
        self.components.iter().map(|c| c.total_energy_usage()).sum()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculate the total cost of all components
 | 
			
		||||
    pub fn components_cost(&self, currency_code: &str) -> Option<Currency> {
 | 
			
		||||
        if self.components.is_empty() {
 | 
			
		||||
            return Some(Currency::new(0.0, currency_code.to_string()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Sum up the costs of all components, converting to the requested currency
 | 
			
		||||
        let mut total = 0.0;
 | 
			
		||||
        for component in &self.components {
 | 
			
		||||
            let component_cost = component.total_cost();
 | 
			
		||||
            if let Some(converted_cost) = component_cost.to_currency(currency_code) {
 | 
			
		||||
                total += converted_cost.amount;
 | 
			
		||||
            } else {
 | 
			
		||||
                return None; // Conversion failed
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Some(Currency::new(total, currency_code.to_string()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculate the total cost of all components in USD
 | 
			
		||||
    pub fn components_cost_in_usd(&self) -> Option<Currency> {
 | 
			
		||||
        self.components_cost("USD")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Product
 | 
			
		||||
@@ -355,4 +438,4 @@ impl SledModel for Product {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Import Currency from the currency module
 | 
			
		||||
use crate::models::biz::Currency;
 | 
			
		||||
use crate::models::biz::Currency;
 | 
			
		||||
		Reference in New Issue
	
	Block a user