use crate::pricing::Money;
use chrono::{DateTime, Timelike, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ComplexPricingModel {
Tiered(TieredPricing),
VolumeBased(VolumePricing),
Subscription(SubscriptionPricing),
Dynamic(DynamicPricing),
Bundle(BundlePricing),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TieredPricing {
pub tiers: Vec<PricingTier>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricingTier {
pub min_quantity: u32,
pub max_quantity: Option<u32>,
pub price: Money,
pub price_per_unit: Option<Money>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolumePricing {
pub base_price: Money,
pub volume_discounts: Vec<VolumeDiscount>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolumeDiscount {
pub min_volume: u32,
pub discount_percentage: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscriptionPricing {
pub recurring_price: Money,
pub billing_cycle: BillingCycle,
pub setup_fee: Option<Money>,
pub trial_period_days: Option<u32>,
pub cancellation_policy: CancellationPolicy,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BillingCycle {
Monthly,
Quarterly,
SemiAnnual,
Annual,
Weekly,
Daily,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CancellationPolicy {
Immediate,
EndOfPeriod,
ProRated,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DynamicPricing {
pub base_price: Money,
pub factors: Vec<PricingFactor>,
pub adjustment_rules: Vec<PriceAdjustmentRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricingFactor {
pub factor_type: FactorType,
pub weight: f64,
pub adjustment_percentage: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FactorType {
Demand,
TimeOfDay,
DayOfWeek,
Season,
Inventory,
Competition,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceAdjustmentRule {
pub condition: String, pub adjustment_type: AdjustmentType,
pub value: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AdjustmentType {
Percentage,
FixedAmount,
Multiplier,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BundlePricing {
pub bundle_id: Uuid,
pub component_prices: Vec<ComponentPrice>,
pub bundle_discount: f64,
pub minimum_components: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentPrice {
pub product_offering_id: Uuid,
pub price: Money,
pub required: bool,
}
pub fn calculate_complex_price(
model: &ComplexPricingModel,
quantity: u32,
context: &PricingContext,
) -> Money {
match model {
ComplexPricingModel::Tiered(tiered) => calculate_tiered_price(tiered, quantity),
ComplexPricingModel::VolumeBased(volume) => calculate_volume_price(volume, quantity),
ComplexPricingModel::Subscription(sub) => calculate_subscription_price(sub),
ComplexPricingModel::Dynamic(dynamic) => calculate_dynamic_price(dynamic, context),
ComplexPricingModel::Bundle(bundle) => calculate_bundle_price(bundle, context),
}
}
#[derive(Debug, Clone)]
pub struct PricingContext {
pub quantity: u32,
pub customer_id: Option<Uuid>,
pub timestamp: DateTime<Utc>,
pub demand_level: Option<f64>,
pub inventory_level: Option<f64>,
pub existing_subscriptions: Vec<Uuid>,
}
fn calculate_tiered_price(tiered: &TieredPricing, quantity: u32) -> Money {
for tier in &tiered.tiers {
if quantity >= tier.min_quantity {
if let Some(max) = tier.max_quantity {
if quantity <= max {
return apply_tier_price(tier, quantity);
}
} else {
return apply_tier_price(tier, quantity);
}
}
}
tiered
.tiers
.first()
.map(|t| apply_tier_price(t, quantity))
.unwrap_or_else(|| Money {
value: 0.0,
unit: "USD".to_string(),
})
}
fn apply_tier_price(tier: &PricingTier, quantity: u32) -> Money {
if let Some(ref per_unit) = tier.price_per_unit {
Money {
value: per_unit.value * quantity as f64,
unit: per_unit.unit.clone(),
}
} else {
tier.price.clone()
}
}
fn calculate_volume_price(volume: &VolumePricing, quantity: u32) -> Money {
let mut final_price = volume.base_price.value;
let mut best_discount: f64 = 0.0;
for discount in &volume.volume_discounts {
if quantity >= discount.min_volume {
best_discount = best_discount.max(discount.discount_percentage);
}
}
final_price *= 1.0 - (best_discount / 100.0);
Money {
value: final_price.max(0.0),
unit: volume.base_price.unit.clone(),
}
}
fn calculate_subscription_price(sub: &SubscriptionPricing) -> Money {
sub.recurring_price.clone()
}
fn calculate_dynamic_price(dynamic: &DynamicPricing, context: &PricingContext) -> Money {
let mut price = dynamic.base_price.value;
for factor in &dynamic.factors {
let adjustment = match factor.factor_type {
FactorType::Demand => context
.demand_level
.map(|d| d * factor.weight * factor.adjustment_percentage / 100.0),
FactorType::TimeOfDay => {
let hour = context.timestamp.hour();
if (9..=17).contains(&hour) {
Some(factor.adjustment_percentage / 100.0)
} else {
Some(-factor.adjustment_percentage / 100.0)
}
}
FactorType::Inventory => context.inventory_level.map(|inv| {
if inv < 0.2 {
factor.adjustment_percentage / 100.0
} else {
-factor.adjustment_percentage / 100.0
}
}),
_ => None,
};
if let Some(adj) = adjustment {
price *= 1.0 + adj;
}
}
Money {
value: price.max(0.0),
unit: dynamic.base_price.unit.clone(),
}
}
fn calculate_bundle_price(bundle: &BundlePricing, _context: &PricingContext) -> Money {
let total: f64 = bundle
.component_prices
.iter()
.map(|cp| cp.price.value)
.sum();
let discounted = total * (1.0 - bundle.bundle_discount / 100.0);
Money {
value: discounted.max(0.0),
unit: bundle
.component_prices
.first()
.map(|cp| cp.price.unit.clone())
.unwrap_or_else(|| "USD".to_string()),
}
}