bo4e_core/com/
billing_period_data.rs

1//! Billing period data (Abrechnungsperiodendaten) component.
2
3use chrono::{DateTime, NaiveDate, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::enums::Unit;
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9/// Data for a billing period.
10///
11/// German: Abrechnungsperiodendaten
12///
13/// # Example
14///
15/// ```rust
16/// use bo4e_core::com::BillingPeriodData;
17/// use bo4e_core::enums::Unit;
18/// use chrono::NaiveDate;
19///
20/// let data = BillingPeriodData {
21///     period_start: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
22///     period_end: Some(NaiveDate::from_ymd_opt(2024, 12, 31).unwrap()),
23///     consumption_value: Some(3500.0),
24///     consumption_unit: Some(Unit::KilowattHour),
25///     ..Default::default()
26/// };
27/// ```
28#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct BillingPeriodData {
31    /// BO4E metadata
32    #[serde(flatten)]
33    pub meta: Bo4eMeta,
34
35    /// Start of billing period (Abrechnungsbeginn)
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub period_start: Option<NaiveDate>,
38
39    /// End of billing period (Abrechnungsende)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub period_end: Option<NaiveDate>,
42
43    /// Starting meter reading (Anfangsstand)
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub start_reading: Option<f64>,
46
47    /// Starting reading timestamp (Anfangsablesung)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub start_reading_timestamp: Option<DateTime<Utc>>,
50
51    /// Ending meter reading (Endstand)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub end_reading: Option<f64>,
54
55    /// Ending reading timestamp (Endablesung)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub end_reading_timestamp: Option<DateTime<Utc>>,
58
59    /// Consumption value for the period (Verbrauchswert)
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub consumption_value: Option<f64>,
62
63    /// Unit of consumption (Verbrauchseinheit)
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub consumption_unit: Option<Unit>,
66
67    /// Number of days in period (Anzahl Tage)
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub days_in_period: Option<i32>,
70}
71
72impl Bo4eObject for BillingPeriodData {
73    fn type_name_german() -> &'static str {
74        "Abrechnungsperiodendaten"
75    }
76
77    fn type_name_english() -> &'static str {
78        "BillingPeriodData"
79    }
80
81    fn meta(&self) -> &Bo4eMeta {
82        &self.meta
83    }
84
85    fn meta_mut(&mut self) -> &mut Bo4eMeta {
86        &mut self.meta
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_billing_period_data() {
96        let data = BillingPeriodData {
97            period_start: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
98            period_end: Some(NaiveDate::from_ymd_opt(2024, 12, 31).unwrap()),
99            start_reading: Some(10000.0),
100            end_reading: Some(13500.0),
101            consumption_value: Some(3500.0),
102            consumption_unit: Some(Unit::KilowattHour),
103            days_in_period: Some(366),
104            ..Default::default()
105        };
106
107        let json = serde_json::to_string(&data).unwrap();
108        assert!(json.contains("3500"));
109    }
110
111    #[test]
112    fn test_roundtrip() {
113        let data = BillingPeriodData {
114            period_start: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
115            period_end: Some(NaiveDate::from_ymd_opt(2024, 6, 30).unwrap()),
116            consumption_value: Some(1750.0),
117            ..Default::default()
118        };
119
120        let json = serde_json::to_string(&data).unwrap();
121        let parsed: BillingPeriodData = serde_json::from_str(&json).unwrap();
122        assert_eq!(data, parsed);
123    }
124
125    #[test]
126    fn test_bo4e_object_impl() {
127        assert_eq!(
128            BillingPeriodData::type_name_german(),
129            "Abrechnungsperiodendaten"
130        );
131        assert_eq!(BillingPeriodData::type_name_english(), "BillingPeriodData");
132    }
133}