Skip to main content

bo4e_core/bo/
contract.rs

1//! Contract (Vertrag) business object.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::com::{ContractConditions, ContractPart, TimePeriod};
7use crate::enums::{ContractStatus, ContractType, Division};
8use crate::traits::{Bo4eMeta, Bo4eObject};
9
10/// A contract between parties.
11///
12/// German: Vertrag
13///
14/// # Example
15///
16/// ```rust
17/// use bo4e_core::bo::Contract;
18/// use bo4e_core::enums::{ContractStatus, ContractType, Division};
19///
20/// let contract = Contract {
21///     contract_number: Some("V-2024-001".to_string()),
22///     contract_type: Some(ContractType::EnergySupplyContract),
23///     status: Some(ContractStatus::Active),
24///     division: Some(Division::Electricity),
25///     ..Default::default()
26/// };
27/// ```
28#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
29#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
30#[cfg_attr(feature = "json-schema", schemars(rename = "Vertrag"))]
31#[serde(rename_all = "camelCase")]
32pub struct Contract {
33    /// BO4E metadata
34    #[serde(flatten)]
35    pub meta: Bo4eMeta,
36
37    /// Contract number (Vertragsnummer)
38    #[serde(skip_serializing_if = "Option::is_none")]
39    #[cfg_attr(feature = "json-schema", schemars(rename = "vertragsnummer"))]
40    pub contract_number: Option<String>,
41
42    /// Description (Beschreibung)
43    #[serde(skip_serializing_if = "Option::is_none")]
44    #[cfg_attr(feature = "json-schema", schemars(rename = "beschreibung"))]
45    pub description: Option<String>,
46
47    /// Type of contract (Vertragsart)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    #[cfg_attr(feature = "json-schema", schemars(rename = "vertragsart"))]
50    pub contract_type: Option<ContractType>,
51
52    /// Status of contract (Vertragsstatus)
53    #[serde(skip_serializing_if = "Option::is_none")]
54    #[cfg_attr(feature = "json-schema", schemars(rename = "vertragsstatus"))]
55    pub status: Option<ContractStatus>,
56
57    /// Energy division (Sparte)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
60    pub division: Option<Division>,
61
62    /// Contract start date (Vertragsbeginn)
63    #[serde(skip_serializing_if = "Option::is_none")]
64    #[cfg_attr(feature = "json-schema", schemars(rename = "vertragsbeginn"))]
65    pub contract_start: Option<DateTime<Utc>>,
66
67    /// Contract end date (Vertragsende)
68    #[serde(skip_serializing_if = "Option::is_none")]
69    #[cfg_attr(feature = "json-schema", schemars(rename = "vertragsende"))]
70    pub contract_end: Option<DateTime<Utc>>,
71
72    /// Signing date (Unterzeichnungsdatum)
73    #[serde(skip_serializing_if = "Option::is_none")]
74    #[cfg_attr(feature = "json-schema", schemars(rename = "unterzeichnungsdatum"))]
75    pub signing_date: Option<DateTime<Utc>>,
76
77    /// Validity period (Gueltigkeitszeitraum)
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[cfg_attr(feature = "json-schema", schemars(rename = "gueltigkeitszeitraum"))]
80    pub validity_period: Option<TimePeriod>,
81
82    /// Contract conditions (Vertragskonditionen)
83    #[serde(skip_serializing_if = "Option::is_none")]
84    #[cfg_attr(feature = "json-schema", schemars(rename = "vertragskonditionen"))]
85    pub conditions: Option<ContractConditions>,
86
87    /// Contract parts (Vertragsteile)
88    #[serde(default, skip_serializing_if = "Vec::is_empty")]
89    #[cfg_attr(feature = "json-schema", schemars(rename = "vertragsteile"))]
90    pub parts: Vec<ContractPart>,
91
92    /// Contracting party (Vertragspartner)
93    #[serde(skip_serializing_if = "Option::is_none")]
94    #[cfg_attr(feature = "json-schema", schemars(rename = "vertragspartner"))]
95    pub contract_partner: Option<Box<super::BusinessPartner>>,
96}
97
98impl Bo4eObject for Contract {
99    fn type_name_german() -> &'static str {
100        "Vertrag"
101    }
102
103    fn type_name_english() -> &'static str {
104        "Contract"
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_supply_contract() {
122        let contract = Contract {
123            contract_number: Some("V-2024-001".to_string()),
124            contract_type: Some(ContractType::EnergySupplyContract),
125            status: Some(ContractStatus::Active),
126            division: Some(Division::Electricity),
127            ..Default::default()
128        };
129
130        assert_eq!(contract.status, Some(ContractStatus::Active));
131    }
132
133    #[test]
134    fn test_serialize() {
135        let contract = Contract {
136            meta: Bo4eMeta::with_type("Vertrag"),
137            contract_number: Some("V-123".to_string()),
138            ..Default::default()
139        };
140
141        let json = serde_json::to_string(&contract).unwrap();
142        assert!(json.contains(r#""contractNumber":"V-123""#));
143    }
144
145    #[test]
146    fn test_roundtrip() {
147        let contract = Contract {
148            meta: Bo4eMeta::with_type("Vertrag"),
149            contract_number: Some("V-123".to_string()),
150            description: Some("Test contract".to_string()),
151            contract_type: Some(ContractType::EnergySupplyContract),
152            status: Some(ContractStatus::Active),
153            division: Some(Division::Electricity),
154            ..Default::default()
155        };
156
157        let json = serde_json::to_string(&contract).unwrap();
158        let parsed: Contract = serde_json::from_str(&json).unwrap();
159        assert_eq!(contract, parsed);
160    }
161
162    #[test]
163    fn test_bo4e_object_impl() {
164        assert_eq!(Contract::type_name_german(), "Vertrag");
165        assert_eq!(Contract::type_name_english(), "Contract");
166    }
167}