bo4e_core/bo/
costs.rs

1//! Costs (Kosten) business object.
2
3use serde::{Deserialize, Serialize};
4
5use crate::com::{Amount, CostBlock, TimePeriod};
6use crate::enums::Division;
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9/// A cost breakdown/summary.
10///
11/// German: Kosten
12///
13/// # Example
14///
15/// ```rust
16/// use bo4e_core::bo::Costs;
17/// use bo4e_core::com::Amount;
18/// use bo4e_core::enums::Division;
19///
20/// let costs = Costs {
21///     designation: Some("Jahreskosten 2024".to_string()),
22///     division: Some(Division::Electricity),
23///     total_amount: Some(Amount::eur(2500.00)),
24///     ..Default::default()
25/// };
26/// ```
27#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct Costs {
30    /// BO4E metadata
31    #[serde(flatten)]
32    pub meta: Bo4eMeta,
33
34    /// Name/designation of the cost summary (Bezeichnung)
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub designation: Option<String>,
37
38    /// Description (Beschreibung)
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub description: Option<String>,
41
42    /// Energy division (Sparte)
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub division: Option<Division>,
45
46    /// Period the costs apply to (Abrechnungszeitraum)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub period: Option<TimePeriod>,
49
50    /// Total amount (Gesamtbetrag)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub total_amount: Option<Amount>,
53
54    /// Cost blocks (Kostenbloecke)
55    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56    pub cost_blocks: Vec<CostBlock>,
57
58    /// Related market location (Marktlokation)
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub market_location: Option<Box<super::MarketLocation>>,
61}
62
63impl Bo4eObject for Costs {
64    fn type_name_german() -> &'static str {
65        "Kosten"
66    }
67
68    fn type_name_english() -> &'static str {
69        "Costs"
70    }
71
72    fn meta(&self) -> &Bo4eMeta {
73        &self.meta
74    }
75
76    fn meta_mut(&mut self) -> &mut Bo4eMeta {
77        &mut self.meta
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::com::CostBlock;
85
86    #[test]
87    fn test_costs_creation() {
88        let costs = Costs {
89            designation: Some("Jahreskosten 2024".to_string()),
90            division: Some(Division::Electricity),
91            total_amount: Some(Amount::eur(2500.00)),
92            ..Default::default()
93        };
94
95        assert_eq!(costs.total_amount, Some(Amount::eur(2500.00)));
96    }
97
98    #[test]
99    fn test_costs_with_blocks() {
100        let costs = Costs {
101            designation: Some("Annual Costs".to_string()),
102            cost_blocks: vec![
103                CostBlock {
104                    designation: Some("Energy".to_string()),
105                    total_amount: Some(Amount::eur(1500.0)),
106                    ..Default::default()
107                },
108                CostBlock {
109                    designation: Some("Network".to_string()),
110                    total_amount: Some(Amount::eur(800.0)),
111                    ..Default::default()
112                },
113            ],
114            total_amount: Some(Amount::eur(2300.0)),
115            ..Default::default()
116        };
117
118        assert_eq!(costs.cost_blocks.len(), 2);
119    }
120
121    #[test]
122    fn test_serialize() {
123        let costs = Costs {
124            meta: Bo4eMeta::with_type("Kosten"),
125            designation: Some("Test Costs".to_string()),
126            total_amount: Some(Amount::eur(1000.0)),
127            ..Default::default()
128        };
129
130        let json = serde_json::to_string(&costs).unwrap();
131        assert!(json.contains(r#""designation":"Test Costs""#));
132        assert!(json.contains(r#""_typ":"Kosten""#));
133    }
134
135    #[test]
136    fn test_roundtrip() {
137        let costs = Costs {
138            meta: Bo4eMeta::with_type("Kosten"),
139            designation: Some("Gas Costs".to_string()),
140            description: Some("Annual gas costs".to_string()),
141            division: Some(Division::Gas),
142            total_amount: Some(Amount::eur(1234.56)),
143            ..Default::default()
144        };
145
146        let json = serde_json::to_string(&costs).unwrap();
147        let parsed: Costs = serde_json::from_str(&json).unwrap();
148        assert_eq!(costs, parsed);
149    }
150
151    #[test]
152    fn test_bo4e_object_impl() {
153        assert_eq!(Costs::type_name_german(), "Kosten");
154        assert_eq!(Costs::type_name_english(), "Costs");
155    }
156}