Skip to main content

bo4e_core/bo/
balancing.rs

1//! Balancing (Bilanzierung) business object.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::com::TimePeriod;
7use crate::enums::Division;
8use crate::traits::{Bo4eMeta, Bo4eObject};
9
10/// Balance group data for energy market balancing.
11///
12/// German: Bilanzierung
13///
14/// # Example
15///
16/// ```rust
17/// use bo4e_core::bo::Balancing;
18/// use bo4e_core::enums::Division;
19///
20/// let balancing = Balancing {
21///     balance_group_id: Some("11XBALANCEGROUP".to_string()),
22///     division: Some(Division::Electricity),
23///     ..Default::default()
24/// };
25/// ```
26#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
27#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
28#[cfg_attr(feature = "json-schema", schemars(rename = "Bilanzierung"))]
29#[serde(rename_all = "camelCase")]
30pub struct Balancing {
31    /// BO4E metadata
32    #[serde(flatten)]
33    pub meta: Bo4eMeta,
34
35    /// Balance group identifier (Bilanzkreis-ID)
36    #[serde(skip_serializing_if = "Option::is_none")]
37    #[cfg_attr(feature = "json-schema", schemars(rename = "bilanzkreisId"))]
38    pub balance_group_id: Option<String>,
39
40    /// Balance group name (Bilanzkreisname)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    #[cfg_attr(feature = "json-schema", schemars(rename = "bilanzkreisname"))]
43    pub balance_group_name: Option<String>,
44
45    /// Description (Beschreibung)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    #[cfg_attr(feature = "json-schema", schemars(rename = "beschreibung"))]
48    pub description: Option<String>,
49
50    /// Energy division (Sparte)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
53    pub division: Option<Division>,
54
55    /// Market area (Marktgebiet)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    #[cfg_attr(feature = "json-schema", schemars(rename = "marktgebiet"))]
58    pub market_area: Option<String>,
59
60    /// Balance responsible party (Bilanzkreisverantwortlicher)
61    #[serde(skip_serializing_if = "Option::is_none")]
62    #[cfg_attr(
63        feature = "json-schema",
64        schemars(rename = "bilanzkreisverantwortlicher")
65    )]
66    pub balance_responsible_party: Option<Box<super::MarketParticipant>>,
67
68    /// Validity period (Gueltigkeitszeitraum)
69    #[serde(skip_serializing_if = "Option::is_none")]
70    #[cfg_attr(feature = "json-schema", schemars(rename = "gueltigkeitszeitraum"))]
71    pub validity_period: Option<TimePeriod>,
72
73    /// Start date of balancing (Startdatum)
74    #[serde(skip_serializing_if = "Option::is_none")]
75    #[cfg_attr(feature = "json-schema", schemars(rename = "startdatum"))]
76    pub start_date: Option<DateTime<Utc>>,
77
78    /// End date of balancing (Enddatum)
79    #[serde(skip_serializing_if = "Option::is_none")]
80    #[cfg_attr(feature = "json-schema", schemars(rename = "enddatum"))]
81    pub end_date: Option<DateTime<Utc>>,
82}
83
84impl Bo4eObject for Balancing {
85    fn type_name_german() -> &'static str {
86        "Bilanzierung"
87    }
88
89    fn type_name_english() -> &'static str {
90        "Balancing"
91    }
92
93    fn meta(&self) -> &Bo4eMeta {
94        &self.meta
95    }
96
97    fn meta_mut(&mut self) -> &mut Bo4eMeta {
98        &mut self.meta
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_balancing_creation() {
108        let balancing = Balancing {
109            balance_group_id: Some("11XBALANCEGROUP".to_string()),
110            division: Some(Division::Electricity),
111            ..Default::default()
112        };
113
114        assert_eq!(
115            balancing.balance_group_id,
116            Some("11XBALANCEGROUP".to_string())
117        );
118        assert_eq!(balancing.division, Some(Division::Electricity));
119    }
120
121    #[test]
122    fn test_gas_balancing() {
123        let balancing = Balancing {
124            balance_group_id: Some("GASBALANCE001".to_string()),
125            balance_group_name: Some("Gas Balance Group 1".to_string()),
126            division: Some(Division::Gas),
127            market_area: Some("NCG".to_string()),
128            ..Default::default()
129        };
130
131        assert_eq!(balancing.division, Some(Division::Gas));
132        assert_eq!(balancing.market_area, Some("NCG".to_string()));
133    }
134
135    #[test]
136    fn test_serialize() {
137        let balancing = Balancing {
138            meta: Bo4eMeta::with_type("Bilanzierung"),
139            balance_group_id: Some("11XTEST".to_string()),
140            division: Some(Division::Electricity),
141            ..Default::default()
142        };
143
144        let json = serde_json::to_string(&balancing).unwrap();
145        assert!(json.contains(r#""balanceGroupId":"11XTEST""#));
146    }
147
148    #[test]
149    fn test_roundtrip() {
150        let balancing = Balancing {
151            meta: Bo4eMeta::with_type("Bilanzierung"),
152            balance_group_id: Some("11XTEST".to_string()),
153            balance_group_name: Some("Test Balance Group".to_string()),
154            description: Some("Test description".to_string()),
155            division: Some(Division::Electricity),
156            market_area: Some("DE".to_string()),
157            ..Default::default()
158        };
159
160        let json = serde_json::to_string(&balancing).unwrap();
161        let parsed: Balancing = serde_json::from_str(&json).unwrap();
162        assert_eq!(balancing, parsed);
163    }
164
165    #[test]
166    fn test_bo4e_object_impl() {
167        assert_eq!(Balancing::type_name_german(), "Bilanzierung");
168        assert_eq!(Balancing::type_name_english(), "Balancing");
169    }
170}