pub const IDE_VORGANG: &str = "24";
pub const IDE_LISTE: &str = "Z01";
pub const BGM_MABIS_ZP_LIFECYCLE: &str = "Z07";
pub const BGM_CLEARINGLISTE: &str = "Z05";
pub const SEQ_SUMMENZEITREIHE: &str = "Z22";
pub const SEQ_DATEN_DER_MARKTLOKATION: &str = "Z01";
pub const SEQ_DATEN_DER_MARKTLOKATION_ANTWORT: &str = "Z98";
pub const SEQ_DATEN_DES_KUNDEN: &str = "Z75";
pub const RFF_ZEITREIHE: &str = "AUU";
pub mod loc {
pub const MELDEPUNKT: &str = "172";
pub const MABIS_ZAEHLPUNKT: &str = "Z15";
pub const MARKTLOKATION: &str = "Z16";
pub const MESSLOKATION: &str = "Z17";
pub const NETZLOKATION: &str = "Z18";
pub const STEUERBARE_RESSOURCE: &str = "Z19";
pub const TECHNISCHE_RESSOURCE: &str = "Z20";
pub const TRANCHE: &str = "Z21";
pub const RUHENDE_MARKTLOKATION: &str = "Z22";
}
pub mod dtm {
pub const LEISTUNGSBEGINN_GEPLANT: &str = "76";
pub const BEGINN_ZUM: &str = "92";
pub const ENDE_ZUM: &str = "93";
pub const BESTAETIGTES_VERTRAGSENDE: &str = "Z05";
pub const AENDERUNG_ZUM: &str = "157";
pub const ENDE_NAECHSTMOEGLICH: &str = "471";
pub const BILANZIERUNGSBEGINN: &str = "158";
pub const BILANZIERUNGSENDE: &str = "159";
pub const UET_LIEFERANMELDUNG: &str = "154";
pub const KUENDIGUNGSFRIST: &str = "Z01";
pub const KUENDIGUNGSTERMIN: &str = "Z10";
pub const NACHRICHTENDATUM: &str = "137";
}
pub const STS_TRANSAKTIONSGRUND: &str = "7";
pub const STS_ANTWORT_DRITTER: &str = "Z35";
pub const STS_STATUS_ANTWORT: &str = "E01";
pub mod transaktionsgrund {
pub const EIN_AUSZUG: &str = "E01";
pub const EINZUG_NEUANLAGE: &str = "E02";
pub const WECHSEL: &str = "E03";
pub const STORNIERUNG: &str = "E05";
pub const ERSATZBELIEFERUNG: &str = "E06";
pub const KUENDIGUNG_LRV: &str = "Z02";
pub const INFO_EXISTIERENDE_ZUORDNUNG: &str = "Z26";
pub const AUSZUG_STILLLEGUNG: &str = "Z33";
pub const EOG_UMZUG: &str = "Z36";
pub const EOG_NEUANLAGE: &str = "Z37";
pub const EOG_VORUEBERGEHEND: &str = "Z39";
pub const ESV_ENDE_OHNE_FOLGE: &str = "Z41";
pub const EOG_BK_SCHLIESSUNG: &str = "ZC6";
pub const EOG_ZUORDNUNGSERMAECHTIGUNG: &str = "ZC7";
pub const BEENDIGUNG_ZUORDNUNG: &str = "ZC8";
pub const BEENDIGUNG_RUECKZUORDNUNG: &str = "ZD9";
pub const AUFHEBUNG_EEG38: &str = "ZG5";
pub const BEENDIGUNG_EEG38: &str = "ZG6";
pub const AUFHEBUNG_AUSZUG: &str = "ZG9";
pub const AUFHEBUNG_FRUEHERE_ANMELDUNG: &str = "ZH0";
pub const AUFHEBUNG_STILLLEGUNG: &str = "ZH1";
pub const AUFHEBUNG_VERTRAGSVERHAELTNIS: &str = "ZH2";
pub const ZUSAETZLICHER_DATENSATZ: &str = "Z15";
pub const STAMMDATENAENDERUNG: &str = "ZE3";
pub const UEBERNAHME_KEIN_IMS: &str = "ZJ4";
pub const STAMMDATEN: &str = "ZP3";
pub const WERTE: &str = "ZP4";
pub const ABMELDUNG_FEHLENDE_ZUORDNUNGSERMAECHTIGUNG: &str = "ZQ7";
pub const KUENDIGUNG_ANSCHLUSSNEHMER: &str = "ZR9";
pub const ABMELDUNG_FEHLENDE_ZE_ZRT: &str = "ZT0";
pub const ENDE_KUENDIGUNG_LF: &str = "ZT4";
pub const ENDE_KUENDIGUNG_KUNDE: &str = "ZT5";
pub const EOG_KUENDIGUNG_LF: &str = "ZT6";
pub const EOG_KUENDIGUNG_KUNDE: &str = "ZT7";
pub const AENDERUNG_MSB_ABRECHNUNGSDATEN: &str = "ZU1";
pub const ABRECHNUNGSDATEN_BK_ERZEUGEND: &str = "ZX2";
pub const ABRECHNUNGSDATEN_BK_VERBRAUCHEND: &str = "ZX3";
pub const ABRECHNUNGSDATEN_NNA: &str = "ZX4";
pub const AENDERUNG_BLINDABRECHNUNGSDATEN_NELO: &str = "ZX5";
pub const AENDERUNG_DATEN_MALO: &str = "ZX6";
pub const AENDERUNG_DATEN_MELO: &str = "ZX7";
pub const AENDERUNG_DATEN_NELO: &str = "ZX8";
pub const AENDERUNG_DATEN_SR: &str = "ZX9";
pub const AENDERUNG_DATEN_TR: &str = "ZY0";
pub const AENDERUNG_DATEN_TRANCHE: &str = "ZY1";
pub const AENDERUNG_LOKATIONSBUENDELSTRUKTUR: &str = "ZY2";
pub const ANTWORT_GDA_MSB: &str = "ZY4";
pub const ANTWORT_GDA_STROM_AN_GAS: &str = "ZY5";
pub const ANTWORT_GDA_ERZEUGENDE_MALO: &str = "ZY6";
pub const ANTWORT_GDA_VERBRAUCHENDE_MALO: &str = "ZY7";
pub const DATEN_INDIVIDUELLE_BESTELLUNG: &str = "ZY9";
pub const STAMMDATEN_BK_TREUE: &str = "ZAM";
pub const KORREKTUR_ABRECHNUNGSDATEN_BK_VERBRAUCHEND: &str = "ZAN";
pub const KORREKTUR_ABRECHNUNGSDATEN_BK_ERZEUGEND: &str = "ZAO";
pub const AENDERUNG_PAKET_ID_MALO: &str = "ZZA";
pub const UEBERGANGSVERSORGUNG: &str = "ZZD";
}
pub mod ergaenzung {
pub const GESCHAEFTSVORFALL_1: &str = "ZW0";
pub const GESCHAEFTSVORFALL_2: &str = "ZW1";
pub const GESCHAEFTSVORFALL_3: &str = "ZW2";
pub const ERZEUGENDE_MALO: &str = "ZW3";
pub const VERBRAUCHENDE_MALO: &str = "ZW4";
pub const TRANCHE: &str = "ZW5";
pub const PAUSCHALE_MALO: &str = "ZW6";
pub const GEMESSENE_MALO: &str = "ZW7";
pub const RUHENDE_MALO: &str = "ZAP";
pub const STILLLEGUNG_INKL_MALO: &str = "ZZB";
pub const STILLLEGUNG_EXKL_MALO: &str = "ZZC";
}
pub mod zuordnungsfall {
pub const FALL_1: &str = "ZW8";
pub const FALL_2: &str = "ZW9";
pub const FALL_3: &str = "ZX0";
pub const FALL_4: &str = "ZX1";
}
pub mod nad {
pub const ABSENDER: &str = "MS";
pub const EMPFAENGER: &str = "MR";
pub const KUNDE_DES_LF: &str = "Z09";
pub const KORRESPONDENZANSCHRIFT_KUNDE: &str = "Z04";
pub const ZUGEHOERIGE_PARTEI: &str = "VY";
}
pub mod cci {
pub const ZUGEORDNETER_MARKTPARTNER: &str = "ZB3";
}
pub mod cav {
pub const MSB: &str = "Z91";
pub const GMSB: &str = "ZF0";
}
pub mod msb {
pub const GRUNDZUSTAENDIG: &str = "Z39";
pub const WETTBEWERBLICH: &str = "Z40";
pub const AUFFANG: &str = "Z41";
pub const GRUNDLAGE_VERTRAG: &str = "Z19";
pub const GRUNDLAGE_WEITERVERPFLICHTUNG: &str = "Z20";
}
pub mod seq_antwort {
pub const DATEN_DER_MESSLOKATION: &str = "ZF3";
}
pub mod rff_lokation {
pub const MESSLOKATION: &str = "Z19";
}
pub mod rff {
pub const PRUEFIDENTIFIKATOR: &str = "Z13";
pub const REFERENZ_VORGANGSNUMMER: &str = "TN";
pub const GEPLANTES_PRODUKTPAKET: &str = "Z60";
}
pub mod namensformat {
pub const PERSON: &str = "Z01";
pub const FIRMA: &str = "Z02";
}
pub mod produkt {
pub const SEQ_PRODUKTPAKET: &str = "Z79";
pub const PIA_ERFORDERLICHES_PRODUKT: &str = "5";
pub const PIA_TYP_PRODUKT: &str = "Z11";
pub const CCI_PRODUKTEIGENSCHAFT: &str = "Z66";
pub const CAV_EIGENSCHAFT: &str = "ZH9";
pub const CAV_WERT: &str = "ZV4";
pub const SEQ_PRIORISIERUNG: &str = "ZH0";
pub const CCI_UMSETZUNGSGRAD: &str = "Z65";
pub const UMSETZUNG_VOLLUMFAENGLICH: &str = "Z01";
pub const UMSETZUNG_IN_TEILEN: &str = "Z02";
pub const BILANZKREIS: &str = "9991000002082";
pub const TRANCHENGROESSE: &str = "9991000002090";
pub const TRANCHE_PROZENTUALE_AUFTEILUNG: &str = "9991000003014";
pub const TRANCHE_AUFTEILUNGSFAKTOR: &str = "9991000003022";
pub const TRANCHE_AUFTEILUNG_TR: &str = "9991000003220";
pub const CCI_BILANZKREIS_GAS: &str = "Z19";
pub const PRIORITAET: [&str; 5] = ["Z75", "Z76", "Z77", "Z78", "Z79"];
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Produkt {
pub produkt_code: String,
pub eigenschaft: Option<String>,
pub wert: Option<String>,
}
impl Produkt {
#[must_use]
pub fn bilanzkreis(bk: impl Into<String>) -> Self {
Self {
produkt_code: produkt::BILANZKREIS.to_owned(),
eigenschaft: None,
wert: Some(bk.into()),
}
}
#[must_use]
pub fn tranchengroesse(groesse: &Tranchengroesse) -> Self {
Self {
produkt_code: produkt::TRANCHENGROESSE.to_owned(),
eigenschaft: groesse.eigenschaft.clone(),
wert: Some(groesse.wert.clone()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tranchengroesse {
pub eigenschaft: Option<String>,
pub wert: String,
}
impl Tranchengroesse {
#[must_use]
pub fn prozent(wert: impl Into<String>) -> Self {
Self {
eigenschaft: Some(produkt::TRANCHE_PROZENTUALE_AUFTEILUNG.to_owned()),
wert: wert.into(),
}
}
#[must_use]
pub fn aufteilungsfaktor(wert: impl Into<String>) -> Self {
Self {
eigenschaft: Some(produkt::TRANCHE_AUFTEILUNGSFAKTOR.to_owned()),
wert: wert.into(),
}
}
#[must_use]
pub fn technische_ressourcen(wert: impl Into<String>) -> Self {
Self {
eigenschaft: Some(produkt::TRANCHE_AUFTEILUNG_TR.to_owned()),
wert: wert.into(),
}
}
#[must_use]
pub fn prozent_wert(&self) -> Option<&str> {
(self.eigenschaft.as_deref() == Some(produkt::TRANCHE_PROZENTUALE_AUFTEILUNG))
.then_some(self.wert.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Umsetzungsgrad {
#[default]
Vollumfaenglich,
InTeilen,
}
impl Umsetzungsgrad {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::Vollumfaenglich => produkt::UMSETZUNG_VOLLUMFAENGLICH,
Self::InTeilen => produkt::UMSETZUNG_IN_TEILEN,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Produktpaket {
pub paket_id: u32,
pub produkte: Vec<Produkt>,
pub umsetzung: Umsetzungsgrad,
}
impl Produktpaket {
#[must_use]
pub fn bilanzkreis(bk: impl Into<String>) -> Self {
Self {
paket_id: 1,
produkte: vec![Produkt::bilanzkreis(bk)],
umsetzung: Umsetzungsgrad::Vollumfaenglich,
}
}
#[must_use]
pub fn tranchenbildung(bk: impl Into<String>, groesse: &Tranchengroesse) -> Self {
Self {
paket_id: 1,
produkte: vec![Produkt::bilanzkreis(bk), Produkt::tranchengroesse(groesse)],
umsetzung: Umsetzungsgrad::Vollumfaenglich,
}
}
}
#[must_use]
pub fn is_valid_prozent_wert(wert: &str) -> bool {
let (ganz, bruch) = match wert.split_once(['.', ',']) {
Some((g, b)) => (g, b),
None => (wert, ""),
};
if ganz.is_empty() && bruch.is_empty() {
return false;
}
if !ganz.chars().all(|c| c.is_ascii_digit())
|| !bruch.chars().all(|c| c.is_ascii_digit())
|| bruch.len() > 2
{
return false;
}
let ganz_wert: u32 = if ganz.is_empty() {
0
} else if ganz.len() > 3 {
return false;
} else {
ganz.parse().unwrap_or(u32::MAX)
};
if ganz_wert >= 100 {
return false;
}
ganz_wert > 0 || bruch.chars().any(|c| c != '0')
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Transaktionsgrund {
pub grund: String,
pub ergaenzung: Option<String>,
pub befristet: Option<String>,
}
impl Transaktionsgrund {
#[must_use]
pub fn verbrauchende_malo(grund: impl Into<String>) -> Self {
Self {
grund: grund.into(),
ergaenzung: Some(ergaenzung::VERBRAUCHENDE_MALO.to_owned()),
befristet: None,
}
}
#[must_use]
pub fn new(grund: impl Into<String>, ergaenzung: impl Into<String>) -> Self {
Self {
grund: grund.into(),
ergaenzung: Some(ergaenzung.into()),
befristet: None,
}
}
#[must_use]
pub fn bare(grund: impl Into<String>) -> Self {
Self {
grund: grund.into(),
ergaenzung: None,
befristet: None,
}
}
#[must_use]
pub fn befristet(mut self, code: impl Into<String>) -> Self {
self.befristet = Some(code.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AntwortStatus {
pub code: String,
pub codeliste: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DritterAntwortStatus {
pub code: String,
pub codeliste: String,
pub referenz_lokation: Option<String>,
pub objekt: Option<String>,
}
impl DritterAntwortStatus {
#[must_use]
pub fn verbrauchend(code: impl Into<String>) -> Self {
Self {
code: code.into(),
codeliste: EBD_BEENDIGUNG_ZUORDNUNG.to_owned(),
referenz_lokation: None,
objekt: None,
}
}
#[must_use]
pub fn erzeugend(
code: impl Into<String>,
referenz_lokation: impl Into<String>,
objekt: impl Into<String>,
) -> Self {
Self {
code: code.into(),
codeliste: EBD_BEENDIGUNG_ZUORDNUNG.to_owned(),
referenz_lokation: Some(referenz_lokation.into()),
objekt: Some(objekt.into()),
}
}
}
pub const EBD_BEENDIGUNG_ZUORDNUNG: &str = "E_0624";
pub const CODES_REQUIRING_DRITTER: &[&str] = &["A50", "A57"];
impl AntwortStatus {
#[must_use]
pub fn from_codeliste(code: impl Into<String>, codeliste: impl Into<String>) -> Self {
Self {
code: code.into(),
codeliste: Some(codeliste.into()),
}
}
#[must_use]
pub fn bare(code: impl Into<String>) -> Self {
Self {
code: code.into(),
codeliste: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_mig_example_shape_is_grund_ergaenzung_befristet() {
let t = Transaktionsgrund::verbrauchende_malo(transaktionsgrund::EIN_AUSZUG)
.befristet(transaktionsgrund::WECHSEL);
assert_eq!(t.grund, "E01");
assert_eq!(t.ergaenzung.as_deref(), Some("ZW4"));
assert_eq!(t.befristet.as_deref(), Some("E03"));
}
#[test]
fn ide_carries_a_vorgang_not_a_location() {
assert_eq!(IDE_VORGANG, "24");
assert_eq!(loc::STEUERBARE_RESSOURCE, "Z19");
assert_ne!(IDE_VORGANG, loc::MARKTLOKATION);
}
}
#[cfg(all(test, feature = "utilmd"))]
mod de9013_tests {
use super::STS_TRANSAKTIONSGRUND;
use crate::registry::ReleaseRegistry;
#[test]
fn the_de9013_tables_are_the_mig_tables() {
for (name, shipped, published) in [
("element 2", SHIPPED_GRUND, &de9013(0)[..]),
("element 3", SHIPPED_ERGAENZUNG, &de9013(1)[..]),
] {
let mut have: Vec<&str> = shipped.to_vec();
have.sort_unstable();
have.dedup();
let mut want: Vec<&str> = published.to_vec();
want.sort_unstable();
want.dedup();
assert_eq!(have, want, "{name} drifted from the UTILMD MIG");
}
}
fn de9013(occurrence: usize) -> Vec<&'static str> {
let profile = ReleaseRegistry::global()
.all_profiles()
.iter()
.filter(|p| p.mig.message_type == "UTILMD" && p.mig.track.as_deref() == Some("Strom"))
.max_by(|a, b| a.mig.valid_from.cmp(&b.mig.valid_from))
.expect("a UTILMD Strom profile is embedded");
let sts = profile
.mig
.segments()
.into_iter()
.find(|s| {
s.tag == "STS"
&& s.elements
.iter()
.flat_map(|e| e.components.iter())
.any(|c| {
c.id == "9015"
&& c.codes.iter().any(|k| k.code == STS_TRANSAKTIONSGRUND)
})
})
.expect("UTILMD SG4 carries the Transaktionsgrund STS");
let tables: Vec<Vec<&str>> = sts
.elements
.iter()
.flat_map(|el| el.components.iter())
.filter(|c| c.id == "9013")
.map(|c| c.codes.iter().map(|k| k.code.as_str()).collect())
.collect();
assert_eq!(
tables.len(),
3,
"the MIG prints DE 9013 three times in {} {}",
sts.nr,
sts.name
);
tables[occurrence].clone()
}
const SHIPPED_GRUND: &[&str] = {
use super::transaktionsgrund as g;
&[
g::EIN_AUSZUG,
g::EINZUG_NEUANLAGE,
g::WECHSEL,
g::STORNIERUNG,
g::ERSATZBELIEFERUNG,
g::KUENDIGUNG_LRV,
g::INFO_EXISTIERENDE_ZUORDNUNG,
g::AUSZUG_STILLLEGUNG,
g::EOG_UMZUG,
g::EOG_NEUANLAGE,
g::EOG_VORUEBERGEHEND,
g::ESV_ENDE_OHNE_FOLGE,
g::EOG_BK_SCHLIESSUNG,
g::EOG_ZUORDNUNGSERMAECHTIGUNG,
g::BEENDIGUNG_ZUORDNUNG,
g::BEENDIGUNG_RUECKZUORDNUNG,
g::AUFHEBUNG_EEG38,
g::BEENDIGUNG_EEG38,
g::AUFHEBUNG_AUSZUG,
g::AUFHEBUNG_FRUEHERE_ANMELDUNG,
g::AUFHEBUNG_STILLLEGUNG,
g::AUFHEBUNG_VERTRAGSVERHAELTNIS,
g::ZUSAETZLICHER_DATENSATZ,
g::STAMMDATENAENDERUNG,
g::UEBERNAHME_KEIN_IMS,
g::STAMMDATEN,
g::WERTE,
g::ABMELDUNG_FEHLENDE_ZUORDNUNGSERMAECHTIGUNG,
g::KUENDIGUNG_ANSCHLUSSNEHMER,
g::ABMELDUNG_FEHLENDE_ZE_ZRT,
g::ENDE_KUENDIGUNG_LF,
g::ENDE_KUENDIGUNG_KUNDE,
g::EOG_KUENDIGUNG_LF,
g::EOG_KUENDIGUNG_KUNDE,
g::AENDERUNG_MSB_ABRECHNUNGSDATEN,
g::ABRECHNUNGSDATEN_BK_ERZEUGEND,
g::ABRECHNUNGSDATEN_BK_VERBRAUCHEND,
g::ABRECHNUNGSDATEN_NNA,
g::AENDERUNG_BLINDABRECHNUNGSDATEN_NELO,
g::AENDERUNG_DATEN_MALO,
g::AENDERUNG_DATEN_MELO,
g::AENDERUNG_DATEN_NELO,
g::AENDERUNG_DATEN_SR,
g::AENDERUNG_DATEN_TR,
g::AENDERUNG_DATEN_TRANCHE,
g::AENDERUNG_LOKATIONSBUENDELSTRUKTUR,
g::ANTWORT_GDA_MSB,
g::ANTWORT_GDA_STROM_AN_GAS,
g::ANTWORT_GDA_ERZEUGENDE_MALO,
g::ANTWORT_GDA_VERBRAUCHENDE_MALO,
g::DATEN_INDIVIDUELLE_BESTELLUNG,
g::STAMMDATEN_BK_TREUE,
g::KORREKTUR_ABRECHNUNGSDATEN_BK_VERBRAUCHEND,
g::KORREKTUR_ABRECHNUNGSDATEN_BK_ERZEUGEND,
g::AENDERUNG_PAKET_ID_MALO,
g::UEBERGANGSVERSORGUNG,
]
};
const SHIPPED_ERGAENZUNG: &[&str] = {
use super::{ergaenzung as e, zuordnungsfall as z};
&[
e::GESCHAEFTSVORFALL_1,
e::GESCHAEFTSVORFALL_2,
e::GESCHAEFTSVORFALL_3,
e::ERZEUGENDE_MALO,
e::VERBRAUCHENDE_MALO,
e::TRANCHE,
e::PAUSCHALE_MALO,
e::GEMESSENE_MALO,
e::RUHENDE_MALO,
e::STILLLEGUNG_INKL_MALO,
e::STILLLEGUNG_EXKL_MALO,
z::FALL_1,
z::FALL_2,
z::FALL_3,
z::FALL_4,
]
};
}