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#[serde(rename_all = "camelCase")]
28pub struct InvoicePosition {
29 #[serde(flatten)]
31 pub meta: Bo4eMeta,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub position_number: Option<i32>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub position_text: Option<String>,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub delivery_period_start: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub delivery_period_end: Option<String>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
53 pub quantity_value: Option<f64>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub unit_price_value: Option<f64>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub total_price_value: Option<f64>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub article_number: Option<String>,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub article_id: Option<String>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub tax_amount_value: Option<f64>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub time_unit: Option<Unit>,
78
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub time_based_quantity_value: Option<f64>,
82}
83
84impl Bo4eObject for InvoicePosition {
85 fn type_name_german() -> &'static str {
86 "Rechnungsposition"
87 }
88
89 fn type_name_english() -> &'static str {
90 "InvoicePosition"
91 }
92
93 fn meta(&self) -> &Bo4eMeta {
94 &self.meta
95 }
96
97 fn meta_mut(&mut self) -> &mut Bo4eMeta {
98 &mut self.meta
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_invoice_position_default() {
108 let pos = InvoicePosition::default();
109 assert!(pos.position_number.is_none());
110 assert!(pos.position_text.is_none());
111 }
112
113 #[test]
114 fn test_invoice_position_serialize() {
115 let pos = InvoicePosition {
116 position_number: Some(1),
117 position_text: Some("Electricity consumption".to_string()),
118 quantity_value: Some(1500.0),
119 unit_price_value: Some(0.32),
120 total_price_value: Some(480.0),
121 ..Default::default()
122 };
123
124 let json = serde_json::to_string(&pos).unwrap();
125 assert!(json.contains(r#""positionNumber":1"#));
126 assert!(json.contains(r#""positionText":"Electricity consumption""#));
127 assert!(json.contains(r#""quantityValue":1500"#));
128 }
129
130 #[test]
131 fn test_invoice_position_roundtrip() {
132 let pos = InvoicePosition {
133 meta: Bo4eMeta::with_type("Rechnungsposition"),
134 position_number: Some(2),
135 position_text: Some("Network usage fee".to_string()),
136 delivery_period_start: Some("2024-01-01T00:00:00+01:00".to_string()),
137 delivery_period_end: Some("2024-01-31T23:59:59+01:00".to_string()),
138 quantity_value: Some(1500.0),
139 unit_price_value: Some(0.08),
140 total_price_value: Some(120.0),
141 article_number: Some("9900001000013".to_string()),
142 article_id: None,
143 tax_amount_value: Some(22.80),
144 time_unit: None,
145 time_based_quantity_value: None,
146 };
147
148 let json = serde_json::to_string(&pos).unwrap();
149 let parsed: InvoicePosition = serde_json::from_str(&json).unwrap();
150 assert_eq!(pos, parsed);
151 }
152
153 #[test]
154 fn test_bo4e_object_impl() {
155 assert_eq!(InvoicePosition::type_name_german(), "Rechnungsposition");
156 assert_eq!(InvoicePosition::type_name_english(), "InvoicePosition");
157 }
158}