1use bon::Builder;
11use serde::{Deserialize, Serialize};
12
13use crate::types::validate_fields;
14use crate::types::{
15 CiString, CountryCode, Currency, DateTime, Extensions, Number, OcpiString, PartyId, PartyRef, Validate,
16 Validator, ViolationCode,
17};
18
19use super::cdrs::{AuthMethod, CdrDimensionType, CdrToken, ChargingPeriod};
20use super::types::Price;
21
22pub use crate::v2_3_0::sessions::{
24 ChargingPreferences, ChargingPreferencesResponse, ProfileType, SessionStatus,
25};
26
27#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
31#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
32#[builder(on(_, into))]
33pub struct Session {
34 pub country_code: CountryCode,
36 pub party_id: PartyId,
38 pub id: CiString<36>,
40 pub start_date_time: DateTime,
42 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub end_date_time: Option<DateTime>,
45 pub kwh: Number,
47 pub cdr_token: CdrToken,
49 pub auth_method: AuthMethod,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub authorization_reference: Option<CiString<36>>,
54 pub location_id: CiString<36>,
56 pub evse_uid: CiString<36>,
58 pub connector_id: CiString<36>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub meter_id: Option<OcpiString<255>>,
63 pub currency: Currency,
65 #[serde(default, skip_serializing_if = "Vec::is_empty")]
67 #[builder(default)]
68 pub charging_periods: Vec<ChargingPeriod>,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub total_cost: Option<Price>,
72 pub status: SessionStatus,
74 pub last_updated: DateTime,
76 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
78 #[builder(default)]
79 pub extensions: Extensions,
80}
81
82impl Session {
83 #[must_use]
85 pub fn owner_party(&self) -> PartyRef {
86 PartyRef { country_code: self.country_code.clone(), party_id: self.party_id.clone() }
87 }
88
89 #[must_use]
91 pub fn has_assigned_evse(&self) -> bool {
92 !self.evse_uid.is_not_available() && !self.connector_id.is_not_available()
93 }
94
95 #[must_use]
97 pub fn dimension_total(&self, dimension: CdrDimensionType) -> Number {
98 self.charging_periods
99 .iter()
100 .flat_map(|p| p.dimensions.iter())
101 .filter(|d| d.dimension_type == dimension)
102 .map(|d| d.volume)
103 .sum()
104 }
105}
106
107impl Validate for Session {
108 fn validate_in(&self, v: &mut Validator) {
109 validate_fields!(
110 self,
111 v,
112 country_code,
113 party_id,
114 id,
115 start_date_time,
116 end_date_time,
117 kwh,
118 cdr_token,
119 auth_method,
120 authorization_reference,
121 location_id,
122 evse_uid,
123 connector_id,
124 meter_id,
125 currency,
126 charging_periods,
127 total_cost,
128 status,
129 last_updated,
130 );
131 crate::v2_3_0::cdrs::validate_period_sequence(
132 &self.charging_periods.iter().map(|p| p.start_date_time).collect::<Vec<_>>(),
133 self.start_date_time,
134 self.end_date_time,
135 v,
136 );
137 if self.end_date_time.is_some_and(|end| end < self.start_date_time) {
138 v.report_at(
139 "end_date_time",
140 ViolationCode::Inconsistent,
141 "a session cannot end before it starts",
142 );
143 }
144 if self.kwh.is_negative() {
145 v.report_at("kwh", ViolationCode::OutOfRange, "a session cannot charge negative energy");
146 }
147 if self.status == SessionStatus::Completed && self.end_date_time.is_none() {
148 v.report_at(
149 "end_date_time",
150 ViolationCode::MissingConditional,
151 "a COMPLETED session has finished and should carry the time it finished",
152 );
153 }
154 }
155}