Skip to main content

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#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
30#[cfg_attr(feature = "json-schema", schemars(rename = "Abrechnungsperiodendaten"))]
31#[serde(rename_all = "camelCase")]
32pub struct BillingPeriodData {
33    /// BO4E metadata
34    #[serde(flatten)]
35    pub meta: Bo4eMeta,
36
37    /// Start of billing period (Abrechnungsbeginn)
38    #[serde(skip_serializing_if = "Option::is_none")]
39    #[cfg_attr(feature = "json-schema", schemars(rename = "abrechnungsbeginn"))]
40    pub period_start: Option<NaiveDate>,
41
42    /// End of billing period (Abrechnungsende)
43    #[serde(skip_serializing_if = "Option::is_none")]
44    #[cfg_attr(feature = "json-schema", schemars(rename = "abrechnungsende"))]
45    pub period_end: Option<NaiveDate>,
46
47    /// Starting meter reading (Anfangsstand)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    #[cfg_attr(feature = "json-schema", schemars(rename = "anfangsstand"))]
50    pub start_reading: Option<f64>,
51
52    /// Starting reading timestamp (Anfangsablesung)
53    #[serde(skip_serializing_if = "Option::is_none")]
54    #[cfg_attr(feature = "json-schema", schemars(rename = "anfangsablesung"))]
55    pub start_reading_timestamp: Option<DateTime<Utc>>,
56
57    /// Ending meter reading (Endstand)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    #[cfg_attr(feature = "json-schema", schemars(rename = "endstand"))]
60    pub end_reading: Option<f64>,
61
62    /// Ending reading timestamp (Endablesung)
63    #[serde(skip_serializing_if = "Option::is_none")]
64    #[cfg_attr(feature = "json-schema", schemars(rename = "endablesung"))]
65    pub end_reading_timestamp: Option<DateTime<Utc>>,
66
67    /// Consumption value for the period (Verbrauchswert)
68    #[serde(skip_serializing_if = "Option::is_none")]
69    #[cfg_attr(feature = "json-schema", schemars(rename = "verbrauchswert"))]
70    pub consumption_value: Option<f64>,
71
72    /// Unit of consumption (Verbrauchseinheit)
73    #[serde(skip_serializing_if = "Option::is_none")]
74    #[cfg_attr(feature = "json-schema", schemars(rename = "verbrauchseinheit"))]
75    pub consumption_unit: Option<Unit>,
76
77    /// Number of days in period (Anzahl Tage)
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[cfg_attr(feature = "json-schema", schemars(rename = "anzahlTage"))]
80    pub days_in_period: Option<i32>,
81}
82
83impl Bo4eObject for BillingPeriodData {
84    fn type_name_german() -> &'static str {
85        "Abrechnungsperiodendaten"
86    }
87
88    fn type_name_english() -> &'static str {
89        "BillingPeriodData"
90    }
91
92    fn meta(&self) -> &Bo4eMeta {
93        &self.meta
94    }
95
96    fn meta_mut(&mut self) -> &mut Bo4eMeta {
97        &mut self.meta
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_billing_period_data() {
107        let data = BillingPeriodData {
108            period_start: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
109            period_end: Some(NaiveDate::from_ymd_opt(2024, 12, 31).unwrap()),
110            start_reading: Some(10000.0),
111            end_reading: Some(13500.0),
112            consumption_value: Some(3500.0),
113            consumption_unit: Some(Unit::KilowattHour),
114            days_in_period: Some(366),
115            ..Default::default()
116        };
117
118        let json = serde_json::to_string(&data).unwrap();
119        assert!(json.contains("3500"));
120    }
121
122    #[test]
123    fn test_roundtrip() {
124        let data = BillingPeriodData {
125            period_start: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
126            period_end: Some(NaiveDate::from_ymd_opt(2024, 6, 30).unwrap()),
127            consumption_value: Some(1750.0),
128            ..Default::default()
129        };
130
131        let json = serde_json::to_string(&data).unwrap();
132        let parsed: BillingPeriodData = serde_json::from_str(&json).unwrap();
133        assert_eq!(data, parsed);
134    }
135
136    #[test]
137    fn test_bo4e_object_impl() {
138        assert_eq!(
139            BillingPeriodData::type_name_german(),
140            "Abrechnungsperiodendaten"
141        );
142        assert_eq!(BillingPeriodData::type_name_english(), "BillingPeriodData");
143    }
144}