Skip to main content

bo4e_core/bo/
tariff.rs

1//! Tariff (Tarif) business object.
2
3use serde::{Deserialize, Serialize};
4
5use crate::com::{EnergyMix, Price, PriceTier, TariffCalculationParameter, TimePeriod};
6use crate::enums::{CustomerType, Division};
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9/// A tariff definition.
10///
11/// German: Tarif
12///
13/// # Example
14///
15/// ```rust
16/// use bo4e_core::bo::Tariff;
17/// use bo4e_core::com::Price;
18/// use bo4e_core::enums::{CustomerType, Division};
19///
20/// let tariff = Tariff {
21///     tariff_name: Some("Haushaltstarif 2024".to_string()),
22///     division: Some(Division::Electricity),
23///     customer_type: Some(CustomerType::Private),
24///     base_price: Some(Price::eur_per_month(9.95)),
25///     working_price: Some(Price::eur_per_kwh(0.32)),
26///     ..Default::default()
27/// };
28/// ```
29#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
30#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
31#[cfg_attr(feature = "json-schema", schemars(rename = "Tarif"))]
32#[serde(rename_all = "camelCase")]
33pub struct Tariff {
34    /// BO4E metadata
35    #[serde(flatten)]
36    pub meta: Bo4eMeta,
37
38    /// Tariff name (Tarifname)
39    #[serde(skip_serializing_if = "Option::is_none")]
40    #[cfg_attr(feature = "json-schema", schemars(rename = "tarifname"))]
41    pub tariff_name: Option<String>,
42
43    /// Tariff description (Tarifbeschreibung)
44    #[serde(skip_serializing_if = "Option::is_none")]
45    #[cfg_attr(feature = "json-schema", schemars(rename = "tarifbeschreibung"))]
46    pub description: Option<String>,
47
48    /// Energy division (Sparte)
49    #[serde(skip_serializing_if = "Option::is_none")]
50    #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
51    pub division: Option<Division>,
52
53    /// Target customer type (Kundentyp)
54    #[serde(skip_serializing_if = "Option::is_none")]
55    #[cfg_attr(feature = "json-schema", schemars(rename = "kundentyp"))]
56    pub customer_type: Option<CustomerType>,
57
58    /// Validity period (Gueltigkeitszeitraum)
59    #[serde(skip_serializing_if = "Option::is_none")]
60    #[cfg_attr(feature = "json-schema", schemars(rename = "gueltigkeitszeitraum"))]
61    pub validity_period: Option<TimePeriod>,
62
63    /// Base price (Grundpreis)
64    #[serde(skip_serializing_if = "Option::is_none")]
65    #[cfg_attr(feature = "json-schema", schemars(rename = "grundpreis"))]
66    pub base_price: Option<Price>,
67
68    /// Working price (Arbeitspreis)
69    #[serde(skip_serializing_if = "Option::is_none")]
70    #[cfg_attr(feature = "json-schema", schemars(rename = "arbeitspreis"))]
71    pub working_price: Option<Price>,
72
73    /// Price tiers (Preisstaffeln)
74    #[serde(default, skip_serializing_if = "Vec::is_empty")]
75    #[cfg_attr(feature = "json-schema", schemars(rename = "preisstaffeln"))]
76    pub price_tiers: Vec<PriceTier>,
77
78    /// Calculation parameters (Tarifberechnungsparameter)
79    #[serde(skip_serializing_if = "Option::is_none")]
80    #[cfg_attr(
81        feature = "json-schema",
82        schemars(rename = "tarifberechnungsparameter")
83    )]
84    pub calculation_parameters: Option<TariffCalculationParameter>,
85
86    /// Energy mix composition (Energiemix)
87    #[serde(skip_serializing_if = "Option::is_none")]
88    #[cfg_attr(feature = "json-schema", schemars(rename = "energiemix"))]
89    pub energy_mix: Option<EnergyMix>,
90
91    /// Provider/supplier
92    #[serde(skip_serializing_if = "Option::is_none")]
93    #[cfg_attr(feature = "json-schema", schemars(rename = "anbieter"))]
94    pub supplier: Option<Box<super::BusinessPartner>>,
95}
96
97impl Bo4eObject for Tariff {
98    fn type_name_german() -> &'static str {
99        "Tarif"
100    }
101
102    fn type_name_english() -> &'static str {
103        "Tariff"
104    }
105
106    fn meta(&self) -> &Bo4eMeta {
107        &self.meta
108    }
109
110    fn meta_mut(&mut self) -> &mut Bo4eMeta {
111        &mut self.meta
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_household_tariff() {
121        let tariff = Tariff {
122            tariff_name: Some("Haushaltstarif 2024".to_string()),
123            division: Some(Division::Electricity),
124            customer_type: Some(CustomerType::Private),
125            base_price: Some(Price::eur_per_month(9.95)),
126            working_price: Some(Price::eur_per_kwh(0.32)),
127            ..Default::default()
128        };
129
130        assert_eq!(tariff.customer_type, Some(CustomerType::Private));
131        assert_eq!(tariff.division, Some(Division::Electricity));
132    }
133
134    #[test]
135    fn test_tiered_tariff() {
136        let tariff = Tariff {
137            tariff_name: Some("Staffeltarif".to_string()),
138            price_tiers: vec![
139                PriceTier {
140                    lower_limit: Some(0.0),
141                    upper_limit: Some(1000.0),
142                    unit_price: Some(0.35),
143                    tier_number: Some(1),
144                    ..Default::default()
145                },
146                PriceTier {
147                    lower_limit: Some(1000.0),
148                    upper_limit: Some(5000.0),
149                    unit_price: Some(0.30),
150                    tier_number: Some(2),
151                    ..Default::default()
152                },
153            ],
154            ..Default::default()
155        };
156
157        assert_eq!(tariff.price_tiers.len(), 2);
158    }
159
160    #[test]
161    fn test_serialize() {
162        let tariff = Tariff {
163            meta: Bo4eMeta::with_type("Tarif"),
164            tariff_name: Some("Basic Tariff".to_string()),
165            division: Some(Division::Electricity),
166            ..Default::default()
167        };
168
169        let json = serde_json::to_string(&tariff).unwrap();
170        assert!(json.contains(r#""tariffName":"Basic Tariff""#));
171        assert!(json.contains(r#""_typ":"Tarif""#));
172    }
173
174    #[test]
175    fn test_roundtrip() {
176        let tariff = Tariff {
177            meta: Bo4eMeta::with_type("Tarif"),
178            tariff_name: Some("Test Tariff".to_string()),
179            description: Some("A test tariff".to_string()),
180            division: Some(Division::Gas),
181            customer_type: Some(CustomerType::Commercial),
182            base_price: Some(Price::eur_per_month(15.0)),
183            ..Default::default()
184        };
185
186        let json = serde_json::to_string(&tariff).unwrap();
187        let parsed: Tariff = serde_json::from_str(&json).unwrap();
188        assert_eq!(tariff, parsed);
189    }
190
191    #[test]
192    fn test_bo4e_object_impl() {
193        assert_eq!(Tariff::type_name_german(), "Tarif");
194        assert_eq!(Tariff::type_name_english(), "Tariff");
195    }
196}