bo4e_core/com/
price.rs

1//! Price (Preis) component.
2
3use serde::{Deserialize, Serialize};
4
5use crate::enums::{Currency, PriceStatus, PriceType, Unit};
6use crate::traits::{Bo4eMeta, Bo4eObject};
7
8/// A price with value, currency, and unit.
9///
10/// German: Preis
11///
12/// # Example
13///
14/// ```rust
15/// use bo4e_core::com::Price;
16/// use bo4e_core::enums::{Currency, Unit};
17///
18/// let price = Price {
19///     value: Some(0.25),
20///     currency: Some(Currency::Eur),
21///     reference_unit: Some(Unit::KilowattHour),
22///     ..Default::default()
23/// };
24/// ```
25#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct Price {
28    /// BO4E metadata
29    #[serde(flatten)]
30    pub meta: Bo4eMeta,
31
32    /// Price value (Wert)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub value: Option<f64>,
35
36    /// Currency (Waehrung)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub currency: Option<Currency>,
39
40    /// Unit that the price applies to (Bezugswert)
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub reference_unit: Option<Unit>,
43
44    /// Type of price (Preistyp)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub price_type: Option<PriceType>,
47
48    /// Status of the price (Preisstatus)
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub status: Option<PriceStatus>,
51}
52
53impl Bo4eObject for Price {
54    fn type_name_german() -> &'static str {
55        "Preis"
56    }
57
58    fn type_name_english() -> &'static str {
59        "Price"
60    }
61
62    fn meta(&self) -> &Bo4eMeta {
63        &self.meta
64    }
65
66    fn meta_mut(&mut self) -> &mut Bo4eMeta {
67        &mut self.meta
68    }
69}
70
71impl Price {
72    /// Create a price in EUR per kWh.
73    pub fn eur_per_kwh(value: f64) -> Self {
74        Self {
75            value: Some(value),
76            currency: Some(Currency::Eur),
77            reference_unit: Some(Unit::KilowattHour),
78            ..Default::default()
79        }
80    }
81
82    /// Create a price in EUR per month (base price).
83    pub fn eur_per_month(value: f64) -> Self {
84        Self {
85            value: Some(value),
86            currency: Some(Currency::Eur),
87            reference_unit: Some(Unit::Month),
88            price_type: Some(PriceType::BasePrice),
89            ..Default::default()
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_energy_price() {
100        let price = Price::eur_per_kwh(0.30);
101        assert_eq!(price.value, Some(0.30));
102        assert_eq!(price.currency, Some(Currency::Eur));
103        assert_eq!(price.reference_unit, Some(Unit::KilowattHour));
104    }
105
106    #[test]
107    fn test_base_price() {
108        let price = Price::eur_per_month(12.50);
109        assert_eq!(price.value, Some(12.50));
110        assert_eq!(price.currency, Some(Currency::Eur));
111        assert_eq!(price.reference_unit, Some(Unit::Month));
112        assert_eq!(price.price_type, Some(PriceType::BasePrice));
113    }
114
115    #[test]
116    fn test_serialize() {
117        let price = Price {
118            value: Some(12.50),
119            currency: Some(Currency::Eur),
120            ..Default::default()
121        };
122
123        let json = serde_json::to_string(&price).unwrap();
124        assert!(json.contains(r#""value":12.5"#));
125        assert!(json.contains(r#""currency":"EUR""#));
126    }
127
128    #[test]
129    fn test_roundtrip() {
130        let price = Price::eur_per_kwh(0.2567);
131        let json = serde_json::to_string(&price).unwrap();
132        let parsed: Price = serde_json::from_str(&json).unwrap();
133        assert_eq!(price, parsed);
134    }
135
136    #[test]
137    fn test_bo4e_object_impl() {
138        assert_eq!(Price::type_name_german(), "Preis");
139        assert_eq!(Price::type_name_english(), "Price");
140    }
141}