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#[serde(rename_all = "camelCase")]
31pub struct Tariff {
32    /// BO4E metadata
33    #[serde(flatten)]
34    pub meta: Bo4eMeta,
35
36    /// Tariff name (Tarifname)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub tariff_name: Option<String>,
39
40    /// Tariff description (Tarifbeschreibung)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub description: Option<String>,
43
44    /// Energy division (Sparte)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub division: Option<Division>,
47
48    /// Target customer type (Kundentyp)
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub customer_type: Option<CustomerType>,
51
52    /// Validity period (Gueltigkeitszeitraum)
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub validity_period: Option<TimePeriod>,
55
56    /// Base price (Grundpreis)
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub base_price: Option<Price>,
59
60    /// Working price (Arbeitspreis)
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub working_price: Option<Price>,
63
64    /// Price tiers (Preisstaffeln)
65    #[serde(default, skip_serializing_if = "Vec::is_empty")]
66    pub price_tiers: Vec<PriceTier>,
67
68    /// Calculation parameters (Tarifberechnungsparameter)
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub calculation_parameters: Option<TariffCalculationParameter>,
71
72    /// Energy mix composition (Energiemix)
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub energy_mix: Option<EnergyMix>,
75
76    /// Provider/supplier
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub supplier: Option<Box<super::BusinessPartner>>,
79}
80
81impl Bo4eObject for Tariff {
82    fn type_name_german() -> &'static str {
83        "Tarif"
84    }
85
86    fn type_name_english() -> &'static str {
87        "Tariff"
88    }
89
90    fn meta(&self) -> &Bo4eMeta {
91        &self.meta
92    }
93
94    fn meta_mut(&mut self) -> &mut Bo4eMeta {
95        &mut self.meta
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_household_tariff() {
105        let tariff = Tariff {
106            tariff_name: Some("Haushaltstarif 2024".to_string()),
107            division: Some(Division::Electricity),
108            customer_type: Some(CustomerType::Private),
109            base_price: Some(Price::eur_per_month(9.95)),
110            working_price: Some(Price::eur_per_kwh(0.32)),
111            ..Default::default()
112        };
113
114        assert_eq!(tariff.customer_type, Some(CustomerType::Private));
115        assert_eq!(tariff.division, Some(Division::Electricity));
116    }
117
118    #[test]
119    fn test_tiered_tariff() {
120        let tariff = Tariff {
121            tariff_name: Some("Staffeltarif".to_string()),
122            price_tiers: vec![
123                PriceTier {
124                    lower_limit: Some(0.0),
125                    upper_limit: Some(1000.0),
126                    unit_price: Some(0.35),
127                    tier_number: Some(1),
128                    ..Default::default()
129                },
130                PriceTier {
131                    lower_limit: Some(1000.0),
132                    upper_limit: Some(5000.0),
133                    unit_price: Some(0.30),
134                    tier_number: Some(2),
135                    ..Default::default()
136                },
137            ],
138            ..Default::default()
139        };
140
141        assert_eq!(tariff.price_tiers.len(), 2);
142    }
143
144    #[test]
145    fn test_serialize() {
146        let tariff = Tariff {
147            meta: Bo4eMeta::with_type("Tarif"),
148            tariff_name: Some("Basic Tariff".to_string()),
149            division: Some(Division::Electricity),
150            ..Default::default()
151        };
152
153        let json = serde_json::to_string(&tariff).unwrap();
154        assert!(json.contains(r#""tariffName":"Basic Tariff""#));
155        assert!(json.contains(r#""_typ":"Tarif""#));
156    }
157
158    #[test]
159    fn test_roundtrip() {
160        let tariff = Tariff {
161            meta: Bo4eMeta::with_type("Tarif"),
162            tariff_name: Some("Test Tariff".to_string()),
163            description: Some("A test tariff".to_string()),
164            division: Some(Division::Gas),
165            customer_type: Some(CustomerType::Commercial),
166            base_price: Some(Price::eur_per_month(15.0)),
167            ..Default::default()
168        };
169
170        let json = serde_json::to_string(&tariff).unwrap();
171        let parsed: Tariff = serde_json::from_str(&json).unwrap();
172        assert_eq!(tariff, parsed);
173    }
174
175    #[test]
176    fn test_bo4e_object_impl() {
177        assert_eq!(Tariff::type_name_german(), "Tarif");
178        assert_eq!(Tariff::type_name_english(), "Tariff");
179    }
180}