1use serde::{Deserialize, Serialize};
4
5use crate::com::{Amount, CostBlock, TimePeriod};
6use crate::enums::Division;
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
28#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
29#[cfg_attr(feature = "json-schema", schemars(rename = "Kosten"))]
30#[serde(rename_all = "camelCase")]
31pub struct Costs {
32 #[serde(flatten)]
34 pub meta: Bo4eMeta,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 #[cfg_attr(feature = "json-schema", schemars(rename = "bezeichnung"))]
39 pub designation: Option<String>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
43 #[cfg_attr(feature = "json-schema", schemars(rename = "beschreibung"))]
44 pub description: Option<String>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
49 pub division: Option<Division>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 #[cfg_attr(feature = "json-schema", schemars(rename = "abrechnungszeitraum"))]
54 pub period: Option<TimePeriod>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 #[cfg_attr(feature = "json-schema", schemars(rename = "gesamtbetrag"))]
59 pub total_amount: Option<Amount>,
60
61 #[serde(default, skip_serializing_if = "Vec::is_empty")]
63 #[cfg_attr(feature = "json-schema", schemars(rename = "kostenbloecke"))]
64 pub cost_blocks: Vec<CostBlock>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 #[cfg_attr(feature = "json-schema", schemars(rename = "marktlokation"))]
69 pub market_location: Option<Box<super::MarketLocation>>,
70}
71
72impl Bo4eObject for Costs {
73 fn type_name_german() -> &'static str {
74 "Kosten"
75 }
76
77 fn type_name_english() -> &'static str {
78 "Costs"
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 use crate::com::CostBlock;
94
95 #[test]
96 fn test_costs_creation() {
97 let costs = Costs {
98 designation: Some("Jahreskosten 2024".to_string()),
99 division: Some(Division::Electricity),
100 total_amount: Some(Amount::eur(2500.00)),
101 ..Default::default()
102 };
103
104 assert_eq!(costs.total_amount, Some(Amount::eur(2500.00)));
105 }
106
107 #[test]
108 fn test_costs_with_blocks() {
109 let costs = Costs {
110 designation: Some("Annual Costs".to_string()),
111 cost_blocks: vec![
112 CostBlock {
113 designation: Some("Energy".to_string()),
114 total_amount: Some(Amount::eur(1500.0)),
115 ..Default::default()
116 },
117 CostBlock {
118 designation: Some("Network".to_string()),
119 total_amount: Some(Amount::eur(800.0)),
120 ..Default::default()
121 },
122 ],
123 total_amount: Some(Amount::eur(2300.0)),
124 ..Default::default()
125 };
126
127 assert_eq!(costs.cost_blocks.len(), 2);
128 }
129
130 #[test]
131 fn test_serialize() {
132 let costs = Costs {
133 meta: Bo4eMeta::with_type("Kosten"),
134 designation: Some("Test Costs".to_string()),
135 total_amount: Some(Amount::eur(1000.0)),
136 ..Default::default()
137 };
138
139 let json = serde_json::to_string(&costs).unwrap();
140 assert!(json.contains(r#""designation":"Test Costs""#));
141 assert!(json.contains(r#""_typ":"Kosten""#));
142 }
143
144 #[test]
145 fn test_roundtrip() {
146 let costs = Costs {
147 meta: Bo4eMeta::with_type("Kosten"),
148 designation: Some("Gas Costs".to_string()),
149 description: Some("Annual gas costs".to_string()),
150 division: Some(Division::Gas),
151 total_amount: Some(Amount::eur(1234.56)),
152 ..Default::default()
153 };
154
155 let json = serde_json::to_string(&costs).unwrap();
156 let parsed: Costs = serde_json::from_str(&json).unwrap();
157 assert_eq!(costs, parsed);
158 }
159
160 #[test]
161 fn test_bo4e_object_impl() {
162 assert_eq!(Costs::type_name_german(), "Kosten");
163 assert_eq!(Costs::type_name_english(), "Costs");
164 }
165}