bo4e_core/com/
contract_part.rs

1//! Contract part (Vertragsteil) component.
2
3use serde::{Deserialize, Serialize};
4
5use crate::traits::{Bo4eMeta, Bo4eObject};
6
7/// Part of a contract linking a service to a location.
8///
9/// Used to represent a contractual service in relation to a location
10/// (market or metering location). Contracts for multiple locations are
11/// modeled with multiple contract parts.
12///
13/// German: Vertragsteil
14///
15/// # Example
16///
17/// ```rust
18/// use bo4e_core::com::ContractPart;
19///
20/// let part = ContractPart {
21///     location_id: Some("DE0001234567890123456789012345678".to_string()),
22///     contract_part_start: Some("2024-01-01T00:00:00+01:00".to_string()),
23///     contract_part_end: Some("2024-12-31T23:59:59+01:00".to_string()),
24///     ..Default::default()
25/// };
26/// ```
27#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct ContractPart {
30    /// BO4E metadata
31    #[serde(flatten)]
32    pub meta: Bo4eMeta,
33
34    /// Start of the contract part validity (inclusive) (Vertragsteilbeginn)
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub contract_part_start: Option<String>,
37
38    /// End of the contract part validity (exclusive) (Vertragsteilende)
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub contract_part_end: Option<String>,
41
42    /// Identifier for the market or metering location belonging to this contract part (Lokation)
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub location_id: Option<String>,
45
46    // Note: The following fields would typically reference Menge COM type.
47    // Using simplified f64 values for now.
48    /// Contractually fixed consumption quantity (Vertraglich fixierte Menge)
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub fixed_quantity_value: Option<f64>,
51
52    /// Minimum consumption quantity (inclusive) (Minimale Abnahmemenge)
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub minimum_quantity_value: Option<f64>,
55
56    /// Maximum consumption quantity (exclusive) (Maximale Abnahmemenge)
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub maximum_quantity_value: Option<f64>,
59}
60
61impl Bo4eObject for ContractPart {
62    fn type_name_german() -> &'static str {
63        "Vertragsteil"
64    }
65
66    fn type_name_english() -> &'static str {
67        "ContractPart"
68    }
69
70    fn meta(&self) -> &Bo4eMeta {
71        &self.meta
72    }
73
74    fn meta_mut(&mut self) -> &mut Bo4eMeta {
75        &mut self.meta
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_contract_part_default() {
85        let part = ContractPart::default();
86        assert!(part.location_id.is_none());
87        assert!(part.contract_part_start.is_none());
88    }
89
90    #[test]
91    fn test_contract_part_serialize() {
92        let part = ContractPart {
93            location_id: Some("DE00012345678901234567890123456".to_string()),
94            contract_part_start: Some("2024-01-01T00:00:00+01:00".to_string()),
95            contract_part_end: Some("2024-12-31T23:59:59+01:00".to_string()),
96            fixed_quantity_value: Some(50000.0),
97            ..Default::default()
98        };
99
100        let json = serde_json::to_string(&part).unwrap();
101        assert!(json.contains(r#""locationId":"DE00012345678901234567890123456""#));
102        assert!(json.contains(r#""contractPartStart":"#));
103        assert!(json.contains(r#""fixedQuantityValue":50000"#));
104    }
105
106    #[test]
107    fn test_contract_part_roundtrip() {
108        let part = ContractPart {
109            meta: Bo4eMeta::with_type("Vertragsteil"),
110            contract_part_start: Some("2024-06-01T00:00:00+02:00".to_string()),
111            contract_part_end: Some("2025-05-31T23:59:59+02:00".to_string()),
112            location_id: Some("MALO-12345".to_string()),
113            fixed_quantity_value: Some(100000.0),
114            minimum_quantity_value: Some(80000.0),
115            maximum_quantity_value: Some(120000.0),
116        };
117
118        let json = serde_json::to_string(&part).unwrap();
119        let parsed: ContractPart = serde_json::from_str(&json).unwrap();
120        assert_eq!(part, parsed);
121    }
122
123    #[test]
124    fn test_bo4e_object_impl() {
125        assert_eq!(ContractPart::type_name_german(), "Vertragsteil");
126        assert_eq!(ContractPart::type_name_english(), "ContractPart");
127    }
128}