Skip to main content

bo4e_core/bo/
service_price_sheet.rs

1//! Service price sheet (PreisblattDienstleistung) business object.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::com::{ServicePrice, TimePeriod};
7use crate::enums::{Division, ServiceType};
8use crate::traits::{Bo4eMeta, Bo4eObject};
9
10/// A price sheet for services (e.g., metering, billing).
11///
12/// German: PreisblattDienstleistung
13///
14/// # Example
15///
16/// ```rust
17/// use bo4e_core::bo::ServicePriceSheet;
18/// use bo4e_core::enums::{Division, ServiceType};
19///
20/// let price_sheet = ServicePriceSheet {
21///     designation: Some("Dienstleistungspreisblatt 2024".to_string()),
22///     division: Some(Division::Electricity),
23///     service_type: Some(ServiceType::RemoteReading),
24///     ..Default::default()
25/// };
26/// ```
27#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
28#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
29#[cfg_attr(feature = "json-schema", schemars(rename = "PreisblattDienstleistung"))]
30#[serde(rename_all = "camelCase")]
31pub struct ServicePriceSheet {
32    /// BO4E metadata
33    #[serde(flatten)]
34    pub meta: Bo4eMeta,
35
36    /// Name/designation of the price sheet (Bezeichnung)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    #[cfg_attr(feature = "json-schema", schemars(rename = "bezeichnung"))]
39    pub designation: Option<String>,
40
41    /// Description (Beschreibung)
42    #[serde(skip_serializing_if = "Option::is_none")]
43    #[cfg_attr(feature = "json-schema", schemars(rename = "beschreibung"))]
44    pub description: Option<String>,
45
46    /// Energy division (Sparte)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
49    pub division: Option<Division>,
50
51    /// Type of service (Dienstleistungsart)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    #[cfg_attr(feature = "json-schema", schemars(rename = "dienstleistungsart"))]
54    pub service_type: Option<ServiceType>,
55
56    /// Price sheet number/identifier (Preisblattnummer)
57    #[serde(skip_serializing_if = "Option::is_none")]
58    #[cfg_attr(feature = "json-schema", schemars(rename = "preisblattnummer"))]
59    pub price_sheet_number: Option<String>,
60
61    /// Validity period (Gueltigkeitszeitraum)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    #[cfg_attr(feature = "json-schema", schemars(rename = "gueltigkeitszeitraum"))]
64    pub validity_period: Option<TimePeriod>,
65
66    /// Valid from date (Gueltig ab)
67    #[serde(skip_serializing_if = "Option::is_none")]
68    #[cfg_attr(feature = "json-schema", schemars(rename = "gueltigAb"))]
69    pub valid_from: Option<DateTime<Utc>>,
70
71    /// Valid until date (Gueltig bis)
72    #[serde(skip_serializing_if = "Option::is_none")]
73    #[cfg_attr(feature = "json-schema", schemars(rename = "gueltigBis"))]
74    pub valid_until: Option<DateTime<Utc>>,
75
76    /// Service prices (Dienstleistungspreise)
77    #[serde(default, skip_serializing_if = "Vec::is_empty")]
78    #[cfg_attr(feature = "json-schema", schemars(rename = "dienstleistungspreise"))]
79    pub prices: Vec<ServicePrice>,
80
81    /// Service provider
82    #[serde(skip_serializing_if = "Option::is_none")]
83    #[cfg_attr(feature = "json-schema", schemars(rename = "dienstleister"))]
84    pub provider: Option<Box<super::BusinessPartner>>,
85}
86
87impl Bo4eObject for ServicePriceSheet {
88    fn type_name_german() -> &'static str {
89        "PreisblattDienstleistung"
90    }
91
92    fn type_name_english() -> &'static str {
93        "ServicePriceSheet"
94    }
95
96    fn meta(&self) -> &Bo4eMeta {
97        &self.meta
98    }
99
100    fn meta_mut(&mut self) -> &mut Bo4eMeta {
101        &mut self.meta
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_service_price_sheet_creation() {
111        let price_sheet = ServicePriceSheet {
112            designation: Some("Dienstleistungspreisblatt 2024".to_string()),
113            division: Some(Division::Electricity),
114            service_type: Some(ServiceType::RemoteReading),
115            price_sheet_number: Some("DL-2024-001".to_string()),
116            ..Default::default()
117        };
118
119        assert_eq!(price_sheet.service_type, Some(ServiceType::RemoteReading));
120    }
121
122    #[test]
123    fn test_serialize() {
124        let price_sheet = ServicePriceSheet {
125            meta: Bo4eMeta::with_type("PreisblattDienstleistung"),
126            designation: Some("Service Prices".to_string()),
127            ..Default::default()
128        };
129
130        let json = serde_json::to_string(&price_sheet).unwrap();
131        assert!(json.contains(r#""designation":"Service Prices""#));
132        assert!(json.contains(r#""_typ":"PreisblattDienstleistung""#));
133    }
134
135    #[test]
136    fn test_roundtrip() {
137        let price_sheet = ServicePriceSheet {
138            meta: Bo4eMeta::with_type("PreisblattDienstleistung"),
139            designation: Some("Billing Services".to_string()),
140            description: Some("Standard billing service prices".to_string()),
141            division: Some(Division::Electricity),
142            price_sheet_number: Some("BS-2024".to_string()),
143            ..Default::default()
144        };
145
146        let json = serde_json::to_string(&price_sheet).unwrap();
147        let parsed: ServicePriceSheet = serde_json::from_str(&json).unwrap();
148        assert_eq!(price_sheet, parsed);
149    }
150
151    #[test]
152    fn test_bo4e_object_impl() {
153        assert_eq!(
154            ServicePriceSheet::type_name_german(),
155            "PreisblattDienstleistung"
156        );
157        assert_eq!(ServicePriceSheet::type_name_english(), "ServicePriceSheet");
158    }
159}