#[cfg(test)]
mod test_from_schema;
use std::convert::Infallible;
use chrono::{DateTime, TimeDelta, Utc};
use rust_decimal::Decimal;
use crate::{
number::FromDecimal as _,
price::{v221, Warning},
schema,
warning::{self, GatherWarnings as _, IntoCaveat as _, IntoInfallible as _},
FromSchema, Kwh, Money, Price, ToDuration, Verdict,
};
#[derive(Clone, Debug)]
pub(crate) struct Cdr {
start_date_time: DateTime<Utc>,
stop_date_time: DateTime<Utc>,
charging_periods: Vec<ChargingPeriod>,
total_cost: Decimal,
total_energy: Decimal,
duration_charging: TimeDelta,
duration_idle: Option<TimeDelta>,
}
#[derive(Debug, Clone)]
struct Dimension {
dimension_type: DimensionType,
volume: Decimal,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum DimensionType {
Energy,
Flat,
MaxCurrent,
MinCurrent,
ParkingTime,
Time,
}
impl FromSchema<'_, schema::v211::CdrDimensionType> for DimensionType {
type Warning = Infallible;
fn from_schema(source: &schema::v211::CdrDimensionType) -> Verdict<Self, Self::Warning> {
use schema::v211::CdrDimensionType;
let v = match source {
CdrDimensionType::Energy => Self::Energy,
CdrDimensionType::Flat => Self::Flat,
CdrDimensionType::MaxCurrent => Self::MaxCurrent,
CdrDimensionType::MinCurrent => Self::MinCurrent,
CdrDimensionType::ParkingTime => Self::ParkingTime,
CdrDimensionType::Time => Self::Time,
};
Ok(v.into_infallible_caveat())
}
}
#[derive(Clone, Debug)]
struct ChargingPeriod {
start_date_time: DateTime<Utc>,
dimensions: Vec<Dimension>,
}
impl From<Cdr> for v221::Cdr {
fn from(cdr: Cdr) -> Self {
let Cdr {
start_date_time,
stop_date_time,
charging_periods,
total_cost,
total_energy,
duration_charging,
duration_idle,
} = cdr;
Self {
end_date_time: stop_date_time,
start_date_time,
charging_periods: charging_periods
.into_iter()
.map(ChargingPeriod::into)
.collect(),
totals: v221::cdr::Totals {
cost: Price {
excl_vat: Money::from_decimal(total_cost),
incl_vat: None,
},
energy: Kwh::from_decimal(total_energy),
energy_cost: None,
duration_charging,
duration_charging_cost: None,
fixed_cost: None,
duration_idle,
duration_idle_cost: None,
},
}
}
}
impl From<ChargingPeriod> for v221::cdr::ChargingPeriod {
fn from(period: ChargingPeriod) -> Self {
let ChargingPeriod {
start_date_time,
dimensions,
} = period;
let dimensions = dimensions
.into_iter()
.filter_map(|d| {
let Dimension {
dimension_type,
volume,
} = d;
if let DimensionType::Flat = dimension_type {
return None;
}
let dimension_type = match dimension_type {
DimensionType::Energy => v221::cdr::DimensionType::Energy,
DimensionType::MaxCurrent => v221::cdr::DimensionType::MaxCurrent,
DimensionType::MinCurrent => v221::cdr::DimensionType::MinCurrent,
DimensionType::ParkingTime => v221::cdr::DimensionType::ParkingTime,
DimensionType::Time => v221::cdr::DimensionType::Time,
DimensionType::Flat => return None,
};
Some(v221::cdr::Dimension {
dimension_type,
volume,
})
})
.collect();
Self {
start_date_time,
dimensions,
}
}
}
impl<'buf> FromSchema<'buf, schema::v211::Cdr<'buf>> for Cdr {
type Warning = Warning;
fn from_schema(source: &schema::v211::Cdr<'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 stop_date_time = warnings.ok_or_bail(&source.stop_date_time)?;
let stop_date_time =
DateTime::<Utc>::from_schema(stop_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 = Decimal::from_schema(total_cost)?.gather_warnings_into(&mut warnings);
let total_energy = warnings.ok_or_bail(&source.total_energy)?;
let total_energy = Decimal::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 cdr = Cdr {
start_date_time,
stop_date_time,
charging_periods,
total_cost,
total_energy,
duration_charging: total_time.to_duration(),
duration_idle: total_parking_time.as_ref().map(ToDuration::to_duration),
};
Ok(cdr.into_caveat(warnings))
}
}
impl<'buf> FromSchema<'buf, schema::v211::ChargingPeriod<'buf>> for ChargingPeriod {
type Warning = Warning;
fn from_schema(source: &schema::v211::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::v211::Dimension<'buf>> for Option<Dimension> {
type Warning = Warning;
fn from_schema(source: &schema::v211::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))
}
}