bo4e_core/com/
cost_block.rs1use serde::{Deserialize, Serialize};
4
5use crate::enums::CostClass;
6use crate::traits::{Bo4eMeta, Bo4eObject};
7
8use super::{Amount, CostPosition};
9
10#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
26#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
27#[cfg_attr(feature = "json-schema", schemars(rename = "Kostenblock"))]
28#[serde(rename_all = "camelCase")]
29pub struct CostBlock {
30 #[serde(flatten)]
32 pub meta: Bo4eMeta,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 #[cfg_attr(feature = "json-schema", schemars(rename = "kostenblockbezeichnung"))]
37 pub designation: Option<String>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 #[cfg_attr(feature = "json-schema", schemars(rename = "kostenklasse"))]
42 pub cost_class: Option<CostClass>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 #[cfg_attr(feature = "json-schema", schemars(rename = "summeKostenblock"))]
47 pub total_amount: Option<Amount>,
48
49 #[serde(default, skip_serializing_if = "Vec::is_empty")]
51 #[cfg_attr(feature = "json-schema", schemars(rename = "kostenpositionen"))]
52 pub positions: Vec<CostPosition>,
53}
54
55impl Bo4eObject for CostBlock {
56 fn type_name_german() -> &'static str {
57 "Kostenblock"
58 }
59
60 fn type_name_english() -> &'static str {
61 "CostBlock"
62 }
63
64 fn meta(&self) -> &Bo4eMeta {
65 &self.meta
66 }
67
68 fn meta_mut(&mut self) -> &mut Bo4eMeta {
69 &mut self.meta
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::com::Price;
77
78 #[test]
79 fn test_cost_block() {
80 let cost_block = CostBlock {
81 designation: Some("Netzkosten".to_string()),
82 cost_class: Some(CostClass::ExternalCosts),
83 total_amount: Some(Amount::eur(500.0)),
84 positions: vec![
85 CostPosition {
86 title: Some("Arbeitspreis Netz".to_string()),
87 amount: Some(Amount::eur(300.0)),
88 ..Default::default()
89 },
90 CostPosition {
91 title: Some("Leistungspreis Netz".to_string()),
92 amount: Some(Amount::eur(200.0)),
93 ..Default::default()
94 },
95 ],
96 ..Default::default()
97 };
98
99 assert_eq!(cost_block.designation, Some("Netzkosten".to_string()));
100 assert_eq!(cost_block.positions.len(), 2);
101 }
102
103 #[test]
104 fn test_default() {
105 let cost_block = CostBlock::default();
106 assert!(cost_block.designation.is_none());
107 assert!(cost_block.total_amount.is_none());
108 assert!(cost_block.positions.is_empty());
109 }
110
111 #[test]
112 fn test_serialize() {
113 let cost_block = CostBlock {
114 designation: Some("Messkosten".to_string()),
115 total_amount: Some(Amount::eur(100.0)),
116 ..Default::default()
117 };
118
119 let json = serde_json::to_string(&cost_block).unwrap();
120 assert!(json.contains(r#""designation":"Messkosten""#));
121 }
122
123 #[test]
124 fn test_roundtrip() {
125 let cost_block = CostBlock {
126 designation: Some("Energiekosten".to_string()),
127 cost_class: Some(CostClass::Procurement),
128 total_amount: Some(Amount::eur(1234.56)),
129 positions: vec![CostPosition {
130 title: Some("Arbeitspreis Energie".to_string()),
131 unit_price: Some(Price::eur_per_kwh(0.25)),
132 ..Default::default()
133 }],
134 ..Default::default()
135 };
136
137 let json = serde_json::to_string(&cost_block).unwrap();
138 let parsed: CostBlock = serde_json::from_str(&json).unwrap();
139 assert_eq!(cost_block, parsed);
140 }
141
142 #[test]
143 fn test_bo4e_object_impl() {
144 assert_eq!(CostBlock::type_name_german(), "Kostenblock");
145 assert_eq!(CostBlock::type_name_english(), "CostBlock");
146 }
147}