use serde::{Deserialize, Serialize};
use crate::model::adjustment::{AdjustmentKind, AppliedAdjustment, PriceAdjustment};
use crate::model::currency::{Currency, CurrencyConverter, CurrencyConverterError}; use crate::model::markup::MarkupType;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use crate::PricingError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricingDetail {
pub buy_price: Decimal,
pub sell_price: Decimal, pub buy_currency: Currency,
pub sell_currency: Currency,
pub markup: Option<MarkupType>,
pub markup_value_in_buy_currency: Option<Decimal>,
pub markup_value_in_sell_currency: Option<Decimal>,
pub converted_buy_price: Option<Decimal>,
pub buy_currency_rate: Option<Decimal>,
pub sell_currency_rate: Option<Decimal>,
pub exchange_rate: Option<Decimal>,
pub applied_adjustments: Vec<AppliedAdjustment>,
}
impl PricingDetail {
pub fn new(buy_price: Decimal, buy_currency: Currency, sell_currency: Currency) -> Self {
Self {
buy_price,
sell_price: dec!(0.0), buy_currency,
sell_currency,
markup: None,
markup_value_in_buy_currency: None,
markup_value_in_sell_currency: None,
converted_buy_price: None,
buy_currency_rate: None,
sell_currency_rate: None,
exchange_rate: None,
applied_adjustments: vec![],
}
}
pub fn apply_markup(&mut self, converter: &CurrencyConverter) -> Result<(), PricingError> {
let buy_rate = converter.get_exchange_rate(&self.buy_currency)
.ok_or_else(|| CurrencyConverterError::RateNotFound(self.buy_currency.get_code().to_string()))
.map_err(PricingError::RateCalculationFailed)?;
let sell_rate = converter.get_exchange_rate(&self.sell_currency)
.ok_or_else(|| CurrencyConverterError::RateNotFound(self.sell_currency.get_code().to_string()))
.map_err(PricingError::RateCalculationFailed)?;
if buy_rate.is_zero() {
return Err(PricingError::RateCalculationFailed(CurrencyConverterError::DivisionByZero));
}
let exchange_rate = sell_rate / buy_rate;
self.buy_currency_rate = Some(buy_rate);
self.sell_currency_rate = Some(sell_rate);
self.exchange_rate = Some(exchange_rate);
let markup_in_buy = match &self.markup {
Some(MarkupType::Amount { value, currency }) => {
converter.convert(*value, currency, &self.buy_currency)
.map_err(PricingError::RateCalculationFailed)?
}
Some(MarkupType::Percentage(pct)) => {
self.buy_price * (*pct / dec!(100.0))
}
Some(MarkupType::Commission(pct)) => {
if *pct >= dec!(100.0) {
return Err(PricingError::InvalidMarkupCalculation(
format!("Commission percentage ({}) must be less than 100.", pct)
));
}
self.buy_price * (*pct / (dec!(100.0) - pct))
}
None => dec!(0.0),
};
self.markup_value_in_buy_currency = Some(markup_in_buy);
let sell_base = self.buy_price + markup_in_buy;
self.converted_buy_price = Some(sell_base);
let initial_sell_price = sell_base * exchange_rate;
self.markup_value_in_sell_currency = Some(markup_in_buy * exchange_rate);
self.sell_price = initial_sell_price;
Ok(())
}
pub fn apply_adjustments(
&mut self,
adjustments: &[PriceAdjustment],
converter: &CurrencyConverter,
) -> Result<(), PricingError> {
let mut current_sell_price = self.sell_price;
self.applied_adjustments.clear();
for adj in adjustments {
let applied = match adj {
PriceAdjustment::Tax { name, percentage } => {
let amt = current_sell_price * (*percentage / dec!(100.0));
current_sell_price += amt;
AppliedAdjustment {
kind: AdjustmentKind::Tax,
name: name.clone(),
percentage: Some(*percentage),
original_currency: Some(self.sell_currency.clone()),
original_amount: None,
applied_amount: amt,
}
}
PriceAdjustment::Discount { name, percentage } => {
let amt = current_sell_price * (*percentage / dec!(100.0));
current_sell_price -= amt;
AppliedAdjustment {
kind: AdjustmentKind::Discount,
name: name.clone(),
percentage: Some(*percentage),
original_currency: Some(self.sell_currency.clone()),
original_amount: None,
applied_amount: -amt,
}
}
PriceAdjustment::Fixed { name, amount, currency } => {
let converted_amount_in_sell_currency = converter.convert(*amount, currency, &self.sell_currency)
.map_err(PricingError::AdjustmentFailed)?;
current_sell_price += converted_amount_in_sell_currency;
AppliedAdjustment {
kind: AdjustmentKind::Fixed,
name: name.clone(),
percentage: None,
original_currency: Some(currency.clone()),
original_amount: Some(*amount),
applied_amount: converted_amount_in_sell_currency,
}
}
};
self.applied_adjustments.push(applied);
}
self.sell_price = current_sell_price;
Ok(())
}
pub fn calculate_final_price(
&mut self,
converter: &CurrencyConverter,
adjustments: &[PriceAdjustment],
) -> Result<(), PricingError> {
self.apply_markup(converter)?;
self.apply_adjustments(adjustments, converter)?;
Ok(())
}
}