Skip to main content

bo4e_core/bo/
metering_location.rs

1//! Metering location (Messlokation) business object.
2//!
3//! Represents the physical location where energy measurement takes place.
4
5use serde::{Deserialize, Serialize};
6
7use crate::com::{Address, GeoCoordinates, Hardware};
8use crate::enums::Division;
9use crate::traits::{Bo4eMeta, Bo4eObject};
10
11/// A metering location (MeLo) - where measurement takes place.
12///
13/// German: Messlokation
14///
15/// A metering location represents the physical point where energy
16/// is measured. It has a 33-character identifier.
17///
18/// # Example
19///
20/// ```rust
21/// use bo4e_core::bo::MeteringLocation;
22/// use bo4e_core::enums::Division;
23///
24/// let melo = MeteringLocation {
25///     metering_location_id: Some("DE00012345678901234567890123456789".to_string()),
26///     division: Some(Division::Electricity),
27///     ..Default::default()
28/// };
29/// ```
30#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
31#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
32#[cfg_attr(feature = "json-schema", schemars(rename = "Messlokation"))]
33#[serde(rename_all = "camelCase")]
34pub struct MeteringLocation {
35    /// BO4E metadata
36    #[serde(flatten)]
37    pub meta: Bo4eMeta,
38
39    /// Metering location ID - 33 characters (Messlokations-ID)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    #[cfg_attr(feature = "json-schema", schemars(rename = "messlokationsId"))]
42    pub metering_location_id: Option<String>,
43
44    /// Energy division (Sparte)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
47    pub division: Option<Division>,
48
49    /// Location address (Adresse)
50    #[serde(skip_serializing_if = "Option::is_none")]
51    #[cfg_attr(feature = "json-schema", schemars(rename = "adresse"))]
52    pub address: Option<Address>,
53
54    /// Geographic coordinates (Geokoordinaten)
55    #[serde(skip_serializing_if = "Option::is_none")]
56    #[cfg_attr(feature = "json-schema", schemars(rename = "geokoordinaten"))]
57    pub coordinates: Option<GeoCoordinates>,
58
59    /// Metering point operator code (Messstellenbetreiber-Codenummer)
60    #[serde(skip_serializing_if = "Option::is_none")]
61    #[cfg_attr(
62        feature = "json-schema",
63        schemars(rename = "messstellenbetreiberCodenummer")
64    )]
65    pub metering_operator_code: Option<String>,
66
67    /// Network operator code (Netzbetreiber-Codenummer)
68    #[serde(skip_serializing_if = "Option::is_none")]
69    #[cfg_attr(feature = "json-schema", schemars(rename = "netzbetreiberCodenummer"))]
70    pub network_operator_code: Option<String>,
71
72    /// Grid area (Regelzone)
73    #[serde(skip_serializing_if = "Option::is_none")]
74    #[cfg_attr(feature = "json-schema", schemars(rename = "regelzone"))]
75    pub grid_area: Option<String>,
76
77    /// Description of the metering location (Beschreibung)
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[cfg_attr(feature = "json-schema", schemars(rename = "beschreibung"))]
80    pub description: Option<String>,
81
82    /// Hardware at this metering location (Geraete)
83    #[serde(default, skip_serializing_if = "Vec::is_empty")]
84    #[cfg_attr(feature = "json-schema", schemars(rename = "geraete"))]
85    pub hardware: Vec<Hardware>,
86
87    /// Associated meter IDs (Zaehler)
88    #[serde(default, skip_serializing_if = "Vec::is_empty")]
89    #[cfg_attr(feature = "json-schema", schemars(rename = "zaehler"))]
90    pub meter_ids: Vec<String>,
91
92    /// Associated market location IDs (Marktlokationen)
93    #[serde(default, skip_serializing_if = "Vec::is_empty")]
94    #[cfg_attr(feature = "json-schema", schemars(rename = "marktlokationen"))]
95    pub market_location_ids: Vec<String>,
96}
97
98impl Bo4eObject for MeteringLocation {
99    fn type_name_german() -> &'static str {
100        "Messlokation"
101    }
102
103    fn type_name_english() -> &'static str {
104        "MeteringLocation"
105    }
106
107    fn meta(&self) -> &Bo4eMeta {
108        &self.meta
109    }
110
111    fn meta_mut(&mut self) -> &mut Bo4eMeta {
112        &mut self.meta
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_melo_creation() {
122        let melo = MeteringLocation {
123            metering_location_id: Some("DE00012345678901234567890123456789".to_string()),
124            division: Some(Division::Electricity),
125            ..Default::default()
126        };
127
128        assert!(melo.metering_location_id.is_some());
129    }
130
131    #[test]
132    fn test_serialize() {
133        let melo = MeteringLocation {
134            meta: Bo4eMeta::with_type("Messlokation"),
135            metering_location_id: Some("DE00012345678901234567890123456789".to_string()),
136            ..Default::default()
137        };
138
139        let json = serde_json::to_string(&melo).unwrap();
140        assert!(json.contains(r#""_typ":"Messlokation""#));
141        assert!(json.contains("DE00012345678901234567890123456789"));
142    }
143
144    #[test]
145    fn test_roundtrip() {
146        let melo = MeteringLocation {
147            meta: Bo4eMeta::with_type("Messlokation"),
148            metering_location_id: Some("DE00012345678901234567890123456789".to_string()),
149            division: Some(Division::Electricity),
150            metering_operator_code: Some("9900001234".to_string()),
151            meter_ids: vec!["METER001".to_string()],
152            ..Default::default()
153        };
154
155        let json = serde_json::to_string(&melo).unwrap();
156        let parsed: MeteringLocation = serde_json::from_str(&json).unwrap();
157        assert_eq!(melo, parsed);
158    }
159
160    #[test]
161    fn test_bo4e_object_impl() {
162        assert_eq!(MeteringLocation::type_name_german(), "Messlokation");
163        assert_eq!(MeteringLocation::type_name_english(), "MeteringLocation");
164    }
165}