#[cfg(test)]
mod test_from_schema;
use std::convert::Infallible;
use chrono::{DateTime, TimeDelta, Utc};
use rust_decimal::Decimal;
use super::super::Consumed;
use crate::{
country, currency,
number::FromDecimal as _,
price::{Period, Warning},
schema::{self, HasElement as _},
string,
warning::{self, GatherWarnings as _, IntoCaveat as _, IntoInfallible as _},
Ampere, FromSchema, Kw, Kwh, Price, ToDuration as _, Verdict,
};
#[derive(Debug)]
pub struct Cdr {
pub start_date_time: DateTime<Utc>,
pub end_date_time: DateTime<Utc>,
pub charging_periods: Vec<ChargingPeriod>,
pub totals: Totals,
}
#[derive(Debug)]
pub struct Totals {
pub cost: Price,
pub fixed_cost: Option<Price>,
pub energy: Kwh,
pub energy_cost: Option<Price>,
pub duration_charging: TimeDelta,
pub duration_charging_cost: Option<Price>,
pub duration_idle: Option<TimeDelta>,
pub duration_idle_cost: Option<Price>,
}
#[derive(Debug, Clone)]
pub(crate) struct Dimension {
pub dimension_type: DimensionType,
pub volume: Decimal,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DimensionType {
Energy,
MaxCurrent,
MinCurrent,
MaxPower,
MinPower,
ParkingTime,
ReservationTime,
Time,
Current,
EnergyExport,
EnergyImport,
Power,
StateOfCharge,
}
impl FromSchema<'_, schema::v221::CdrDimensionType> for DimensionType {
type Warning = Infallible;
fn from_schema(source: &schema::v221::CdrDimensionType) -> Verdict<Self, Self::Warning> {
use schema::v221::CdrDimensionType;
let v = match source {
CdrDimensionType::Energy => Self::Energy,
CdrDimensionType::MaxCurrent => Self::MaxCurrent,
CdrDimensionType::MinCurrent => Self::MinCurrent,
CdrDimensionType::ParkingTime => Self::ParkingTime,
CdrDimensionType::Time => Self::Time,
CdrDimensionType::Current => Self::Current,
CdrDimensionType::EnergyExport => Self::EnergyExport,
CdrDimensionType::EnergyImport => Self::EnergyImport,
CdrDimensionType::MaxPower => Self::MaxPower,
CdrDimensionType::MinPower => Self::MinPower,
CdrDimensionType::Power => Self::Power,
CdrDimensionType::ReservationTime => Self::ReservationTime,
CdrDimensionType::StateOfCharge => Self::StateOfCharge,
};
Ok(v.into_infallible_caveat())
}
}
#[derive(Clone, Debug)]
pub struct ChargingPeriod {
pub start_date_time: DateTime<Utc>,
pub dimensions: Vec<Dimension>,
}
impl<'buf> FromSchema<'buf, schema::v221::Cdr<'buf>> for Cdr {
type Warning = Warning;
fn from_schema(source: &schema::v221::Cdr<'buf>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
if let schema::Integrity::Ok(country_code) = &source.country_code {
let code_set =
country::CodeSet::from_schema(country_code).gather_warnings_into(&mut warnings)?;
if let country::CodeSet::Alpha3(_) = code_set {
warnings.insert(country_code.element(), Warning::CountryShouldBeAlpha2);
}
}
if let schema::Integrity::Ok(currency) = &source.currency {
let _ignore_value =
currency::Code::from_schema(currency).gather_warnings_into(&mut warnings);
}
if let schema::Integrity::Ok(party_id) = &source.party_id {
let _ignore_value = string::CiExactLen::<'_, 3>::from_schema(party_id)
.gather_warnings_into(&mut warnings);
}
let start_date_time = warnings.ok_or_bail(&source.start_date_time)?;
let start_date_time =
DateTime::<Utc>::from_schema(start_date_time)?.gather_warnings_into(&mut warnings);
let end_date_time = warnings.ok_or_bail(&source.end_date_time)?;
let end_date_time =
DateTime::<Utc>::from_schema(end_date_time)?.gather_warnings_into(&mut warnings);
let periods = warnings.ok_or_bail(&source.charging_periods)?;
let mut charging_periods = Vec::with_capacity(periods.len());
for period in periods {
let period = warnings.ok_or_bail(period)?;
charging_periods
.push(ChargingPeriod::from_schema(period)?.gather_warnings_into(&mut warnings));
}
charging_periods.sort_unstable_by_key(|period| period.start_date_time);
let total_cost = warnings.ok_or_bail(&source.total_cost)?;
let total_cost = Price::from_schema(total_cost)?.gather_warnings_into(&mut warnings);
let total_energy = warnings.ok_or_bail(&source.total_energy)?;
let total_energy = Kwh::from_schema(total_energy)?.gather_warnings_into(&mut warnings);
let total_time = warnings.ok_or_bail(&source.total_time)?;
let total_time = Decimal::from_schema(total_time)?.gather_warnings_into(&mut warnings);
let total_parking_time = warnings.ok_or_bail(&source.total_parking_time)?;
let total_parking_time = total_parking_time
.as_ref()
.map(Decimal::from_schema)
.transpose()?
.gather_warnings_into(&mut warnings);
let total_fixed_cost = warnings.ok_or_bail(&source.total_fixed_cost)?;
let total_fixed_cost = total_fixed_cost
.as_ref()
.map(Price::from_schema)
.transpose()?
.gather_warnings_into(&mut warnings);
let total_energy_cost = warnings.ok_or_bail(&source.total_energy_cost)?;
let total_energy_cost = total_energy_cost
.as_ref()
.map(Price::from_schema)
.transpose()?
.gather_warnings_into(&mut warnings);
let total_time_cost = warnings.ok_or_bail(&source.total_time_cost)?;
let total_time_cost = total_time_cost
.as_ref()
.map(Price::from_schema)
.transpose()?
.gather_warnings_into(&mut warnings);
let total_parking_cost = warnings.ok_or_bail(&source.total_parking_cost)?;
let total_parking_cost = total_parking_cost
.as_ref()
.map(Price::from_schema)
.transpose()?
.gather_warnings_into(&mut warnings);
let cdr = Cdr {
start_date_time,
end_date_time,
charging_periods,
totals: Totals {
cost: total_cost,
fixed_cost: total_fixed_cost,
energy: total_energy,
energy_cost: total_energy_cost,
duration_charging: total_time.to_duration(),
duration_charging_cost: total_time_cost,
duration_idle: total_parking_time.map(|d| d.to_duration()),
duration_idle_cost: total_parking_cost,
},
};
Ok(cdr.into_caveat(warnings))
}
}
impl<'buf> FromSchema<'buf, schema::v221::ChargingPeriod<'buf>> for ChargingPeriod {
type Warning = Warning;
fn from_schema(source: &schema::v221::ChargingPeriod<'buf>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
let start_date_time = warnings.ok_or_bail(&source.start_date_time)?;
let start_date_time =
DateTime::<Utc>::from_schema(start_date_time)?.gather_warnings_into(&mut warnings);
let dimensions = warnings.ok_or_bail(&source.dimensions)?;
let mut lowered = Vec::with_capacity(dimensions.len());
for dimension in dimensions {
let dimension = warnings.ok_or_bail(dimension)?;
let dimension =
Option::<Dimension>::from_schema(dimension)?.gather_warnings_into(&mut warnings);
if let Some(dimension) = dimension {
lowered.push(dimension);
}
}
let period = ChargingPeriod {
start_date_time,
dimensions: lowered,
};
Ok(period.into_caveat(warnings))
}
}
impl<'buf> FromSchema<'buf, schema::v221::Dimension<'buf>> for Option<Dimension> {
type Warning = Warning;
fn from_schema(source: &schema::v221::Dimension<'buf>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
if let schema::Integrity::Err(_) = &source.dimension_type {
return Ok(None.into_caveat(warnings));
}
let dimension_type = warnings.ok_or_bail(&source.dimension_type)?;
let dimension_type = DimensionType::from_schema(&dimension_type.value()).into_infallible();
let volume = warnings.ok_or_bail(&source.volume)?;
let volume = Decimal::from_schema(volume)?.gather_warnings_into(&mut warnings);
let dimension = Dimension {
dimension_type,
volume,
};
Ok(Some(dimension).into_caveat(warnings))
}
}
impl From<ChargingPeriod> for Period {
fn from(period: ChargingPeriod) -> Self {
let ChargingPeriod {
start_date_time,
dimensions,
} = period;
let mut consumed = Consumed {
current_max: None,
current_min: None,
duration_charging: None,
duration_idle: None,
energy: None,
power_max: None,
power_min: None,
};
for dimension in dimensions {
let Dimension {
dimension_type,
volume,
} = dimension;
match dimension_type {
DimensionType::MinCurrent => {
consumed.current_min = Some(Ampere::from_decimal(volume));
}
DimensionType::MaxCurrent => {
consumed.current_max = Some(Ampere::from_decimal(volume));
}
DimensionType::MaxPower => {
consumed.power_max = Some(Kw::from_decimal(volume));
}
DimensionType::MinPower => {
consumed.power_min = Some(Kw::from_decimal(volume));
}
DimensionType::Energy => {
consumed.energy = Some(Kwh::from_decimal(volume));
}
DimensionType::Time => {
consumed.duration_charging = Some(volume.to_duration());
}
DimensionType::ParkingTime => {
consumed.duration_idle = Some(volume.to_duration());
}
DimensionType::Current
| DimensionType::EnergyExport
| DimensionType::EnergyImport
| DimensionType::Power
| DimensionType::ReservationTime
| DimensionType::StateOfCharge => {
}
}
}
Period {
start_date_time,
consumed,
}
}
}