use bon::Builder;
use serde::{Deserialize, Serialize};
use crate::types::validate_fields;
use crate::types::{
CiString, CountryCode, Currency, DateTime, Extensions, Number, OcpiString, PartyId, PartyRef, Validate,
Validator, ViolationCode,
};
use super::cdrs::{AuthMethod, CdrDimensionType, CdrToken, ChargingPeriod};
use super::types::Price;
pub use crate::v2_3_0::sessions::{
ChargingPreferences, ChargingPreferencesResponse, ProfileType, SessionStatus,
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[builder(on(_, into))]
pub struct Session {
pub country_code: CountryCode,
pub party_id: PartyId,
pub id: CiString<36>,
pub start_date_time: DateTime,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub end_date_time: Option<DateTime>,
pub kwh: Number,
pub cdr_token: CdrToken,
pub auth_method: AuthMethod,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub authorization_reference: Option<CiString<36>>,
pub location_id: CiString<36>,
pub evse_uid: CiString<36>,
pub connector_id: CiString<36>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub meter_id: Option<OcpiString<255>>,
pub currency: Currency,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[builder(default)]
pub charging_periods: Vec<ChargingPeriod>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub total_cost: Option<Price>,
pub status: SessionStatus,
pub last_updated: DateTime,
#[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
#[builder(default)]
pub extensions: Extensions,
}
impl Session {
#[must_use]
pub fn owner_party(&self) -> PartyRef {
PartyRef { country_code: self.country_code.clone(), party_id: self.party_id.clone() }
}
#[must_use]
pub fn has_assigned_evse(&self) -> bool {
!self.evse_uid.is_not_available() && !self.connector_id.is_not_available()
}
#[must_use]
pub fn dimension_total(&self, dimension: CdrDimensionType) -> Number {
self.charging_periods
.iter()
.flat_map(|p| p.dimensions.iter())
.filter(|d| d.dimension_type == dimension)
.map(|d| d.volume)
.sum()
}
}
impl Validate for Session {
fn validate_in(&self, v: &mut Validator) {
validate_fields!(
self,
v,
country_code,
party_id,
id,
start_date_time,
end_date_time,
kwh,
cdr_token,
auth_method,
authorization_reference,
location_id,
evse_uid,
connector_id,
meter_id,
currency,
charging_periods,
total_cost,
status,
last_updated,
);
crate::v2_3_0::cdrs::validate_period_sequence(
&self.charging_periods.iter().map(|p| p.start_date_time).collect::<Vec<_>>(),
self.start_date_time,
self.end_date_time,
v,
);
if self.end_date_time.is_some_and(|end| end < self.start_date_time) {
v.report_at(
"end_date_time",
ViolationCode::Inconsistent,
"a session cannot end before it starts",
);
}
if self.kwh.is_negative() {
v.report_at("kwh", ViolationCode::OutOfRange, "a session cannot charge negative energy");
}
if self.status == SessionStatus::Completed && self.end_date_time.is_none() {
v.report_at(
"end_date_time",
ViolationCode::MissingConditional,
"a COMPLETED session has finished and should carry the time it finished",
);
}
}
}