bo4e_core/com/
invoice_position.rs1use serde::{Deserialize, Serialize};
4
5use crate::enums::Unit;
6use crate::traits::{Bo4eMeta, Bo4eObject};
7
8#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
27#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
28#[cfg_attr(feature = "json-schema", schemars(rename = "Rechnungsposition"))]
29#[serde(rename_all = "camelCase")]
30pub struct InvoicePosition {
31 #[serde(flatten)]
33 pub meta: Bo4eMeta,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 #[cfg_attr(feature = "json-schema", schemars(rename = "positionsnummer"))]
38 pub position_number: Option<i32>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 #[cfg_attr(feature = "json-schema", schemars(rename = "positionstext"))]
43 pub position_text: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 #[cfg_attr(feature = "json-schema", schemars(rename = "lieferungszeitraumVon"))]
48 pub delivery_period_start: Option<String>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 #[cfg_attr(feature = "json-schema", schemars(rename = "lieferungszeitraumBis"))]
53 pub delivery_period_end: Option<String>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
59 #[cfg_attr(feature = "json-schema", schemars(rename = "positionsmenge"))]
60 pub quantity_value: Option<f64>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 #[cfg_attr(feature = "json-schema", schemars(rename = "einzelpreis"))]
65 pub unit_price_value: Option<f64>,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 #[cfg_attr(feature = "json-schema", schemars(rename = "gesamtpreis"))]
70 pub total_price_value: Option<f64>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 #[cfg_attr(feature = "json-schema", schemars(rename = "artikelnummer"))]
75 pub article_number: Option<String>,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 #[cfg_attr(feature = "json-schema", schemars(rename = "artikelId"))]
80 pub article_id: Option<String>,
81
82 #[serde(skip_serializing_if = "Option::is_none")]
84 #[cfg_attr(feature = "json-schema", schemars(rename = "steuerbetrag"))]
85 pub tax_amount_value: Option<f64>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
89 #[cfg_attr(feature = "json-schema", schemars(rename = "zeiteinheit"))]
90 pub time_unit: Option<Unit>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 #[cfg_attr(feature = "json-schema", schemars(rename = "zeitbezogeneMenge"))]
95 pub time_based_quantity_value: Option<f64>,
96}
97
98impl Bo4eObject for InvoicePosition {
99 fn type_name_german() -> &'static str {
100 "Rechnungsposition"
101 }
102
103 fn type_name_english() -> &'static str {
104 "InvoicePosition"
105 }
106
107 fn meta(&self) -> &Bo4eMeta {
108 &self.meta
109 }
110
111 fn meta_mut(&mut self) -> &mut Bo4eMeta {
112 &mut self.meta
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_invoice_position_default() {
122 let pos = InvoicePosition::default();
123 assert!(pos.position_number.is_none());
124 assert!(pos.position_text.is_none());
125 }
126
127 #[test]
128 fn test_invoice_position_serialize() {
129 let pos = InvoicePosition {
130 position_number: Some(1),
131 position_text: Some("Electricity consumption".to_string()),
132 quantity_value: Some(1500.0),
133 unit_price_value: Some(0.32),
134 total_price_value: Some(480.0),
135 ..Default::default()
136 };
137
138 let json = serde_json::to_string(&pos).unwrap();
139 assert!(json.contains(r#""positionNumber":1"#));
140 assert!(json.contains(r#""positionText":"Electricity consumption""#));
141 assert!(json.contains(r#""quantityValue":1500"#));
142 }
143
144 #[test]
145 fn test_invoice_position_roundtrip() {
146 let pos = InvoicePosition {
147 meta: Bo4eMeta::with_type("Rechnungsposition"),
148 position_number: Some(2),
149 position_text: Some("Network usage fee".to_string()),
150 delivery_period_start: Some("2024-01-01T00:00:00+01:00".to_string()),
151 delivery_period_end: Some("2024-01-31T23:59:59+01:00".to_string()),
152 quantity_value: Some(1500.0),
153 unit_price_value: Some(0.08),
154 total_price_value: Some(120.0),
155 article_number: Some("9900001000013".to_string()),
156 article_id: None,
157 tax_amount_value: Some(22.80),
158 time_unit: None,
159 time_based_quantity_value: None,
160 };
161
162 let json = serde_json::to_string(&pos).unwrap();
163 let parsed: InvoicePosition = serde_json::from_str(&json).unwrap();
164 assert_eq!(pos, parsed);
165 }
166
167 #[test]
168 fn test_bo4e_object_impl() {
169 assert_eq!(InvoicePosition::type_name_german(), "Rechnungsposition");
170 assert_eq!(InvoicePosition::type_name_english(), "InvoicePosition");
171 }
172}