1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::com::{OfferVariant, TimePeriod};
7use crate::enums::{Division, OfferStatus};
8use crate::traits::{Bo4eMeta, Bo4eObject};
9
10#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct Offer {
30 #[serde(flatten)]
32 pub meta: Bo4eMeta,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub offer_number: Option<String>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub description: Option<String>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub status: Option<OfferStatus>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub division: Option<Division>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub offer_date: Option<DateTime<Utc>>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub valid_until: Option<DateTime<Utc>>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub delivery_period: Option<TimePeriod>,
61
62 #[serde(default, skip_serializing_if = "Vec::is_empty")]
64 pub variants: Vec<OfferVariant>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub bidder: Option<Box<super::BusinessPartner>>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub customer: Option<Box<super::BusinessPartner>>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub tender_id: Option<String>,
77}
78
79impl Bo4eObject for Offer {
80 fn type_name_german() -> &'static str {
81 "Angebot"
82 }
83
84 fn type_name_english() -> &'static str {
85 "Offer"
86 }
87
88 fn meta(&self) -> &Bo4eMeta {
89 &self.meta
90 }
91
92 fn meta_mut(&mut self) -> &mut Bo4eMeta {
93 &mut self.meta
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_offer_creation() {
103 let offer = Offer {
104 offer_number: Some("A-2024-001".to_string()),
105 status: Some(OfferStatus::Binding),
106 division: Some(Division::Electricity),
107 ..Default::default()
108 };
109
110 assert_eq!(offer.status, Some(OfferStatus::Binding));
111 }
112
113 #[test]
114 fn test_offer_with_variants() {
115 let variant = OfferVariant {
116 offer_status: Some(OfferStatus::NonBinding),
117 parts_count: Some(3),
118 ..Default::default()
119 };
120
121 let offer = Offer {
122 offer_number: Some("A-2024-001".to_string()),
123 variants: vec![variant],
124 ..Default::default()
125 };
126
127 assert_eq!(offer.variants.len(), 1);
128 }
129
130 #[test]
131 fn test_serialize() {
132 let offer = Offer {
133 meta: Bo4eMeta::with_type("Angebot"),
134 offer_number: Some("A-123".to_string()),
135 status: Some(OfferStatus::Binding),
136 ..Default::default()
137 };
138
139 let json = serde_json::to_string(&offer).unwrap();
140 assert!(json.contains(r#""offerNumber":"A-123""#));
141 }
142
143 #[test]
144 fn test_roundtrip() {
145 let offer = Offer {
146 meta: Bo4eMeta::with_type("Angebot"),
147 offer_number: Some("A-123".to_string()),
148 description: Some("Test offer".to_string()),
149 status: Some(OfferStatus::Binding),
150 division: Some(Division::Electricity),
151 ..Default::default()
152 };
153
154 let json = serde_json::to_string(&offer).unwrap();
155 let parsed: Offer = serde_json::from_str(&json).unwrap();
156 assert_eq!(offer, parsed);
157 }
158
159 #[test]
160 fn test_bo4e_object_impl() {
161 assert_eq!(Offer::type_name_german(), "Angebot");
162 assert_eq!(Offer::type_name_english(), "Offer");
163 }
164}