1use crate::pricing::Money;
6use chrono::{DateTime, Timelike, Utc};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub enum ComplexPricingModel {
13 Tiered(TieredPricing),
15 VolumeBased(VolumePricing),
17 Subscription(SubscriptionPricing),
19 Dynamic(DynamicPricing),
21 Bundle(BundlePricing),
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct TieredPricing {
28 pub tiers: Vec<PricingTier>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct PricingTier {
34 pub min_quantity: u32,
35 pub max_quantity: Option<u32>,
36 pub price: Money,
37 pub price_per_unit: Option<Money>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct VolumePricing {
43 pub base_price: Money,
44 pub volume_discounts: Vec<VolumeDiscount>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct VolumeDiscount {
50 pub min_volume: u32,
51 pub discount_percentage: f64,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct SubscriptionPricing {
57 pub recurring_price: Money,
58 pub billing_cycle: BillingCycle,
59 pub setup_fee: Option<Money>,
60 pub trial_period_days: Option<u32>,
61 pub cancellation_policy: CancellationPolicy,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
66#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
67pub enum BillingCycle {
68 Monthly,
69 Quarterly,
70 SemiAnnual,
71 Annual,
72 Weekly,
73 Daily,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
78#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
79pub enum CancellationPolicy {
80 Immediate,
81 EndOfPeriod,
82 ProRated,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct DynamicPricing {
88 pub base_price: Money,
89 pub factors: Vec<PricingFactor>,
90 pub adjustment_rules: Vec<PriceAdjustmentRule>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct PricingFactor {
96 pub factor_type: FactorType,
97 pub weight: f64,
98 pub adjustment_percentage: f64,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
103#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
104pub enum FactorType {
105 Demand,
106 TimeOfDay,
107 DayOfWeek,
108 Season,
109 Inventory,
110 Competition,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct PriceAdjustmentRule {
116 pub condition: String, pub adjustment_type: AdjustmentType,
118 pub value: f64,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
123#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
124pub enum AdjustmentType {
125 Percentage,
126 FixedAmount,
127 Multiplier,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct BundlePricing {
133 pub bundle_id: Uuid,
134 pub component_prices: Vec<ComponentPrice>,
135 pub bundle_discount: f64,
136 pub minimum_components: Option<u32>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ComponentPrice {
142 pub product_offering_id: Uuid,
143 pub price: Money,
144 pub required: bool,
145}
146
147pub fn calculate_complex_price(
149 model: &ComplexPricingModel,
150 quantity: u32,
151 context: &PricingContext,
152) -> Money {
153 match model {
154 ComplexPricingModel::Tiered(tiered) => calculate_tiered_price(tiered, quantity),
155 ComplexPricingModel::VolumeBased(volume) => calculate_volume_price(volume, quantity),
156 ComplexPricingModel::Subscription(sub) => calculate_subscription_price(sub),
157 ComplexPricingModel::Dynamic(dynamic) => calculate_dynamic_price(dynamic, context),
158 ComplexPricingModel::Bundle(bundle) => calculate_bundle_price(bundle, context),
159 }
160}
161
162#[derive(Debug, Clone)]
164pub struct PricingContext {
165 pub quantity: u32,
166 pub customer_id: Option<Uuid>,
167 pub timestamp: DateTime<Utc>,
168 pub demand_level: Option<f64>,
169 pub inventory_level: Option<f64>,
170 pub existing_subscriptions: Vec<Uuid>,
171}
172
173fn calculate_tiered_price(tiered: &TieredPricing, quantity: u32) -> Money {
174 for tier in &tiered.tiers {
175 if quantity >= tier.min_quantity {
176 if let Some(max) = tier.max_quantity {
177 if quantity <= max {
178 return apply_tier_price(tier, quantity);
179 }
180 } else {
181 return apply_tier_price(tier, quantity);
182 }
183 }
184 }
185 tiered
187 .tiers
188 .first()
189 .map(|t| apply_tier_price(t, quantity))
190 .unwrap_or_else(|| Money {
191 value: 0.0,
192 unit: "USD".to_string(),
193 })
194}
195
196fn apply_tier_price(tier: &PricingTier, quantity: u32) -> Money {
197 if let Some(ref per_unit) = tier.price_per_unit {
198 Money {
199 value: per_unit.value * quantity as f64,
200 unit: per_unit.unit.clone(),
201 }
202 } else {
203 tier.price.clone()
204 }
205}
206
207fn calculate_volume_price(volume: &VolumePricing, quantity: u32) -> Money {
208 let mut final_price = volume.base_price.value;
209 let mut best_discount: f64 = 0.0;
210
211 for discount in &volume.volume_discounts {
212 if quantity >= discount.min_volume {
213 best_discount = best_discount.max(discount.discount_percentage);
214 }
215 }
216
217 final_price *= 1.0 - (best_discount / 100.0);
218
219 Money {
220 value: final_price.max(0.0),
221 unit: volume.base_price.unit.clone(),
222 }
223}
224
225fn calculate_subscription_price(sub: &SubscriptionPricing) -> Money {
226 sub.recurring_price.clone()
227}
228
229fn calculate_dynamic_price(dynamic: &DynamicPricing, context: &PricingContext) -> Money {
230 let mut price = dynamic.base_price.value;
231
232 for factor in &dynamic.factors {
233 let adjustment = match factor.factor_type {
234 FactorType::Demand => context
235 .demand_level
236 .map(|d| d * factor.weight * factor.adjustment_percentage / 100.0),
237 FactorType::TimeOfDay => {
238 let hour = context.timestamp.hour();
239 if (9..=17).contains(&hour) {
240 Some(factor.adjustment_percentage / 100.0)
241 } else {
242 Some(-factor.adjustment_percentage / 100.0)
243 }
244 }
245 FactorType::Inventory => context.inventory_level.map(|inv| {
246 if inv < 0.2 {
247 factor.adjustment_percentage / 100.0
248 } else {
249 -factor.adjustment_percentage / 100.0
250 }
251 }),
252 _ => None,
253 };
254
255 if let Some(adj) = adjustment {
256 price *= 1.0 + adj;
257 }
258 }
259
260 Money {
261 value: price.max(0.0),
262 unit: dynamic.base_price.unit.clone(),
263 }
264}
265
266fn calculate_bundle_price(bundle: &BundlePricing, _context: &PricingContext) -> Money {
267 let total: f64 = bundle
268 .component_prices
269 .iter()
270 .map(|cp| cp.price.value)
271 .sum();
272
273 let discounted = total * (1.0 - bundle.bundle_discount / 100.0);
274
275 Money {
276 value: discounted.max(0.0),
277 unit: bundle
278 .component_prices
279 .first()
280 .map(|cp| cp.price.unit.clone())
281 .unwrap_or_else(|| "USD".to_string()),
282 }
283}