...
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",
|
"bincode",
|
||||||
"brotli",
|
"brotli",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"lazy_static",
|
||||||
"paste",
|
"paste",
|
||||||
"poem",
|
"poem",
|
||||||
"poem-openapi",
|
"poem-openapi",
|
||||||
@ -831,6 +832,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.171"
|
version = "0.2.171"
|
||||||
|
@ -21,6 +21,7 @@ poem-openapi = { version = "2.0.11", features = ["swagger-ui"] }
|
|||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
rhai = "1.15.1"
|
rhai = "1.15.1"
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "rhai_demo"
|
name = "rhai_demo"
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use chrono::{DateTime, Utc, Duration};
|
use chrono::{Utc, Duration};
|
||||||
use herodb::db::{DB, DBBuilder};
|
use herodb::db::DBBuilder;
|
||||||
use herodb::models::biz::{
|
use herodb::models::biz::{
|
||||||
Currency, CurrencyBuilder,
|
Currency, CurrencyBuilder,
|
||||||
Product, ProductBuilder, ProductComponent, ProductComponentBuilder,
|
Product, ProductBuilder, ProductComponentBuilder,
|
||||||
ProductType, ProductStatus,
|
ProductType, ProductStatus,
|
||||||
Sale, SaleBuilder, SaleItem, SaleItemBuilder, SaleStatus
|
Sale, SaleBuilder, SaleItemBuilder, SaleStatus,
|
||||||
|
ExchangeRate, ExchangeRateBuilder, EXCHANGE_RATE_SERVICE
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@ -26,6 +27,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.register_model::<Product>()
|
.register_model::<Product>()
|
||||||
.register_model::<Currency>()
|
.register_model::<Currency>()
|
||||||
.register_model::<Sale>()
|
.register_model::<Sale>()
|
||||||
|
.register_model::<ExchangeRate>()
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
println!("\n1. Creating Products with Builder Pattern");
|
println!("\n1. Creating Products with Builder Pattern");
|
||||||
@ -39,14 +41,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
// Insert the currency
|
// Insert the currency
|
||||||
db.insert_currency(&usd)?;
|
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()
|
let component1 = ProductComponentBuilder::new()
|
||||||
.id(101)
|
.id(101)
|
||||||
.name("Basic Support")
|
.name("Basic Support")
|
||||||
.description("24/7 email support")
|
.description("24/7 email support")
|
||||||
.quantity(1)
|
.quantity(1)
|
||||||
|
.energy_usage(5.0) // 5 watts
|
||||||
|
.cost(CurrencyBuilder::new()
|
||||||
|
.amount(5.0)
|
||||||
|
.currency_code("USD")
|
||||||
|
.build()?)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let component2 = ProductComponentBuilder::new()
|
let component2 = ProductComponentBuilder::new()
|
||||||
@ -54,6 +61,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.name("Premium Support")
|
.name("Premium Support")
|
||||||
.description("24/7 phone and email support")
|
.description("24/7 phone and email support")
|
||||||
.quantity(1)
|
.quantity(1)
|
||||||
|
.energy_usage(10.0) // 10 watts
|
||||||
|
.cost(CurrencyBuilder::new()
|
||||||
|
.amount(15.0)
|
||||||
|
.currency_code("USD")
|
||||||
|
.build()?)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
// Create products using the builder
|
// Create products using the builder
|
||||||
@ -67,7 +79,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.build()?)
|
.build()?)
|
||||||
.type_(ProductType::Service)
|
.type_(ProductType::Service)
|
||||||
.category("Subscription")
|
.category("Subscription")
|
||||||
.status(ProductStatus::Available)
|
.status(ProductStatus::Active)
|
||||||
.max_amount(1000)
|
.max_amount(1000)
|
||||||
.validity_days(30)
|
.validity_days(30)
|
||||||
.add_component(component1)
|
.add_component(component1)
|
||||||
@ -83,7 +95,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.build()?)
|
.build()?)
|
||||||
.type_(ProductType::Service)
|
.type_(ProductType::Service)
|
||||||
.category("Subscription")
|
.category("Subscription")
|
||||||
.status(ProductStatus::Available)
|
.status(ProductStatus::Active)
|
||||||
.max_amount(500)
|
.max_amount(500)
|
||||||
.validity_days(30)
|
.validity_days(30)
|
||||||
.add_component(component2)
|
.add_component(component2)
|
||||||
@ -93,18 +105,32 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
db.insert_product(&product1)?;
|
db.insert_product(&product1)?;
|
||||||
db.insert_product(&product2)?;
|
db.insert_product(&product2)?;
|
||||||
|
|
||||||
println!("Product created: {} (${:.2})", product1.name, product1.price.amount);
|
println!("Product created: {} (${}) USD", product1.name, product1.price.amount);
|
||||||
println!("Product created: {} (${:.2})", product2.name, product2.price.amount);
|
println!("Product created: {} (${}) USD", product2.name, product2.price.amount);
|
||||||
|
|
||||||
println!("\n2. Retrieving Products");
|
println!("\n2. Retrieving Products");
|
||||||
println!("--------------------");
|
println!("--------------------");
|
||||||
|
|
||||||
// Retrieve products using model-specific methods
|
// Retrieve products using model-specific methods
|
||||||
let retrieved_product1 = db.get_product(1)?;
|
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:");
|
println!("Components:");
|
||||||
for component in &retrieved_product1.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");
|
println!("\n3. Listing All Products");
|
||||||
@ -114,7 +140,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let all_products = db.list_products()?;
|
let all_products = db.list_products()?;
|
||||||
println!("Found {} products:", all_products.len());
|
println!("Found {} products:", all_products.len());
|
||||||
for product in all_products {
|
for product in all_products {
|
||||||
println!(" - {} (${:.2}, {})",
|
println!(" - {} (${} USD, {})",
|
||||||
product.name,
|
product.name,
|
||||||
product.price.amount,
|
product.price.amount,
|
||||||
if product.is_purchasable() { "Available" } else { "Unavailable" }
|
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
|
// Insert the sale using model-specific methods
|
||||||
db.insert_sale(&sale)?;
|
db.insert_sale(&sale)?;
|
||||||
println!("Sale created: #{} for {} (${:.2})",
|
println!("Sale created: #{} for {} (${} USD)",
|
||||||
sale.id,
|
sale.id,
|
||||||
sale.buyer_name,
|
sale.buyer_name,
|
||||||
sale.total_amount.amount
|
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!("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!("------------------");
|
println!("------------------");
|
||||||
|
|
||||||
// Delete a product
|
// Delete a product
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::db::db::DB;
|
use crate::db::db::DB;
|
||||||
use crate::db::base::{SledDBResult, SledModel};
|
use crate::db::base::{SledDBResult, SledModel};
|
||||||
use crate::impl_model_methods;
|
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
|
// Implement model-specific methods for Product
|
||||||
impl_model_methods!(Product, product, products);
|
impl_model_methods!(Product, product, products);
|
||||||
@ -10,4 +10,7 @@ impl_model_methods!(Product, product, products);
|
|||||||
impl_model_methods!(Sale, sale, sales);
|
impl_model_methods!(Sale, sale, sales);
|
||||||
|
|
||||||
// Implement model-specific methods for Currency
|
// 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 chrono::{DateTime, Utc, Duration};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
|
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
|
/// Currency represents a monetary value with amount and currency code
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -17,6 +18,26 @@ impl Currency {
|
|||||||
currency_code,
|
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
|
/// 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 currency;
|
||||||
pub mod product;
|
pub mod product;
|
||||||
pub mod sale;
|
pub mod sale;
|
||||||
|
pub mod exchange_rate;
|
||||||
|
|
||||||
// Re-export all model types for convenience
|
// Re-export all model types for convenience
|
||||||
pub use product::{Product, ProductComponent, ProductType, ProductStatus};
|
pub use product::{Product, ProductComponent, ProductType, ProductStatus};
|
||||||
pub use sale::{Sale, SaleItem, SaleStatus};
|
pub use sale::{Sale, SaleItem, SaleStatus};
|
||||||
pub use currency::Currency;
|
pub use currency::Currency;
|
||||||
|
pub use exchange_rate::{ExchangeRate, ExchangeRateService, EXCHANGE_RATE_SERVICE};
|
||||||
|
|
||||||
// Re-export builder types
|
// Re-export builder types
|
||||||
pub use product::{ProductBuilder, ProductComponentBuilder};
|
pub use product::{ProductBuilder, ProductComponentBuilder};
|
||||||
pub use sale::{SaleBuilder, SaleItemBuilder};
|
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 chrono::{DateTime, Utc, Duration};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
|
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
|
/// ProductType represents the type of a product
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@ -13,6 +13,10 @@ pub enum ProductType {
|
|||||||
/// ProductStatus represents the status of a product
|
/// ProductStatus represents the status of a product
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum ProductStatus {
|
pub enum ProductStatus {
|
||||||
|
Active,
|
||||||
|
Error,
|
||||||
|
EndOfLife,
|
||||||
|
Paused,
|
||||||
Available,
|
Available,
|
||||||
Unavailable,
|
Unavailable,
|
||||||
}
|
}
|
||||||
@ -26,6 +30,8 @@ pub struct ProductComponent {
|
|||||||
pub quantity: i32,
|
pub quantity: i32,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_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 {
|
impl ProductComponent {
|
||||||
@ -39,8 +45,20 @@ impl ProductComponent {
|
|||||||
quantity,
|
quantity,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
updated_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
|
/// Builder for ProductComponent
|
||||||
@ -51,6 +69,8 @@ pub struct ProductComponentBuilder {
|
|||||||
quantity: Option<i32>,
|
quantity: Option<i32>,
|
||||||
created_at: Option<DateTime<Utc>>,
|
created_at: Option<DateTime<Utc>>,
|
||||||
updated_at: Option<DateTime<Utc>>,
|
updated_at: Option<DateTime<Utc>>,
|
||||||
|
energy_usage: Option<f64>,
|
||||||
|
cost: Option<Currency>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProductComponentBuilder {
|
impl ProductComponentBuilder {
|
||||||
@ -63,6 +83,8 @@ impl ProductComponentBuilder {
|
|||||||
quantity: None,
|
quantity: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
updated_at: None,
|
updated_at: None,
|
||||||
|
energy_usage: None,
|
||||||
|
cost: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +124,18 @@ impl ProductComponentBuilder {
|
|||||||
self
|
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
|
/// Build the ProductComponent object
|
||||||
pub fn build(self) -> Result<ProductComponent, &'static str> {
|
pub fn build(self) -> Result<ProductComponent, &'static str> {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
@ -112,6 +146,8 @@ impl ProductComponentBuilder {
|
|||||||
quantity: self.quantity.ok_or("quantity is required")?,
|
quantity: self.quantity.ok_or("quantity is required")?,
|
||||||
created_at: self.created_at.unwrap_or(now),
|
created_at: self.created_at.unwrap_or(now),
|
||||||
updated_at: self.updated_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
|
/// Check if the product is available for purchase
|
||||||
pub fn is_purchasable(&self) -> bool {
|
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)
|
/// Check if the product is still active (for services)
|
||||||
pub fn is_active(&self) -> bool {
|
pub fn is_active(&self) -> bool {
|
||||||
Utc::now() <= self.active_till
|
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
|
/// Builder for Product
|
||||||
@ -355,4 +438,4 @@ impl SledModel for Product {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Import Currency from the currency module
|
// 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