1use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Bundle {
9 pub id: Uuid,
10 pub name: String,
11 pub bundle_type: BundleType,
12 pub products: Vec<BundleProduct>,
13 pub bundle_price: Option<BundlePrice>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
19pub enum BundleType {
20 Mandatory,
22 Optional,
24 Exclusive,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct BundleProduct {
31 pub product_offering_id: Uuid,
32 pub quantity: u32,
33 pub is_required: bool,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct BundlePrice {
39 pub discount_type: BundleDiscountType,
40 pub value: f64,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
46pub enum BundleDiscountType {
47 PercentageOff,
49 FixedAmountOff,
51 FixedPrice,
53}
54
55pub fn validate_bundle(bundle: &Bundle) -> Result<(), String> {
57 if bundle.products.is_empty() {
58 return Err("Bundle must contain at least one product".to_string());
59 }
60
61 match bundle.bundle_type {
62 BundleType::Mandatory => {
63 if bundle.products.iter().any(|p| !p.is_required) {
64 return Err("Mandatory bundles cannot have optional products".to_string());
65 }
66 }
67 BundleType::Exclusive => {
68 if bundle.products.len() < 2 {
69 return Err("Exclusive bundles must have at least 2 products".to_string());
70 }
71 }
72 _ => {}
73 }
74
75 Ok(())
76}
77
78pub fn calculate_bundle_price(
80 bundle: &Bundle,
81 individual_prices: &[(Uuid, f64)],
82) -> Result<f64, String> {
83 let total_individual_price: f64 = bundle
84 .products
85 .iter()
86 .map(|bp| {
87 individual_prices
88 .iter()
89 .find(|(id, _)| *id == bp.product_offering_id)
90 .map(|(_, price)| *price * bp.quantity as f64)
91 .unwrap_or(0.0)
92 })
93 .sum();
94
95 match &bundle.bundle_price {
96 Some(bp) => match bp.discount_type {
97 BundleDiscountType::PercentageOff => {
98 Ok(total_individual_price * (1.0 - bp.value / 100.0))
99 }
100 BundleDiscountType::FixedAmountOff => Ok((total_individual_price - bp.value).max(0.0)),
101 BundleDiscountType::FixedPrice => Ok(bp.value),
102 },
103 None => Ok(total_individual_price),
104 }
105}