bo4e_core/com/
cost_position.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::enums::Unit;
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9use super::{Amount, Price};
10
11#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct CostPosition {
29 #[serde(flatten)]
31 pub meta: Bo4eMeta,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub title: Option<String>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub amount: Option<Amount>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub article_description: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub unit_price: Option<Price>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub start_date: Option<DateTime<Utc>>,
52
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub end_date: Option<DateTime<Utc>>,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub quantity_value: Option<f64>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub quantity_unit: Option<Unit>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub time_quantity_value: Option<f64>,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub time_quantity_unit: Option<Unit>,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub article_detail: Option<String>,
76}
77
78impl Bo4eObject for CostPosition {
79 fn type_name_german() -> &'static str {
80 "Kostenposition"
81 }
82
83 fn type_name_english() -> &'static str {
84 "CostPosition"
85 }
86
87 fn meta(&self) -> &Bo4eMeta {
88 &self.meta
89 }
90
91 fn meta_mut(&mut self) -> &mut Bo4eMeta {
92 &mut self.meta
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::enums::Currency;
100
101 #[test]
102 fn test_cost_position() {
103 let position = CostPosition {
104 title: Some("Netznutzung".to_string()),
105 article_description: Some("Arbeitspreis".to_string()),
106 unit_price: Some(Price::eur_per_kwh(0.0582)),
107 quantity_value: Some(3660.0),
108 quantity_unit: Some(Unit::KilowattHour),
109 amount: Some(Amount::eur(213.01)),
110 ..Default::default()
111 };
112
113 assert_eq!(position.title, Some("Netznutzung".to_string()));
114 assert_eq!(position.quantity_value, Some(3660.0));
115 }
116
117 #[test]
118 fn test_default() {
119 let position = CostPosition::default();
120 assert!(position.title.is_none());
121 assert!(position.amount.is_none());
122 }
123
124 #[test]
125 fn test_serialize() {
126 let position = CostPosition {
127 title: Some("Stromsteuer".to_string()),
128 amount: Some(Amount {
129 value: Some(100.0),
130 currency: Some(Currency::Eur),
131 ..Default::default()
132 }),
133 ..Default::default()
134 };
135
136 let json = serde_json::to_string(&position).unwrap();
137 assert!(json.contains(r#""title":"Stromsteuer""#));
138 }
139
140 #[test]
141 fn test_roundtrip() {
142 let position = CostPosition {
143 title: Some("Netzentgelt".to_string()),
144 article_description: Some("Leistungspreis".to_string()),
145 unit_price: Some(Price::eur_per_month(55.0)),
146 amount: Some(Amount::eur(660.0)),
147 ..Default::default()
148 };
149
150 let json = serde_json::to_string(&position).unwrap();
151 let parsed: CostPosition = serde_json::from_str(&json).unwrap();
152 assert_eq!(position, parsed);
153 }
154
155 #[test]
156 fn test_bo4e_object_impl() {
157 assert_eq!(CostPosition::type_name_german(), "Kostenposition");
158 assert_eq!(CostPosition::type_name_english(), "CostPosition");
159 }
160}