Skip to main content

bo4e_core/bo/
external_costs.rs

1//! External costs (Fremdkosten) business object.
2
3use serde::{Deserialize, Serialize};
4
5use crate::com::{Amount, ExternalCostBlock, TimePeriod};
6use crate::enums::Division;
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9/// External/third-party costs.
10///
11/// Represents costs from external parties like network operators,
12/// metering service providers, etc.
13///
14/// German: Fremdkosten
15///
16/// # Example
17///
18/// ```rust
19/// use bo4e_core::bo::ExternalCosts;
20/// use bo4e_core::com::Amount;
21/// use bo4e_core::enums::Division;
22///
23/// let external_costs = ExternalCosts {
24///     designation: Some("Netzkosten 2024".to_string()),
25///     division: Some(Division::Electricity),
26///     total_amount: Some(Amount::eur(350.00)),
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 = "Fremdkosten"))]
33#[serde(rename_all = "camelCase")]
34pub struct ExternalCosts {
35    /// BO4E metadata
36    #[serde(flatten)]
37    pub meta: Bo4eMeta,
38
39    /// Name/designation of the external costs (Bezeichnung)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    #[cfg_attr(feature = "json-schema", schemars(rename = "bezeichnung"))]
42    pub designation: Option<String>,
43
44    /// Description (Beschreibung)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    #[cfg_attr(feature = "json-schema", schemars(rename = "beschreibung"))]
47    pub description: Option<String>,
48
49    /// Energy division (Sparte)
50    #[serde(skip_serializing_if = "Option::is_none")]
51    #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
52    pub division: Option<Division>,
53
54    /// Period the costs apply to (Abrechnungszeitraum)
55    #[serde(skip_serializing_if = "Option::is_none")]
56    #[cfg_attr(feature = "json-schema", schemars(rename = "abrechnungszeitraum"))]
57    pub period: Option<TimePeriod>,
58
59    /// Total amount (Gesamtbetrag)
60    #[serde(skip_serializing_if = "Option::is_none")]
61    #[cfg_attr(feature = "json-schema", schemars(rename = "gesamtbetrag"))]
62    pub total_amount: Option<Amount>,
63
64    /// External cost blocks (Fremdkostenbloecke)
65    #[serde(default, skip_serializing_if = "Vec::is_empty")]
66    #[cfg_attr(feature = "json-schema", schemars(rename = "fremdkostenbloecke"))]
67    pub cost_blocks: Vec<ExternalCostBlock>,
68
69    /// External provider/party
70    #[serde(skip_serializing_if = "Option::is_none")]
71    #[cfg_attr(feature = "json-schema", schemars(rename = "fremdpartei"))]
72    pub external_party: Option<Box<super::BusinessPartner>>,
73
74    /// Related market location (Marktlokation)
75    #[serde(skip_serializing_if = "Option::is_none")]
76    #[cfg_attr(feature = "json-schema", schemars(rename = "marktlokation"))]
77    pub market_location: Option<Box<super::MarketLocation>>,
78}
79
80impl Bo4eObject for ExternalCosts {
81    fn type_name_german() -> &'static str {
82        "Fremdkosten"
83    }
84
85    fn type_name_english() -> &'static str {
86        "ExternalCosts"
87    }
88
89    fn meta(&self) -> &Bo4eMeta {
90        &self.meta
91    }
92
93    fn meta_mut(&mut self) -> &mut Bo4eMeta {
94        &mut self.meta
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::com::ExternalCostBlock;
102
103    #[test]
104    fn test_external_costs_creation() {
105        let external_costs = ExternalCosts {
106            designation: Some("Netzkosten 2024".to_string()),
107            division: Some(Division::Electricity),
108            total_amount: Some(Amount::eur(350.00)),
109            ..Default::default()
110        };
111
112        assert_eq!(external_costs.total_amount, Some(Amount::eur(350.00)));
113    }
114
115    #[test]
116    fn test_external_costs_with_blocks() {
117        let external_costs = ExternalCosts {
118            designation: Some("Network Costs".to_string()),
119            cost_blocks: vec![
120                ExternalCostBlock {
121                    designation: Some("Network usage".to_string()),
122                    total_amount: Some(Amount::eur(200.0)),
123                    ..Default::default()
124                },
125                ExternalCostBlock {
126                    designation: Some("Metering".to_string()),
127                    total_amount: Some(Amount::eur(50.0)),
128                    ..Default::default()
129                },
130            ],
131            total_amount: Some(Amount::eur(250.0)),
132            ..Default::default()
133        };
134
135        assert_eq!(external_costs.cost_blocks.len(), 2);
136    }
137
138    #[test]
139    fn test_serialize() {
140        let external_costs = ExternalCosts {
141            meta: Bo4eMeta::with_type("Fremdkosten"),
142            designation: Some("Test External Costs".to_string()),
143            total_amount: Some(Amount::eur(100.0)),
144            ..Default::default()
145        };
146
147        let json = serde_json::to_string(&external_costs).unwrap();
148        assert!(json.contains(r#""designation":"Test External Costs""#));
149        assert!(json.contains(r#""_typ":"Fremdkosten""#));
150    }
151
152    #[test]
153    fn test_roundtrip() {
154        let external_costs = ExternalCosts {
155            meta: Bo4eMeta::with_type("Fremdkosten"),
156            designation: Some("Gas Network Costs".to_string()),
157            description: Some("External gas network costs".to_string()),
158            division: Some(Division::Gas),
159            total_amount: Some(Amount::eur(180.0)),
160            ..Default::default()
161        };
162
163        let json = serde_json::to_string(&external_costs).unwrap();
164        let parsed: ExternalCosts = serde_json::from_str(&json).unwrap();
165        assert_eq!(external_costs, parsed);
166    }
167
168    #[test]
169    fn test_bo4e_object_impl() {
170        assert_eq!(ExternalCosts::type_name_german(), "Fremdkosten");
171        assert_eq!(ExternalCosts::type_name_english(), "ExternalCosts");
172    }
173}