bo4e_core/com/
surcharge.rs

1//! Surcharge/discount (AufAbschlag) component.
2
3use serde::{Deserialize, Serialize};
4
5use crate::enums::{Currency, SurchargeTarget, SurchargeType};
6use crate::traits::{Bo4eMeta, Bo4eObject};
7
8use super::PriceTier;
9
10/// A surcharge or discount applied to a price.
11///
12/// German: AufAbschlag
13///
14/// # Example
15///
16/// ```rust
17/// use bo4e_core::com::Surcharge;
18///
19/// let surcharge = Surcharge {
20///     description: Some("Ökosteuer".to_string()),
21///     value: Some(0.02),
22///     ..Default::default()
23/// };
24/// ```
25#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct Surcharge {
28    /// BO4E metadata
29    #[serde(flatten)]
30    pub meta: Bo4eMeta,
31
32    /// Name/designation of the surcharge (Bezeichnung)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub description: Option<String>,
35
36    /// Type of surcharge (AufAbschlagstyp)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub surcharge_type: Option<SurchargeType>,
39
40    /// Value of the surcharge (Wert)
41    /// Positive = surcharge, negative = discount
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub value: Option<f64>,
44
45    /// Currency unit for absolute surcharges (Waehrungseinheit)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub currency: Option<Currency>,
48
49    /// Target price/cost category (AufAbschlagsziel)
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub target: Option<SurchargeTarget>,
52
53    /// Tiered surcharge values (Staffeln)
54    #[serde(default, skip_serializing_if = "Vec::is_empty")]
55    pub tiers: Vec<PriceTier>,
56
57    /// Additional description (Beschreibung)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub details: Option<String>,
60
61    /// Website for published information (Website)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub website: Option<String>,
64}
65
66impl Bo4eObject for Surcharge {
67    fn type_name_german() -> &'static str {
68        "AufAbschlag"
69    }
70
71    fn type_name_english() -> &'static str {
72        "Surcharge"
73    }
74
75    fn meta(&self) -> &Bo4eMeta {
76        &self.meta
77    }
78
79    fn meta_mut(&mut self) -> &mut Bo4eMeta {
80        &mut self.meta
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_surcharge() {
90        let surcharge = Surcharge {
91            description: Some("Ökosteuer".to_string()),
92            value: Some(0.02),
93            surcharge_type: Some(SurchargeType::Absolute),
94            currency: Some(Currency::Eur),
95            ..Default::default()
96        };
97
98        let json = serde_json::to_string(&surcharge).unwrap();
99        let parsed: Surcharge = serde_json::from_str(&json).unwrap();
100        assert_eq!(surcharge, parsed);
101    }
102
103    #[test]
104    fn test_discount() {
105        let discount = Surcharge {
106            description: Some("Neukunden-Rabatt".to_string()),
107            value: Some(-50.0),
108            surcharge_type: Some(SurchargeType::Absolute),
109            currency: Some(Currency::Eur),
110            ..Default::default()
111        };
112
113        assert!(discount.value.unwrap() < 0.0);
114    }
115
116    #[test]
117    fn test_percentage_surcharge() {
118        let surcharge = Surcharge {
119            description: Some("MwSt".to_string()),
120            value: Some(19.0),
121            surcharge_type: Some(SurchargeType::Relative),
122            ..Default::default()
123        };
124
125        assert_eq!(surcharge.surcharge_type, Some(SurchargeType::Relative));
126    }
127
128    #[test]
129    fn test_tiered_surcharge() {
130        let surcharge = Surcharge {
131            description: Some("Staffelrabatt".to_string()),
132            tiers: vec![
133                PriceTier {
134                    lower_limit: Some(0.0),
135                    upper_limit: Some(1000.0),
136                    unit_price: Some(0.05),
137                    ..Default::default()
138                },
139                PriceTier {
140                    lower_limit: Some(1000.0),
141                    upper_limit: Some(5000.0),
142                    unit_price: Some(0.03),
143                    ..Default::default()
144                },
145            ],
146            ..Default::default()
147        };
148
149        assert_eq!(surcharge.tiers.len(), 2);
150    }
151
152    #[test]
153    fn test_default() {
154        let surcharge = Surcharge::default();
155        assert!(surcharge.description.is_none());
156        assert!(surcharge.value.is_none());
157        assert!(surcharge.tiers.is_empty());
158    }
159
160    #[test]
161    fn test_bo4e_object_impl() {
162        assert_eq!(Surcharge::type_name_german(), "AufAbschlag");
163        assert_eq!(Surcharge::type_name_english(), "Surcharge");
164    }
165}