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#[serde(rename_all = "camelCase")]
32pub struct MeteringLocation {
33    /// BO4E metadata
34    #[serde(flatten)]
35    pub meta: Bo4eMeta,
36
37    /// Metering location ID - 33 characters (Messlokations-ID)
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub metering_location_id: Option<String>,
40
41    /// Energy division (Sparte)
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub division: Option<Division>,
44
45    /// Location address (Adresse)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub address: Option<Address>,
48
49    /// Geographic coordinates (Geokoordinaten)
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub coordinates: Option<GeoCoordinates>,
52
53    /// Metering point operator code (Messstellenbetreiber-Codenummer)
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub metering_operator_code: Option<String>,
56
57    /// Network operator code (Netzbetreiber-Codenummer)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub network_operator_code: Option<String>,
60
61    /// Grid area (Regelzone)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub grid_area: Option<String>,
64
65    /// Description of the metering location (Beschreibung)
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub description: Option<String>,
68
69    /// Hardware at this metering location (Geraete)
70    #[serde(default, skip_serializing_if = "Vec::is_empty")]
71    pub hardware: Vec<Hardware>,
72
73    /// Associated meter IDs (Zaehler)
74    #[serde(default, skip_serializing_if = "Vec::is_empty")]
75    pub meter_ids: Vec<String>,
76
77    /// Associated market location IDs (Marktlokationen)
78    #[serde(default, skip_serializing_if = "Vec::is_empty")]
79    pub market_location_ids: Vec<String>,
80}
81
82impl Bo4eObject for MeteringLocation {
83    fn type_name_german() -> &'static str {
84        "Messlokation"
85    }
86
87    fn type_name_english() -> &'static str {
88        "MeteringLocation"
89    }
90
91    fn meta(&self) -> &Bo4eMeta {
92        &self.meta
93    }
94
95    fn meta_mut(&mut self) -> &mut Bo4eMeta {
96        &mut self.meta
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_melo_creation() {
106        let melo = MeteringLocation {
107            metering_location_id: Some("DE00012345678901234567890123456789".to_string()),
108            division: Some(Division::Electricity),
109            ..Default::default()
110        };
111
112        assert!(melo.metering_location_id.is_some());
113    }
114
115    #[test]
116    fn test_serialize() {
117        let melo = MeteringLocation {
118            meta: Bo4eMeta::with_type("Messlokation"),
119            metering_location_id: Some("DE00012345678901234567890123456789".to_string()),
120            ..Default::default()
121        };
122
123        let json = serde_json::to_string(&melo).unwrap();
124        assert!(json.contains(r#""_typ":"Messlokation""#));
125        assert!(json.contains("DE00012345678901234567890123456789"));
126    }
127
128    #[test]
129    fn test_roundtrip() {
130        let melo = MeteringLocation {
131            meta: Bo4eMeta::with_type("Messlokation"),
132            metering_location_id: Some("DE00012345678901234567890123456789".to_string()),
133            division: Some(Division::Electricity),
134            metering_operator_code: Some("9900001234".to_string()),
135            meter_ids: vec!["METER001".to_string()],
136            ..Default::default()
137        };
138
139        let json = serde_json::to_string(&melo).unwrap();
140        let parsed: MeteringLocation = serde_json::from_str(&json).unwrap();
141        assert_eq!(melo, parsed);
142    }
143
144    #[test]
145    fn test_bo4e_object_impl() {
146        assert_eq!(MeteringLocation::type_name_german(), "Messlokation");
147        assert_eq!(MeteringLocation::type_name_english(), "MeteringLocation");
148    }
149}