...
This commit is contained in:
parent
04233e6f1a
commit
be3ad84c7d
7
herodb/Cargo.lock
generated
7
herodb/Cargo.lock
generated
@ -658,6 +658,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"brotli",
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"paste",
|
||||
"poem",
|
||||
"poem-openapi",
|
||||
@ -831,6 +832,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
|
@ -21,6 +21,7 @@ poem-openapi = { version = "2.0.11", features = ["swagger-ui"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
rhai = "1.15.1"
|
||||
paste = "1.0"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[[example]]
|
||||
name = "rhai_demo"
|
||||
|
@ -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;
|
4
herodb/tmp/dbexample2/exchange_rate/conf
Normal file
4
herodb/tmp/dbexample2/exchange_rate/conf
Normal file
@ -0,0 +1,4 @@
|
||||
segment_size: 524288
|
||||
use_compression: false
|
||||
version: 0.34
|
||||
vQÁ
|
BIN
herodb/tmp/dbexample2/exchange_rate/db
Normal file
BIN
herodb/tmp/dbexample2/exchange_rate/db
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user