use rust_decimal::Decimal;
use crate::{
datetime::DvgwPeriod,
document::DvgwMessageType,
message::DvgwMessage,
model::{nad, qty, sts},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Verfahren {
Slp,
Rlm,
}
impl Verfahren {
#[must_use]
pub fn from_sts_code(code: &str) -> Option<Self> {
match code {
sts::SLP => Some(Self::Slp),
sts::RLM => Some(Self::Rlm),
_ => None,
}
}
#[must_use]
pub fn sts_code(self) -> &'static str {
match self {
Self::Slp => sts::SLP,
Self::Rlm => sts::RLM,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SsqnotError {
#[error("not a SSQNOT: the message is {0}")]
NotSsqnot(DvgwMessageType),
#[error("no usable DTM+Z01 Abrechnungszeitraum")]
NoPeriod,
#[error("no SG39 NAD+ZSH Netzkontonummer")]
NoNetzkonto,
#[error("the positions name more than one Netzkonto")]
SeveralNetzkonten,
#[error("no single Verfahren: every QTY needs STS+A1G or STS+A2G, all the same")]
NoVerfahren,
#[error("QTY+{0} is not numeric")]
NotNumeric(String),
#[error("QTY+{0} is neither ZY0 Mehrmenge nor ZY2 Mindermenge")]
UnknownQualifier(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MehrMindermengenmeldung {
pub netzkonto: String,
pub netzbetreiber: String,
pub zeitraum: DvgwPeriod,
pub verfahren: Verfahren,
pub mehrmenge_kwh: Decimal,
pub mindermenge_kwh: Decimal,
}
impl MehrMindermengenmeldung {
pub fn from_message(msg: &DvgwMessage) -> Result<Self, SsqnotError> {
if msg.message_type != DvgwMessageType::Ssqnot {
return Err(SsqnotError::NotSsqnot(msg.message_type));
}
let zeitraum = msg.validity_period.ok_or(SsqnotError::NoPeriod)?;
let netzbetreiber = msg.sender().map(|p| p.id.clone()).unwrap_or_default();
let mut netzkonto: Option<String> = None;
let mut verfahren: Option<Verfahren> = None;
let mut mehrmenge = Decimal::ZERO;
let mut mindermenge = Decimal::ZERO;
for item in &msg.items {
if let Some(party) = item.party(nad::NETZKONTO_ZO_T3) {
match &netzkonto {
Some(known) if known != &party.id => {
return Err(SsqnotError::SeveralNetzkonten);
}
Some(_) => {}
None => netzkonto = Some(party.id.clone()),
}
}
for quantity in item.quantities() {
let this = quantity
.status_code()
.and_then(Verfahren::from_sts_code)
.ok_or(SsqnotError::NoVerfahren)?;
if verfahren.is_some_and(|v| v != this) {
return Err(SsqnotError::NoVerfahren);
}
verfahren = Some(this);
let value = quantity
.value
.ok_or_else(|| SsqnotError::NotNumeric(quantity.qualifier.clone()))?;
match quantity.qualifier.as_str() {
qty::MEHRMENGE => mehrmenge += value,
qty::MINDERMENGE => mindermenge += value,
other => return Err(SsqnotError::UnknownQualifier(other.to_owned())),
}
}
}
Ok(Self {
netzkonto: netzkonto.ok_or(SsqnotError::NoNetzkonto)?,
netzbetreiber,
zeitraum,
verfahren: verfahren.ok_or(SsqnotError::NoVerfahren)?,
mehrmenge_kwh: mehrmenge,
mindermenge_kwh: mindermenge,
})
}
#[must_use]
pub fn saldo_kwh(&self) -> Decimal {
self.mehrmenge_kwh - self.mindermenge_kwh
}
}