bo4e_core/bo/
tariff_costs.rs

1//! Tariff costs (Tarifkosten) business object.
2
3use serde::{Deserialize, Serialize};
4
5use crate::com::{Amount, CostBlock, Price, TimePeriod};
6use crate::enums::Division;
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9/// Tariff-related costs.
10///
11/// Represents the costs associated with a specific tariff.
12///
13/// German: Tarifkosten
14///
15/// # Example
16///
17/// ```rust
18/// use bo4e_core::bo::TariffCosts;
19/// use bo4e_core::com::{Amount, Price};
20/// use bo4e_core::enums::Division;
21///
22/// let tariff_costs = TariffCosts {
23///     designation: Some("Haushaltstarif Kosten".to_string()),
24///     division: Some(Division::Electricity),
25///     base_price_cost: Some(Amount::eur(119.40)),
26///     working_price_cost: Some(Amount::eur(960.00)),
27///     ..Default::default()
28/// };
29/// ```
30#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct TariffCosts {
33    /// BO4E metadata
34    #[serde(flatten)]
35    pub meta: Bo4eMeta,
36
37    /// Name/designation of the tariff costs (Bezeichnung)
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub designation: Option<String>,
40
41    /// Description (Beschreibung)
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub description: Option<String>,
44
45    /// Energy division (Sparte)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub division: Option<Division>,
48
49    /// Period the costs apply to (Abrechnungszeitraum)
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub period: Option<TimePeriod>,
52
53    /// Total amount (Gesamtbetrag)
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub total_amount: Option<Amount>,
56
57    /// Base price applied (Grundpreis)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub base_price: Option<Price>,
60
61    /// Base price cost (Grundpreiskosten)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub base_price_cost: Option<Amount>,
64
65    /// Working price applied (Arbeitspreis)
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub working_price: Option<Price>,
68
69    /// Working price cost (Arbeitspreiskosten)
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub working_price_cost: Option<Amount>,
72
73    /// Consumption quantity (Verbrauchsmenge)
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub consumption: Option<f64>,
76
77    /// Cost blocks (Kostenbloecke)
78    #[serde(default, skip_serializing_if = "Vec::is_empty")]
79    pub cost_blocks: Vec<CostBlock>,
80
81    /// Reference to the tariff
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub tariff: Option<Box<super::Tariff>>,
84}
85
86impl Bo4eObject for TariffCosts {
87    fn type_name_german() -> &'static str {
88        "Tarifkosten"
89    }
90
91    fn type_name_english() -> &'static str {
92        "TariffCosts"
93    }
94
95    fn meta(&self) -> &Bo4eMeta {
96        &self.meta
97    }
98
99    fn meta_mut(&mut self) -> &mut Bo4eMeta {
100        &mut self.meta
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_tariff_costs_creation() {
110        let tariff_costs = TariffCosts {
111            designation: Some("Haushaltstarif Kosten".to_string()),
112            division: Some(Division::Electricity),
113            base_price: Some(Price::eur_per_month(9.95)),
114            base_price_cost: Some(Amount::eur(119.40)),
115            working_price: Some(Price::eur_per_kwh(0.32)),
116            working_price_cost: Some(Amount::eur(960.00)),
117            consumption: Some(3000.0),
118            total_amount: Some(Amount::eur(1079.40)),
119            ..Default::default()
120        };
121
122        assert_eq!(tariff_costs.consumption, Some(3000.0));
123    }
124
125    #[test]
126    fn test_serialize() {
127        let tariff_costs = TariffCosts {
128            meta: Bo4eMeta::with_type("Tarifkosten"),
129            designation: Some("Test Tariff Costs".to_string()),
130            total_amount: Some(Amount::eur(500.0)),
131            ..Default::default()
132        };
133
134        let json = serde_json::to_string(&tariff_costs).unwrap();
135        assert!(json.contains(r#""designation":"Test Tariff Costs""#));
136        assert!(json.contains(r#""_typ":"Tarifkosten""#));
137    }
138
139    #[test]
140    fn test_roundtrip() {
141        let tariff_costs = TariffCosts {
142            meta: Bo4eMeta::with_type("Tarifkosten"),
143            designation: Some("Gas Tariff Costs".to_string()),
144            description: Some("Annual gas tariff costs".to_string()),
145            division: Some(Division::Gas),
146            consumption: Some(15000.0),
147            total_amount: Some(Amount::eur(1500.0)),
148            ..Default::default()
149        };
150
151        let json = serde_json::to_string(&tariff_costs).unwrap();
152        let parsed: TariffCosts = serde_json::from_str(&json).unwrap();
153        assert_eq!(tariff_costs, parsed);
154    }
155
156    #[test]
157    fn test_bo4e_object_impl() {
158        assert_eq!(TariffCosts::type_name_german(), "Tarifkosten");
159        assert_eq!(TariffCosts::type_name_english(), "TariffCosts");
160    }
161}