use mako_engine::types::Pruefidentifikator;
use mako_engine::{
error::WorkflowError,
outbox::PendingOutbox,
types::{MaLo, MarktpartnerCode, MessageRef},
workflow::{CommandPayload, EventPayload, Workflow, WorkflowOutput},
};
use edi_codes::{BEGINN_ZUM, ENDE_ZUM, transaktionsgrund as grund};
mod edi_codes {
pub const ENDE_ZUM: &str = "93";
pub const BEGINN_ZUM: &str = "92";
pub mod transaktionsgrund {
pub const INFO_EXISTIERENDE_ZUORDNUNG: &str = "Z26";
pub const BEENDIGUNG_ZUORDNUNG: &str = "ZC8";
pub const BEENDIGUNG_RUECKZUORDNUNG: &str = "ZD9";
pub const BEENDIGUNG_EEG38: &str = "ZG6";
pub const AUFHEBUNG_EEG38: &str = "ZG5";
pub const AUFHEBUNG_AUSZUG: &str = "ZG9";
pub const AUFHEBUNG_FRUEHERE_ANMELDUNG: &str = "ZH0";
pub const AUFHEBUNG_STILLLEGUNG: &str = "ZH1";
}
}
pub const WORKFLOW_NAME: &str = "gpke-zuordnungsmeldung";
pub const INFORMATION_PID: u32 = 55_036;
pub const BEENDIGUNG_PID: u32 = 55_037;
pub const AUFHEBUNG_PID: u32 = 55_038;
pub const MSB_BEENDIGUNG_PID: u32 = 55_611;
pub const LOC_MARKTLOKATION: &str = "Z16";
pub const LOC_TRANCHE: &str = "Z21";
pub const LOC_MESSLOKATION: &str = "Z17";
pub const ZUORDNUNGSMELDUNG_PIDS: &[u32] = &[
INFORMATION_PID,
BEENDIGUNG_PID,
AUFHEBUNG_PID,
MSB_BEENDIGUNG_PID,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Zuordnungsmeldung {
Information,
Beendigung,
Aufhebung,
MsbBeendigung,
}
impl Zuordnungsmeldung {
#[must_use]
pub const fn from_pid(pid: u32) -> Option<Self> {
match pid {
INFORMATION_PID => Some(Self::Information),
BEENDIGUNG_PID => Some(Self::Beendigung),
AUFHEBUNG_PID => Some(Self::Aufhebung),
MSB_BEENDIGUNG_PID => Some(Self::MsbBeendigung),
_ => None,
}
}
#[must_use]
pub const fn pid(self) -> u32 {
match self {
Self::Information => INFORMATION_PID,
Self::Beendigung => BEENDIGUNG_PID,
Self::Aufhebung => AUFHEBUNG_PID,
Self::MsbBeendigung => MSB_BEENDIGUNG_PID,
}
}
#[must_use]
pub const fn bgm_document_code(self) -> &'static str {
match self {
Self::Information => "E01",
Self::Beendigung | Self::Aufhebung | Self::MsbBeendigung => "E02",
}
}
#[must_use]
pub fn dtm_qualifier(self, transaktionsgrund: &str) -> Option<&'static str> {
match self {
Self::Information => None,
Self::Beendigung => Some(ENDE_ZUM),
Self::Aufhebung => Some(BEGINN_ZUM),
Self::MsbBeendigung if transaktionsgrund == grund::AUFHEBUNG_STILLLEGUNG => {
Some(BEGINN_ZUM)
}
Self::MsbBeendigung => Some(ENDE_ZUM),
}
}
#[must_use]
pub const fn admissible_gruende(self) -> &'static [&'static str] {
match self {
Self::Information => &[grund::INFO_EXISTIERENDE_ZUORDNUNG],
Self::Beendigung => &[
grund::BEENDIGUNG_ZUORDNUNG,
grund::BEENDIGUNG_RUECKZUORDNUNG,
grund::BEENDIGUNG_EEG38,
],
Self::Aufhebung => &[
grund::AUFHEBUNG_EEG38,
grund::AUFHEBUNG_AUSZUG,
grund::AUFHEBUNG_FRUEHERE_ANMELDUNG,
grund::AUFHEBUNG_STILLLEGUNG,
],
Self::MsbBeendigung => &[grund::BEENDIGUNG_ZUORDNUNG, grund::AUFHEBUNG_STILLLEGUNG],
}
}
#[must_use]
pub fn grund_is_admissible(self, code: &str) -> bool {
self.admissible_gruende().contains(&code)
}
#[must_use]
pub fn requires_beteiligter(self, transaktionsgrund: &str) -> bool {
match self {
Self::Information => true,
Self::Beendigung | Self::MsbBeendigung => false,
Self::Aufhebung => transaktionsgrund != grund::AUFHEBUNG_EEG38,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum ZuordnungsmeldungEvent {
Gesendet {
pruefidentifikator: Pruefidentifikator,
location_id: MaLo,
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
transaktionsgrund: String,
#[serde(default)]
process_date: Option<String>,
#[serde(default)]
beteiligte: Vec<MarktpartnerCode>,
#[serde(default)]
referenz_vorgangsnummer: Option<String>,
},
Empfangen {
pruefidentifikator: Pruefidentifikator,
location_id: MaLo,
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
transaktionsgrund: String,
message_ref: MessageRef,
},
}
impl EventPayload for ZuordnungsmeldungEvent {
fn event_type(&self) -> &'static str {
match self {
Self::Gesendet { .. } => "ZuordnungsmeldungGesendet",
Self::Empfangen { .. } => "ZuordnungsmeldungEmpfangen",
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(tag = "status", content = "data")]
pub enum ZuordnungsmeldungState {
#[default]
New,
Gesendet {
pruefidentifikator: Pruefidentifikator,
location_id: MaLo,
receiver: MarktpartnerCode,
},
Empfangen {
pruefidentifikator: Pruefidentifikator,
location_id: MaLo,
sender: MarktpartnerCode,
},
}
impl ZuordnungsmeldungState {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::New => "New",
Self::Gesendet { .. } => "Gesendet",
Self::Empfangen { .. } => "Empfangen",
}
}
}
impl mako_engine::workflow::OccupiesBusinessKey for ZuordnungsmeldungState {
fn occupies_business_key(&self) -> bool {
false
}
}
#[derive(Clone)]
pub enum ZuordnungsmeldungCommand {
Senden {
meldung: Zuordnungsmeldung,
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
location_id: MaLo,
lokationstyp: Option<String>,
transaktionsgrund: String,
process_date: Option<String>,
beteiligte: Vec<MarktpartnerCode>,
referenz_vorgangsnummer: Option<String>,
},
Empfangen {
pid: Pruefidentifikator,
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
location_id: MaLo,
transaktionsgrund: String,
message_ref: MessageRef,
validation_passed: bool,
validation_errors: Vec<String>,
},
}
impl CommandPayload for ZuordnungsmeldungCommand {}
#[derive(Debug, Clone, Copy, Default)]
pub struct GpkeZuordnungsmeldungWorkflow;
impl Workflow for GpkeZuordnungsmeldungWorkflow {
type State = ZuordnungsmeldungState;
type Event = ZuordnungsmeldungEvent;
type Command = ZuordnungsmeldungCommand;
fn apply(_state: Self::State, event: &Self::Event) -> Self::State {
match event {
ZuordnungsmeldungEvent::Gesendet {
pruefidentifikator,
location_id,
receiver,
..
} => ZuordnungsmeldungState::Gesendet {
pruefidentifikator: *pruefidentifikator,
location_id: location_id.clone(),
receiver: receiver.clone(),
},
ZuordnungsmeldungEvent::Empfangen {
pruefidentifikator,
location_id,
sender,
..
} => ZuordnungsmeldungState::Empfangen {
pruefidentifikator: *pruefidentifikator,
location_id: location_id.clone(),
sender: sender.clone(),
},
}
}
fn handle(
state: &Self::State,
command: Self::Command,
) -> Result<WorkflowOutput<Self::Event>, WorkflowError> {
match command {
ZuordnungsmeldungCommand::Senden {
meldung,
sender,
receiver,
location_id,
lokationstyp,
transaktionsgrund,
process_date,
beteiligte,
referenz_vorgangsnummer,
} => {
if !matches!(state, ZuordnungsmeldungState::New) {
return Err(WorkflowError::invalid_state("New", state.label()));
}
if !meldung.grund_is_admissible(&transaktionsgrund) {
return Err(WorkflowError::rejected(format!(
"SG4 STS+7 DE 9013 {transaktionsgrund:?} is not admissible on PID {} — \
UTILMD AHB Strom Kap. 8.11 lists {:?}",
meldung.pid(),
meldung.admissible_gruende(),
)));
}
match (
meldung.dtm_qualifier(&transaktionsgrund),
process_date.as_deref(),
) {
(Some(_), None | Some("")) => {
return Err(WorkflowError::rejected(format!(
"PID {} requires a process date (SG4 DTM+{})",
meldung.pid(),
meldung
.dtm_qualifier(&transaktionsgrund)
.unwrap_or_default(),
)));
}
(None, Some(_)) => {
return Err(WorkflowError::rejected(format!(
"PID {} carries no SG4 date — UTILMD AHB Strom Kap. 8.11 leaves both \
the „Beginn zum\" and „Ende zum\" columns empty for it",
meldung.pid(),
)));
}
_ => {}
}
if meldung.requires_beteiligter(&transaktionsgrund) && beteiligte.is_empty() {
return Err(WorkflowError::rejected(format!(
"PID {} requires SG12 NAD+VY — {}",
meldung.pid(),
match meldung {
Zuordnungsmeldung::Information =>
"every Altlieferant an Abmeldeanfrage was sent to (Bedingung [518])",
_ => "the auslösender Marktpartner (Bedingung [579])",
},
)));
}
if meldung == Zuordnungsmeldung::Information
&& referenz_vorgangsnummer
.as_deref()
.unwrap_or_default()
.is_empty()
{
return Err(WorkflowError::rejected(
"PID 55036 requires SG6 RFF+TN — the Vorgangsnummer of the Anmeldung it \
answers to (UTILMD AHB Strom Kap. 8.11, „Referenz Vorgangsnummer (aus \
Anfragenachricht)\")"
.to_owned(),
));
}
let pid = Pruefidentifikator::new(meldung.pid())
.map_err(|e| WorkflowError::rejected(e.clone()))?;
let outbox = vec![PendingOutbox::new(
"UTILMD",
receiver.as_str(),
meldung_payload(
meldung,
&sender,
&receiver,
&location_id,
lokationstyp.as_deref(),
&transaktionsgrund,
process_date.as_deref(),
&beteiligte,
referenz_vorgangsnummer.as_deref(),
),
)];
Ok(WorkflowOutput::with_outbox(
vec![ZuordnungsmeldungEvent::Gesendet {
pruefidentifikator: pid,
location_id,
sender,
receiver,
transaktionsgrund,
process_date,
beteiligte,
referenz_vorgangsnummer,
}],
outbox,
))
}
ZuordnungsmeldungCommand::Empfangen {
pid,
sender,
receiver,
location_id,
transaktionsgrund,
message_ref,
validation_passed,
validation_errors,
} => {
if !matches!(state, ZuordnungsmeldungState::New) {
return Err(WorkflowError::invalid_state("New", state.label()));
}
if Zuordnungsmeldung::from_pid(pid.as_u32()).is_none() {
return Err(WorkflowError::rejected(format!(
"expected a Zuordnungs-Meldung PID ({ZUORDNUNGSMELDUNG_PIDS:?}), got {pid}",
)));
}
let aperak = if validation_passed {
PendingOutbox::aperak_anerkennung(
receiver.as_str(),
sender.as_str(),
message_ref.as_str(),
)
} else {
PendingOutbox::aperak_fehler(
receiver.as_str(),
sender.as_str(),
message_ref.as_str(),
mako_engine::erc::codes::Z29,
validation_errors.join("; "),
)
};
let outbox = vec![
aperak,
PendingOutbox::new(
"ProcessInitiated",
receiver.as_str(),
serde_json::json!({
"pid": pid.as_u32(),
"malo_id": location_id.as_str(),
"grid_operator": sender.as_str(),
"transaktionsgrund": transaktionsgrund,
}),
),
];
Ok(WorkflowOutput::with_outbox(
vec![ZuordnungsmeldungEvent::Empfangen {
pruefidentifikator: pid,
location_id,
sender,
receiver,
transaktionsgrund,
message_ref,
}],
outbox,
))
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn meldung_payload(
meldung: Zuordnungsmeldung,
sender: &MarktpartnerCode,
receiver: &MarktpartnerCode,
location_id: &MaLo,
lokationstyp: Option<&str>,
transaktionsgrund: &str,
process_date: Option<&str>,
beteiligte: &[MarktpartnerCode],
referenz_vorgangsnummer: Option<&str>,
) -> serde_json::Value {
let mut payload = serde_json::json!({
"direction": "outbound",
"pid": meldung.pid(),
"sender": sender.as_str(),
"receiver": receiver.as_str(),
"malo": location_id.as_str(),
"document_code": meldung.bgm_document_code(),
"transaktionsgrund": transaktionsgrund,
"dtm_qualifier": meldung.dtm_qualifier(transaktionsgrund),
"lokationstyp": lokationstyp.unwrap_or(LOC_MARKTLOKATION),
});
let obj = payload.as_object_mut().expect("json! built an object");
if let Some(date) = process_date {
obj.insert("process_date".into(), date.into());
}
if let Some(vorgang) = referenz_vorgangsnummer.filter(|v| !v.is_empty()) {
obj.insert("referenz_vorgangsnummer".into(), vorgang.into());
}
if !beteiligte.is_empty() {
obj.insert(
"beteiligte_marktpartner".into(),
serde_json::Value::Array(
beteiligte
.iter()
.map(|m| serde_json::Value::String(m.as_str().to_owned()))
.collect(),
),
);
}
payload
}
#[cfg(test)]
mod tests {
use super::*;
fn mp(s: &str) -> MarktpartnerCode {
MarktpartnerCode::new(s.to_owned())
}
fn malo() -> MaLo {
MaLo::new("51238696781".to_owned())
}
fn senden(meldung: Zuordnungsmeldung) -> ZuordnungsmeldungCommand {
let (grund, date, beteiligte, referenz) = match meldung {
Zuordnungsmeldung::Information => (
grund::INFO_EXISTIERENDE_ZUORDNUNG,
None,
vec![mp("9900555000005")],
Some("VG-4711".to_owned()),
),
Zuordnungsmeldung::Beendigung => (
grund::BEENDIGUNG_ZUORDNUNG,
Some("20260701".to_owned()),
vec![],
None,
),
Zuordnungsmeldung::Aufhebung => (
grund::AUFHEBUNG_FRUEHERE_ANMELDUNG,
Some("20260801".to_owned()),
vec![mp("9900111000002")],
None,
),
Zuordnungsmeldung::MsbBeendigung => (
grund::BEENDIGUNG_ZUORDNUNG,
Some("20260701".to_owned()),
vec![],
None,
),
};
ZuordnungsmeldungCommand::Senden {
meldung,
sender: mp("9900357000004"),
receiver: mp("9900123456789"),
location_id: malo(),
lokationstyp: None,
transaktionsgrund: grund.to_owned(),
process_date: date,
beteiligte,
referenz_vorgangsnummer: referenz,
}
}
fn run(cmd: ZuordnungsmeldungCommand) -> WorkflowOutput<ZuordnungsmeldungEvent> {
GpkeZuordnungsmeldungWorkflow::handle(&ZuordnungsmeldungState::New, cmd)
.expect("command accepted")
}
#[test]
fn each_meldung_renders_its_own_bgm_code() {
for (meldung, expected) in [
(Zuordnungsmeldung::Information, "E01"),
(Zuordnungsmeldung::Beendigung, "E02"),
(Zuordnungsmeldung::Aufhebung, "E02"),
] {
let out = run(senden(meldung));
assert_eq!(out.outbox[0].payload["document_code"], expected);
}
}
#[test]
fn the_information_carries_no_sg4_date() {
let out = run(senden(Zuordnungsmeldung::Information));
assert!(out.outbox[0].payload.get("process_date").is_none());
let ZuordnungsmeldungCommand::Senden {
meldung,
sender,
receiver,
location_id,
lokationstyp,
transaktionsgrund,
beteiligte,
referenz_vorgangsnummer,
..
} = senden(Zuordnungsmeldung::Information)
else {
unreachable!()
};
let err = GpkeZuordnungsmeldungWorkflow::handle(
&ZuordnungsmeldungState::New,
ZuordnungsmeldungCommand::Senden {
meldung,
sender,
receiver,
location_id,
lokationstyp,
transaktionsgrund,
process_date: Some("20260701".to_owned()),
beteiligte,
referenz_vorgangsnummer,
},
)
.expect_err("55036 takes no SG4 date");
assert!(format!("{err}").contains("no SG4 date"), "{err}");
}
#[test]
fn the_two_dated_meldungen_take_different_qualifiers() {
assert_eq!(
Zuordnungsmeldung::Beendigung.dtm_qualifier(grund::BEENDIGUNG_ZUORDNUNG),
Some("93")
);
assert_eq!(
Zuordnungsmeldung::Aufhebung.dtm_qualifier(grund::AUFHEBUNG_AUSZUG),
Some("92")
);
}
#[test]
fn a_grund_belongs_to_exactly_one_meldung() {
let all = [
Zuordnungsmeldung::Information,
Zuordnungsmeldung::Beendigung,
Zuordnungsmeldung::Aufhebung,
];
for m in all {
for code in m.admissible_gruende() {
let owners = all.iter().filter(|o| o.grund_is_admissible(code)).count();
assert_eq!(owners, 1, "{code} is admissible on {owners} Meldungen");
}
}
assert!(!Zuordnungsmeldung::Beendigung.grund_is_admissible(grund::AUFHEBUNG_AUSZUG));
}
#[test]
fn an_inadmissible_grund_is_refused() {
let err = GpkeZuordnungsmeldungWorkflow::handle(
&ZuordnungsmeldungState::New,
ZuordnungsmeldungCommand::Senden {
meldung: Zuordnungsmeldung::Beendigung,
sender: mp("9900357000004"),
receiver: mp("9900123456789"),
location_id: malo(),
lokationstyp: None,
transaktionsgrund: "E03".to_owned(),
process_date: Some("20260701".to_owned()),
beteiligte: vec![],
referenz_vorgangsnummer: None,
},
)
.expect_err("E03 is a Lieferantenwechsel, not a Beendigungsgrund");
assert!(format!("{err}").contains("not admissible"), "{err}");
}
#[test]
fn only_the_eeg38_aufhebung_may_omit_the_beteiligter() {
let base =
|grund: &str, beteiligte: Vec<MarktpartnerCode>| ZuordnungsmeldungCommand::Senden {
meldung: Zuordnungsmeldung::Aufhebung,
sender: mp("9900357000004"),
receiver: mp("9900123456789"),
location_id: malo(),
lokationstyp: None,
transaktionsgrund: grund.to_owned(),
process_date: Some("20260801".to_owned()),
beteiligte,
referenz_vorgangsnummer: None,
};
GpkeZuordnungsmeldungWorkflow::handle(
&ZuordnungsmeldungState::New,
base(grund::AUFHEBUNG_EEG38, vec![]),
)
.expect("ZG5 names no beteiligten Marktpartner");
GpkeZuordnungsmeldungWorkflow::handle(
&ZuordnungsmeldungState::New,
base(grund::AUFHEBUNG_STILLLEGUNG, vec![]),
)
.expect_err("ZH1 names the NB itself and must carry SG12");
}
#[test]
fn the_information_names_every_altlieferant() {
let out = run(ZuordnungsmeldungCommand::Senden {
meldung: Zuordnungsmeldung::Information,
sender: mp("9900357000004"),
receiver: mp("9900123456789"),
location_id: malo(),
lokationstyp: Some(LOC_TRANCHE.to_owned()),
transaktionsgrund: grund::INFO_EXISTIERENDE_ZUORDNUNG.to_owned(),
process_date: None,
beteiligte: vec![mp("9900555000005"), mp("9900111000002")],
referenz_vorgangsnummer: Some("VG-4711".to_owned()),
});
let payload = &out.outbox[0].payload;
assert_eq!(
payload["beteiligte_marktpartner"].as_array().map(Vec::len),
Some(2)
);
assert_eq!(payload["lokationstyp"], "Z21");
}
#[test]
fn the_information_must_reference_the_anmeldung() {
let err = GpkeZuordnungsmeldungWorkflow::handle(
&ZuordnungsmeldungState::New,
ZuordnungsmeldungCommand::Senden {
meldung: Zuordnungsmeldung::Information,
sender: mp("9900357000004"),
receiver: mp("9900123456789"),
location_id: malo(),
lokationstyp: None,
transaktionsgrund: grund::INFO_EXISTIERENDE_ZUORDNUNG.to_owned(),
process_date: None,
beteiligte: vec![mp("9900555000005")],
referenz_vorgangsnummer: None,
},
)
.expect_err("55036 must carry RFF+TN");
assert!(format!("{err}").contains("RFF+TN"), "{err}");
}
#[test]
fn an_inbound_meldung_is_acknowledged_but_not_answered() {
let out = run(ZuordnungsmeldungCommand::Empfangen {
pid: Pruefidentifikator::new(BEENDIGUNG_PID).expect("valid"),
sender: mp("9900357000004"),
receiver: mp("9900123456789"),
location_id: malo(),
transaktionsgrund: grund::BEENDIGUNG_ZUORDNUNG.to_owned(),
message_ref: MessageRef::new("MSG-1".to_owned()),
validation_passed: true,
validation_errors: vec![],
});
let kinds: Vec<&str> = out.outbox.iter().map(|o| &*o.message_type).collect();
assert_eq!(kinds, vec!["APERAK", "ProcessInitiated"]);
assert!(
!kinds.contains(&"UTILMD"),
"a Meldung has no Antwortnachricht"
);
}
#[test]
fn every_pid_resolves_to_exactly_one_meldung() {
for &pid in ZUORDNUNGSMELDUNG_PIDS {
let m = Zuordnungsmeldung::from_pid(pid).expect("catalogued");
assert_eq!(m.pid(), pid);
}
assert!(Zuordnungsmeldung::from_pid(55_001).is_none());
}
#[test]
fn the_workflow_covers_the_catalogued_strom_meldepflichten() {
let mut catalogued: Vec<u32> = mako_fristen::meldung::GPKE
.iter()
.chain(mako_fristen::meldung::GPKE_LIEFERENDE)
.map(|m| m.pid)
.collect();
catalogued.sort_unstable();
let mut routed = ZUORDNUNGSMELDUNG_PIDS.to_vec();
routed.sort_unstable();
assert_eq!(catalogued, routed);
}
}
#[cfg(test)]
mod msb_beendigung_tests {
use super::*;
fn mp(s: &str) -> MarktpartnerCode {
MarktpartnerCode::new(s.to_owned())
}
fn senden(g: &str, lokationstyp: &str, id: &str) -> ZuordnungsmeldungCommand {
ZuordnungsmeldungCommand::Senden {
meldung: Zuordnungsmeldung::MsbBeendigung,
sender: mp("9900357000004"),
receiver: mp("9900111000002"),
location_id: MaLo::new(id.to_owned()),
lokationstyp: Some(lokationstyp.to_owned()),
transaktionsgrund: g.to_owned(),
process_date: Some("20261101".to_owned()),
beteiligte: vec![],
referenz_vorgangsnummer: None,
}
}
fn run(cmd: ZuordnungsmeldungCommand) -> WorkflowOutput<ZuordnungsmeldungEvent> {
GpkeZuordnungsmeldungWorkflow::handle(&ZuordnungsmeldungState::New, cmd).expect("accepted")
}
#[test]
fn the_date_qualifier_follows_the_grund_not_the_pid() {
let beendigung = run(senden(
grund::BEENDIGUNG_ZUORDNUNG,
LOC_MESSLOKATION,
"51238696781",
));
assert_eq!(beendigung.outbox[0].payload["dtm_qualifier"], "93");
let aufhebung = run(senden(
grund::AUFHEBUNG_STILLLEGUNG,
LOC_MESSLOKATION,
"51238696781",
));
assert_eq!(aufhebung.outbox[0].payload["dtm_qualifier"], "92");
}
#[test]
fn it_may_name_a_messlokation_and_is_an_abmeldung() {
let out = run(senden(
grund::BEENDIGUNG_ZUORDNUNG,
LOC_MESSLOKATION,
"51238696781",
));
let p = &out.outbox[0].payload;
assert_eq!(p["pid"], 55_611);
assert_eq!(p["document_code"], "E02");
assert_eq!(p["lokationstyp"], "Z17");
assert!(p.get("beteiligte_marktpartner").is_none());
assert!(
!Zuordnungsmeldung::MsbBeendigung.requires_beteiligter(grund::BEENDIGUNG_ZUORDNUNG)
);
}
#[test]
fn only_zc8_and_zh1_are_admissible() {
for code in [
grund::AUFHEBUNG_EEG38,
grund::AUFHEBUNG_AUSZUG,
grund::AUFHEBUNG_FRUEHERE_ANMELDUNG,
grund::BEENDIGUNG_RUECKZUORDNUNG,
grund::BEENDIGUNG_EEG38,
grund::INFO_EXISTIERENDE_ZUORDNUNG,
] {
assert!(
!Zuordnungsmeldung::MsbBeendigung.grund_is_admissible(code),
"{code} must not ride a 55611"
);
}
for code in [grund::BEENDIGUNG_ZUORDNUNG, grund::AUFHEBUNG_STILLLEGUNG] {
assert!(
Zuordnungsmeldung::MsbBeendigung.grund_is_admissible(code),
"{code}"
);
}
}
#[test]
fn the_catalogue_agrees_and_anchors_on_the_nbs_own_message() {
let m = mako_fristen::meldung::meldepflicht(MSB_BEENDIGUNG_PID).expect("catalogued");
assert_eq!(m.sent_by, "NB");
assert_eq!(m.triggered_by, &[55_007]);
assert_eq!(
m.anchor,
mako_fristen::meldung::MeldungAnchor::EigeneAnkuendigung
);
assert!(ZUORDNUNGSMELDUNG_PIDS.contains(&MSB_BEENDIGUNG_PID));
}
}