1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct PricingRule {
10 pub id: Uuid,
11 pub product_offering_id: Uuid,
12 pub price_type: PriceType,
13 pub base_price: Money,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub discount_rules: Option<Vec<DiscountRule>>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub valid_for: Option<TimePeriod>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
22#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
23pub enum PriceType {
24 Recurring,
25 OneTime,
26 Usage,
27 Tiered,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Money {
33 pub value: f64,
34 pub unit: String,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct DiscountRule {
40 pub name: String,
41 pub discount_type: DiscountType,
42 pub value: f64,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub conditions: Option<Vec<DiscountCondition>>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
50pub enum DiscountType {
51 Percentage,
52 FixedAmount,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct DiscountCondition {
58 pub field: String,
59 pub operator: PricingConditionOperator,
60 pub value: String,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
65#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
66pub enum PricingConditionOperator {
67 Equals,
68 GreaterThan,
69 LessThan,
70 Contains,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct TimePeriod {
76 pub start_date_time: DateTime<Utc>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub end_date_time: Option<DateTime<Utc>>,
79}
80
81pub fn calculate_final_price(rule: &PricingRule, context: &PricingContext) -> Money {
83 let mut final_price = rule.base_price.value;
84
85 if let Some(ref discounts) = rule.discount_rules {
86 for discount in discounts {
87 if is_discount_applicable(discount, context) {
88 final_price = apply_discount(final_price, discount);
89 }
90 }
91 }
92
93 Money {
94 value: final_price.max(0.0),
95 unit: rule.base_price.unit.clone(),
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct PricingContext {
102 pub customer_segment: Option<String>,
103 pub quantity: u32,
104 pub existing_products: Vec<Uuid>,
105}
106
107fn is_discount_applicable(discount: &DiscountRule, context: &PricingContext) -> bool {
108 if let Some(ref conditions) = discount.conditions {
109 conditions
110 .iter()
111 .all(|condition| evaluate_condition(condition, context))
112 } else {
113 true
114 }
115}
116
117fn evaluate_condition(condition: &DiscountCondition, context: &PricingContext) -> bool {
118 match condition.field.as_str() {
119 "customer_segment" => {
120 if let Some(ref segment) = context.customer_segment {
121 match condition.operator {
122 PricingConditionOperator::Equals => segment == &condition.value,
123 PricingConditionOperator::Contains => segment.contains(&condition.value),
124 _ => false,
125 }
126 } else {
127 false
128 }
129 }
130 "quantity" => {
131 let qty: u32 = condition.value.parse().unwrap_or(0);
132 match condition.operator {
133 PricingConditionOperator::GreaterThan => context.quantity > qty,
134 PricingConditionOperator::LessThan => context.quantity < qty,
135 PricingConditionOperator::Equals => context.quantity == qty,
136 _ => false,
137 }
138 }
139 _ => false,
140 }
141}
142
143fn apply_discount(base_price: f64, discount: &DiscountRule) -> f64 {
144 match discount.discount_type {
145 DiscountType::Percentage => base_price * (1.0 - discount.value / 100.0),
146 DiscountType::FixedAmount => (base_price - discount.value).max(0.0),
147 }
148}