bo4e_core/com/
offer_part.rs

1//! Offer part (Angebotsteil) component.
2
3use serde::{Deserialize, Serialize};
4
5use crate::traits::{Bo4eMeta, Bo4eObject};
6
7/// Part of an offer variant.
8///
9/// Aggregates offer positions. Offer parts are typically created for a market location
10/// or delivery address. Contains the quantities and total costs of all offer positions.
11/// A variant consists of at least one offer part.
12///
13/// German: Angebotsteil
14///
15/// # Example
16///
17/// ```rust
18/// use bo4e_core::com::OfferPart;
19///
20/// let part = OfferPart {
21///     request_sub_reference: Some("Lot 1".to_string()),
22///     ..Default::default()
23/// };
24/// ```
25#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct OfferPart {
28    /// BO4E metadata
29    #[serde(flatten)]
30    pub meta: Bo4eMeta,
31
32    /// Sub-reference identifying a sub-chapter of a request, e.g., tender lot (AnfrageSubreferenz)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub request_sub_reference: Option<String>,
35
36    // Note: The following fields would typically reference other COM types
37    // (Angebotsposition, Marktlokation, Menge, Betrag, Zeitraum) which will be added later.
38    // For now, we use simplified representations.
39    /// Number of positions in this offer part
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub position_count: Option<i32>,
42
43    /// Total quantity value for this offer part (simplified - Gesamtmengeangebotsteil)
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub total_quantity_value: Option<f64>,
46
47    /// Total cost value for this offer part (simplified - Gesamtkostenangebotsteil)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub total_cost_value: Option<f64>,
50
51    /// Delivery period start (simplified - Lieferzeitraum)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub delivery_period_start: Option<String>,
54
55    /// Delivery period end (simplified - Lieferzeitraum)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub delivery_period_end: Option<String>,
58}
59
60impl Bo4eObject for OfferPart {
61    fn type_name_german() -> &'static str {
62        "Angebotsteil"
63    }
64
65    fn type_name_english() -> &'static str {
66        "OfferPart"
67    }
68
69    fn meta(&self) -> &Bo4eMeta {
70        &self.meta
71    }
72
73    fn meta_mut(&mut self) -> &mut Bo4eMeta {
74        &mut self.meta
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn test_offer_part_default() {
84        let part = OfferPart::default();
85        assert!(part.request_sub_reference.is_none());
86        assert!(part.total_cost_value.is_none());
87    }
88
89    #[test]
90    fn test_offer_part_serialize() {
91        let part = OfferPart {
92            request_sub_reference: Some("LOT-001".to_string()),
93            position_count: Some(5),
94            total_quantity_value: Some(50000.0),
95            total_cost_value: Some(12500.0),
96            ..Default::default()
97        };
98
99        let json = serde_json::to_string(&part).unwrap();
100        assert!(json.contains(r#""requestSubReference":"LOT-001""#));
101        assert!(json.contains(r#""positionCount":5"#));
102    }
103
104    #[test]
105    fn test_offer_part_roundtrip() {
106        let part = OfferPart {
107            meta: Bo4eMeta::with_type("Angebotsteil"),
108            request_sub_reference: Some("LOT-002".to_string()),
109            position_count: Some(3),
110            total_quantity_value: Some(30000.0),
111            total_cost_value: Some(7500.0),
112            delivery_period_start: Some("2024-01-01T00:00:00+01:00".to_string()),
113            delivery_period_end: Some("2024-12-31T23:59:59+01:00".to_string()),
114        };
115
116        let json = serde_json::to_string(&part).unwrap();
117        let parsed: OfferPart = serde_json::from_str(&json).unwrap();
118        assert_eq!(part, parsed);
119    }
120
121    #[test]
122    fn test_bo4e_object_impl() {
123        assert_eq!(OfferPart::type_name_german(), "Angebotsteil");
124        assert_eq!(OfferPart::type_name_english(), "OfferPart");
125    }
126}