use mako_engine::types::Pruefidentifikator;
use mako_engine::{
error::WorkflowError,
ids::DeadlineId,
outbox::PendingOutbox,
types::{MarktpartnerCode, MeLo, MessageRef, Sparte},
workflow::{CommandPayload, EventPayload, PendingDeadline, Workflow, WorkflowOutput},
};
use mako_fristen::{HolidayCalendar, antwort::Messtechnik};
use time::OffsetDateTime;
pub const WORKFLOW_NAME: &str = "wim-insrpt";
pub const INSRPT_ANFRAGE_PIDS: &[u32] = &[23001];
pub const INSRPT_ANTWORT_PIDS: &[u32] = &[23003, 23004, 23005, 23008, 23009, 23011, 23012];
pub const INSRPT_ENTSCHEIDUNGS_PIDS: &[u32] = &[23003, 23004];
pub const INSRPT_ERGEBNIS_PID: u32 = 23008;
pub const INSRPT_INFORMATIONS_PIDS: &[u32] = &[23005, 23009, 23011, 23012];
pub const ANTWORT_WINDOW_LABEL: &str = "wim-insrpt-antwort";
pub const ERGEBNIS_WINDOW_LABEL: &str = "wim-insrpt-ergebnis";
pub const WEITERLEITUNG_WINDOW_LABEL: &str = "wim-insrpt-weiterleitung";
#[must_use]
pub fn antwort_werktage(sparte: Sparte, messtechnik: Messtechnik) -> u32 {
match sparte {
Sparte::Strom => messtechnik.stoerungsmeldung_werktage(),
Sparte::Gas => mako_fristen::antwort::STOERUNGSMELDUNG_KME_WERKTAGE,
}
}
#[must_use]
pub fn ergebnis_werktage(sparte: Sparte, messtechnik: Messtechnik) -> u32 {
match sparte {
Sparte::Strom => messtechnik.ergebnisbericht_werktage(),
Sparte::Gas => mako_fristen::antwort::ERGEBNISBERICHT_GAS_WERKTAGE,
}
}
fn window(label: &'static str, from: OffsetDateTime, werktage: u32) -> PendingDeadline {
PendingDeadline::new(
label,
mako_fristen::deadline_at_werktage(from, werktage, HolidayCalendar::BdewMaKo),
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Seite {
Melder,
Msb,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StoerungsmeldungData {
pub seite: Seite,
pub pruefidentifikator: Pruefidentifikator,
pub melder_mp_id: MarktpartnerCode,
pub msb_mp_id: MarktpartnerCode,
pub melo_id: MeLo,
pub sparte: Sparte,
pub document_date: String,
pub message_ref: MessageRef,
#[serde(default)]
pub weiterleitung_offen: Vec<u32>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum StoerungsmeldungEvent {
StoerungsmeldungGesendet(Box<StoerungsmeldungData>),
StoerungsmeldungEmpfangen(Box<StoerungsmeldungData>),
AntwortGesendet {
pruefidentifikator: Pruefidentifikator,
message_ref: MessageRef,
},
AntwortErhalten {
pruefidentifikator: Pruefidentifikator,
sender: MarktpartnerCode,
is_confirmation: bool,
message_ref: MessageRef,
},
ErgebnisberichtGesendet {
message_ref: MessageRef,
},
Informationsmeldung {
pruefidentifikator: Pruefidentifikator,
counterparty: MarktpartnerCode,
message_ref: MessageRef,
outbound: bool,
},
DeadlineExpired {
deadline_id: DeadlineId,
label: Box<str>,
},
}
impl EventPayload for StoerungsmeldungEvent {
fn event_type(&self) -> &'static str {
match self {
Self::StoerungsmeldungGesendet(_) => "InsrptStoerungsmeldungGesendet",
Self::StoerungsmeldungEmpfangen(_) => "InsrptStoerungsmeldungEmpfangen",
Self::AntwortGesendet { .. } => "InsrptAntwortGesendet",
Self::AntwortErhalten { .. } => "InsrptAntwortErhalten",
Self::ErgebnisberichtGesendet { .. } => "InsrptErgebnisberichtGesendet",
Self::Informationsmeldung { .. } => "InsrptInformationsmeldung",
Self::DeadlineExpired { .. } => "InsrptDeadlineExpired",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "status", content = "data")]
#[derive(Default)]
pub enum StoerungsmeldungState {
#[default]
New,
StoerungsmeldungGesendet(Box<StoerungsmeldungData>),
StoerungsmeldungEmpfangen(Box<StoerungsmeldungData>),
Bestaetigt(Box<StoerungsmeldungData>),
Abgelehnt(Box<StoerungsmeldungData>),
Ergebnisbericht(Box<StoerungsmeldungData>),
DeadlineExpired {
label: String,
},
}
impl StoerungsmeldungState {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::New => "New",
Self::StoerungsmeldungGesendet(_) => "StoerungsmeldungGesendet",
Self::StoerungsmeldungEmpfangen(_) => "StoerungsmeldungEmpfangen",
Self::Bestaetigt(_) => "Bestaetigt",
Self::Abgelehnt(_) => "Abgelehnt",
Self::Ergebnisbericht(_) => "Ergebnisbericht",
Self::DeadlineExpired { .. } => "DeadlineExpired",
}
}
#[must_use]
pub const fn is_terminal(&self) -> bool {
matches!(
self,
Self::Abgelehnt(_) | Self::Ergebnisbericht(_) | Self::DeadlineExpired { .. }
)
}
#[must_use]
pub const fn data(&self) -> Option<&StoerungsmeldungData> {
match self {
Self::StoerungsmeldungGesendet(d)
| Self::StoerungsmeldungEmpfangen(d)
| Self::Bestaetigt(d)
| Self::Abgelehnt(d)
| Self::Ergebnisbericht(d) => Some(d),
Self::New | Self::DeadlineExpired { .. } => None,
}
}
}
#[derive(Clone)]
pub enum StoerungsmeldungCommand {
SendStoerungsmeldung {
pid: Pruefidentifikator,
msb_mp_id: MarktpartnerCode,
melo_id: MeLo,
sparte: Sparte,
document_date: String,
message_ref: MessageRef,
},
ReceiveStoerungsmeldung {
pid: Pruefidentifikator,
melder_mp_id: MarktpartnerCode,
msb_mp_id: MarktpartnerCode,
melo_id: MeLo,
sparte: Sparte,
document_date: String,
message_ref: MessageRef,
received_at: OffsetDateTime,
messtechnik: Messtechnik,
},
DispatchAntwort {
pid: Pruefidentifikator,
status_code: Option<String>,
message_ref: MessageRef,
sent_at: OffsetDateTime,
messtechnik: Messtechnik,
},
DispatchErgebnisbericht {
message_ref: MessageRef,
status_code: Option<String>,
},
DispatchInformationsmeldung {
pid: Pruefidentifikator,
receiver: MarktpartnerCode,
message_ref: MessageRef,
status_code: Option<String>,
},
ReceiveAntwort {
pid: Pruefidentifikator,
sender: MarktpartnerCode,
message_ref: MessageRef,
},
ReceiveInformationsmeldung {
pid: Pruefidentifikator,
sender: MarktpartnerCode,
message_ref: MessageRef,
},
TimeoutExpired {
deadline_id: DeadlineId,
label: Box<str>,
},
}
impl CommandPayload for StoerungsmeldungCommand {}
pub struct WimInsrptWorkflow;
impl WimInsrptWorkflow {
fn outbox(
data: &StoerungsmeldungData,
pid: u32,
receiver: &MarktpartnerCode,
message_ref: &MessageRef,
status_code: Option<&str>,
) -> PendingOutbox {
let mut payload = serde_json::json!({
"type": "Stoerungsmeldung",
"pid": pid,
"melo": data.melo_id.as_str(),
"receiver": receiver.as_str(),
"document_date": data.document_date,
"message_ref": message_ref.as_str(),
});
if let Some(code) = status_code {
payload["status_code"] = serde_json::Value::String(code.to_owned());
}
PendingOutbox::new("INSRPT", receiver.as_str(), payload)
}
}
impl Workflow for WimInsrptWorkflow {
type State = StoerungsmeldungState;
type Event = StoerungsmeldungEvent;
type Command = StoerungsmeldungCommand;
fn on_deadline(
deadline: &mako_engine::deadline::Deadline,
state: &Self::State,
) -> Option<Self::Command> {
let open = if deadline.label() == WEITERLEITUNG_WINDOW_LABEL {
state
.data()
.is_some_and(|d| !d.weiterleitung_offen.is_empty())
} else {
matches!(
deadline.label(),
ANTWORT_WINDOW_LABEL | ERGEBNIS_WINDOW_LABEL
) && !state.is_terminal()
};
open.then(|| StoerungsmeldungCommand::TimeoutExpired {
deadline_id: deadline.deadline_id(),
label: deadline.label().into(),
})
}
fn apply(state: Self::State, event: &Self::Event) -> Self::State {
match event {
StoerungsmeldungEvent::StoerungsmeldungGesendet(d) => {
StoerungsmeldungState::StoerungsmeldungGesendet(d.clone())
}
StoerungsmeldungEvent::StoerungsmeldungEmpfangen(d) => {
StoerungsmeldungState::StoerungsmeldungEmpfangen(d.clone())
}
StoerungsmeldungEvent::AntwortGesendet {
pruefidentifikator, ..
} => match state {
StoerungsmeldungState::StoerungsmeldungEmpfangen(mut d) => {
if pruefidentifikator.as_u32() == 23_004 {
if d.sparte == Sparte::Strom {
d.weiterleitung_offen.push(23_011);
}
StoerungsmeldungState::Bestaetigt(d)
} else {
StoerungsmeldungState::Abgelehnt(d)
}
}
other => other,
},
StoerungsmeldungEvent::AntwortErhalten {
pruefidentifikator,
is_confirmation,
..
} => match state {
StoerungsmeldungState::StoerungsmeldungGesendet(d)
| StoerungsmeldungState::Bestaetigt(d) => {
if pruefidentifikator.as_u32() == INSRPT_ERGEBNIS_PID {
StoerungsmeldungState::Ergebnisbericht(d)
} else if *is_confirmation {
StoerungsmeldungState::Bestaetigt(d)
} else {
StoerungsmeldungState::Abgelehnt(d)
}
}
other => other,
},
StoerungsmeldungEvent::ErgebnisberichtGesendet { .. } => match state {
StoerungsmeldungState::Bestaetigt(mut d)
| StoerungsmeldungState::StoerungsmeldungEmpfangen(mut d) => {
if d.sparte == Sparte::Strom {
d.weiterleitung_offen.push(23_012);
}
StoerungsmeldungState::Ergebnisbericht(d)
}
other => other,
},
StoerungsmeldungEvent::Informationsmeldung {
pruefidentifikator,
outbound: true,
..
} => {
let pid = pruefidentifikator.as_u32();
match state {
StoerungsmeldungState::Bestaetigt(mut d) => {
d.weiterleitung_offen.retain(|p| *p != pid);
StoerungsmeldungState::Bestaetigt(d)
}
StoerungsmeldungState::Ergebnisbericht(mut d) => {
d.weiterleitung_offen.retain(|p| *p != pid);
StoerungsmeldungState::Ergebnisbericht(d)
}
other => other,
}
}
StoerungsmeldungEvent::Informationsmeldung { .. } => state,
StoerungsmeldungEvent::DeadlineExpired { label, .. } => {
if state.is_terminal() {
state
} else {
StoerungsmeldungState::DeadlineExpired {
label: label.to_string(),
}
}
}
}
}
fn handle(
state: &Self::State,
command: Self::Command,
) -> Result<WorkflowOutput<Self::Event>, WorkflowError> {
match command {
StoerungsmeldungCommand::SendStoerungsmeldung {
pid,
msb_mp_id,
melo_id,
sparte,
document_date,
message_ref,
} => {
if !matches!(state, StoerungsmeldungState::New) {
return Err(WorkflowError::invalid_state("New", state.label()));
}
if !INSRPT_ANFRAGE_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"expected INSRPT PID 23001 for Störungsmeldung, got {pid}",
)));
}
let data = StoerungsmeldungData {
seite: Seite::Melder,
pruefidentifikator: pid,
melder_mp_id: MarktpartnerCode::new(""),
msb_mp_id: msb_mp_id.clone(),
melo_id,
sparte,
document_date,
message_ref: message_ref.clone(),
weiterleitung_offen: Vec::new(),
};
let outbox = Self::outbox(&data, pid.as_u32(), &msb_mp_id, &message_ref, None);
Ok(WorkflowOutput::with_outbox(
vec![StoerungsmeldungEvent::StoerungsmeldungGesendet(Box::new(
data,
))],
vec![outbox],
))
}
StoerungsmeldungCommand::ReceiveStoerungsmeldung {
pid,
melder_mp_id,
msb_mp_id,
melo_id,
sparte,
document_date,
message_ref,
received_at,
messtechnik,
} => {
if !matches!(state, StoerungsmeldungState::New) {
return Err(WorkflowError::invalid_state("New", state.label()));
}
if !INSRPT_ANFRAGE_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"expected INSRPT PID 23001 for Störungsmeldung, got {pid}",
)));
}
let data = StoerungsmeldungData {
seite: Seite::Msb,
pruefidentifikator: pid,
melder_mp_id,
msb_mp_id,
melo_id,
sparte,
document_date,
message_ref,
weiterleitung_offen: Vec::new(),
};
Ok(WorkflowOutput::with_outbox_and_deadlines(
vec![StoerungsmeldungEvent::StoerungsmeldungEmpfangen(Box::new(
data,
))],
vec![],
vec![window(
ANTWORT_WINDOW_LABEL,
received_at,
antwort_werktage(sparte, messtechnik),
)],
))
}
StoerungsmeldungCommand::DispatchAntwort {
pid,
status_code,
message_ref,
sent_at,
messtechnik,
} => {
let StoerungsmeldungState::StoerungsmeldungEmpfangen(data) = state else {
return Err(WorkflowError::invalid_state(
"StoerungsmeldungEmpfangen",
state.label(),
));
};
if !INSRPT_ENTSCHEIDUNGS_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"the Störungsmeldung is answered with INSRPT 23003 or 23004, got {pid}",
)));
}
let outbox = Self::outbox(
data,
pid.as_u32(),
&data.melder_mp_id,
&message_ref,
status_code.as_deref(),
);
let events = vec![StoerungsmeldungEvent::AntwortGesendet {
pruefidentifikator: pid,
message_ref,
}];
if pid.as_u32() == 23_004 {
let mut deadlines = vec![window(
ERGEBNIS_WINDOW_LABEL,
sent_at,
ergebnis_werktage(data.sparte, messtechnik),
)];
if data.sparte == Sparte::Strom {
deadlines.push(window(
WEITERLEITUNG_WINDOW_LABEL,
sent_at,
mako_fristen::antwort::STOERUNG_WEITERLEITUNG_WERKTAGE,
));
}
Ok(WorkflowOutput::with_outbox_and_deadlines(
events,
vec![outbox],
deadlines,
))
} else {
Ok(WorkflowOutput::with_outbox(events, vec![outbox]))
}
}
StoerungsmeldungCommand::DispatchErgebnisbericht {
message_ref,
status_code,
} => {
let StoerungsmeldungState::Bestaetigt(data) = state else {
return Err(WorkflowError::invalid_state("Bestaetigt", state.label()));
};
if data.seite != Seite::Msb {
return Err(WorkflowError::rejected(
"only the MSB sends the Ergebnisbericht; this stream is the Melder's",
));
}
let outbox = Self::outbox(
data,
INSRPT_ERGEBNIS_PID,
&data.melder_mp_id,
&message_ref,
status_code.as_deref(),
);
Ok(WorkflowOutput::with_outbox(
vec![StoerungsmeldungEvent::ErgebnisberichtGesendet { message_ref }],
vec![outbox],
))
}
StoerungsmeldungCommand::DispatchInformationsmeldung {
pid,
receiver,
message_ref,
status_code,
} => {
let Some(data) = state.data() else {
return Err(WorkflowError::invalid_state(
"an open Störungsmeldung",
state.label(),
));
};
if !INSRPT_INFORMATIONS_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"PID {pid} is not an INSRPT Informationsmeldung",
)));
}
if data.seite != Seite::Msb {
return Err(WorkflowError::rejected(
"only the MSB sends an Informationsmeldung; this stream is the Melder's",
));
}
let outbox = Self::outbox(
data,
pid.as_u32(),
&receiver,
&message_ref,
status_code.as_deref(),
);
Ok(WorkflowOutput::with_outbox(
vec![StoerungsmeldungEvent::Informationsmeldung {
pruefidentifikator: pid,
counterparty: receiver,
message_ref,
outbound: true,
}],
vec![outbox],
))
}
StoerungsmeldungCommand::ReceiveAntwort {
pid,
sender,
message_ref,
} => {
if !INSRPT_ANTWORT_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"PID {pid} is not a handled INSRPT response PID",
)));
}
if state.is_terminal() {
return Ok(WorkflowOutput::events(vec![]));
}
if INSRPT_INFORMATIONS_PIDS.contains(&pid.as_u32()) {
return Ok(vec![StoerungsmeldungEvent::Informationsmeldung {
pruefidentifikator: pid,
counterparty: sender,
message_ref,
outbound: false,
}]
.into());
}
let is_confirmation = matches!(pid.as_u32(), 23_004 | INSRPT_ERGEBNIS_PID);
Ok(vec![StoerungsmeldungEvent::AntwortErhalten {
pruefidentifikator: pid,
sender,
is_confirmation,
message_ref,
}]
.into())
}
StoerungsmeldungCommand::ReceiveInformationsmeldung {
pid,
sender,
message_ref,
} => Ok(vec![StoerungsmeldungEvent::Informationsmeldung {
pruefidentifikator: pid,
counterparty: sender,
message_ref,
outbound: false,
}]
.into()),
StoerungsmeldungCommand::TimeoutExpired { deadline_id, label } => {
let weiterleitung_offen = &*label == WEITERLEITUNG_WINDOW_LABEL
&& state
.data()
.is_some_and(|d| !d.weiterleitung_offen.is_empty());
if state.is_terminal() && !weiterleitung_offen {
return Ok(WorkflowOutput::events(vec![]));
}
Ok(vec![StoerungsmeldungEvent::DeadlineExpired { deadline_id, label }].into())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pid(n: u32) -> Pruefidentifikator {
Pruefidentifikator::new(n).expect("valid PID")
}
fn now() -> OffsetDateTime {
time::PrimitiveDateTime::new(
time::Date::from_calendar_date(2026, time::Month::June, 15).expect("valid date"),
time::Time::from_hms(9, 0, 0).expect("valid time"),
)
.assume_utc()
}
fn receive(sparte: Sparte, messtechnik: Messtechnik) -> StoerungsmeldungCommand {
StoerungsmeldungCommand::ReceiveStoerungsmeldung {
pid: pid(23_001),
melder_mp_id: MarktpartnerCode::new("9900001000001"),
msb_mp_id: MarktpartnerCode::new("9900001000003"),
melo_id: MeLo::new("DE0001112223334445556667778889"),
sparte,
document_date: "20260615".into(),
message_ref: MessageRef::new("INS-1"),
received_at: now(),
messtechnik,
}
}
fn run(
state: StoerungsmeldungState,
cmd: StoerungsmeldungCommand,
) -> (StoerungsmeldungState, WorkflowOutput<StoerungsmeldungEvent>) {
let out = WimInsrptWorkflow::handle(&state, cmd).expect("command accepted");
let next = out.events.iter().fold(state, WimInsrptWorkflow::apply);
(next, out)
}
#[test]
fn the_msb_receives_the_stoerungsmeldung_and_owes_an_answer() {
let (state, out) = run(
StoerungsmeldungState::default(),
receive(Sparte::Strom, Messtechnik::RlmOderImsMsHs),
);
assert_eq!(state.label(), "StoerungsmeldungEmpfangen");
assert!(!state.is_terminal());
let labels: Vec<_> = out.deadlines.iter().map(|d| d.label.as_str()).collect();
assert_eq!(labels, vec![ANTWORT_WINDOW_LABEL]);
}
#[test]
fn only_the_bestaetigung_arms_the_ergebnisfrist() {
let (empfangen, _) = run(
StoerungsmeldungState::default(),
receive(Sparte::Strom, Messtechnik::KmeOhneRlm),
);
let antwort = |p: u32| StoerungsmeldungCommand::DispatchAntwort {
pid: pid(p),
status_code: None,
message_ref: MessageRef::new("INS-2"),
sent_at: now(),
messtechnik: Messtechnik::KmeOhneRlm,
};
let (bestaetigt, out) = run(empfangen.clone(), antwort(23_004));
assert_eq!(bestaetigt.label(), "Bestaetigt");
assert!(
!bestaetigt.is_terminal(),
"the Ergebnisbericht still follows"
);
assert_eq!(
out.deadlines
.iter()
.map(|d| d.label.as_str())
.collect::<Vec<_>>(),
vec![ERGEBNIS_WINDOW_LABEL, WEITERLEITUNG_WINDOW_LABEL]
);
let (abgelehnt, out) = run(empfangen, antwort(23_003));
assert_eq!(abgelehnt.label(), "Abgelehnt");
assert!(abgelehnt.is_terminal());
assert!(out.deadlines.is_empty());
}
#[test]
fn only_the_msb_side_sends_the_ergebnisbericht() {
let (gesendet, _) = run(
StoerungsmeldungState::default(),
StoerungsmeldungCommand::SendStoerungsmeldung {
pid: pid(23_001),
msb_mp_id: MarktpartnerCode::new("9900001000003"),
melo_id: MeLo::new("DE0001112223334445556667778889"),
sparte: Sparte::Strom,
document_date: "20260615".into(),
message_ref: MessageRef::new("INS-1"),
},
);
let (bestaetigt, _) = run(
gesendet,
StoerungsmeldungCommand::ReceiveAntwort {
pid: pid(23_004),
sender: MarktpartnerCode::new("9900001000003"),
message_ref: MessageRef::new("INS-3"),
},
);
assert_eq!(bestaetigt.label(), "Bestaetigt");
assert!(
WimInsrptWorkflow::handle(
&bestaetigt,
StoerungsmeldungCommand::DispatchErgebnisbericht {
message_ref: MessageRef::new("INS-5"),
status_code: None,
},
)
.is_err()
);
}
#[test]
fn the_ergebnisbericht_closes_the_use_case_not_the_bestaetigung() {
let (state, _) = run(
StoerungsmeldungState::default(),
StoerungsmeldungCommand::SendStoerungsmeldung {
pid: pid(23_001),
msb_mp_id: MarktpartnerCode::new("9900001000003"),
melo_id: MeLo::new("DE0001112223334445556667778889"),
sparte: Sparte::Strom,
document_date: "20260615".into(),
message_ref: MessageRef::new("INS-1"),
},
);
let recv = |p: u32| StoerungsmeldungCommand::ReceiveAntwort {
pid: pid(p),
sender: MarktpartnerCode::new("9900001000003"),
message_ref: MessageRef::new("INS-3"),
};
let (bestaetigt, _) = run(state, recv(23_004));
assert_eq!(bestaetigt.label(), "Bestaetigt");
assert!(!bestaetigt.is_terminal());
let (ergebnis, out) = run(bestaetigt, recv(23_008));
assert_eq!(ergebnis.label(), "Ergebnisbericht");
assert!(ergebnis.is_terminal());
assert_eq!(out.events.len(), 1);
}
#[test]
fn the_weiterleitung_window_survives_the_ergebnisbericht() {
let (empfangen, _) = run(
StoerungsmeldungState::default(),
receive(Sparte::Strom, Messtechnik::KmeOhneRlm),
);
let (bestaetigt, out) = run(
empfangen,
StoerungsmeldungCommand::DispatchAntwort {
pid: pid(23_004),
status_code: None,
message_ref: MessageRef::new("INS-2"),
sent_at: now(),
messtechnik: Messtechnik::KmeOhneRlm,
},
);
assert!(
out.deadlines
.iter()
.any(|d| d.label == WEITERLEITUNG_WINDOW_LABEL)
);
assert_eq!(
bestaetigt.data().map(|d| d.weiterleitung_offen.as_slice()),
Some([23_011].as_slice())
);
let (nach_23011, _) = run(
bestaetigt.clone(),
StoerungsmeldungCommand::DispatchInformationsmeldung {
pid: pid(23_011),
receiver: MarktpartnerCode::new("9900001000001"),
message_ref: MessageRef::new("INS-6"),
status_code: None,
},
);
assert_eq!(
nach_23011.data().map(|d| d.weiterleitung_offen.len()),
Some(0)
);
let (ergebnis, _) = run(
nach_23011,
StoerungsmeldungCommand::DispatchErgebnisbericht {
message_ref: MessageRef::new("INS-7"),
status_code: None,
},
);
assert!(ergebnis.is_terminal());
assert_eq!(
ergebnis.data().map(|d| d.weiterleitung_offen.as_slice()),
Some([23_012].as_slice())
);
}
#[test]
fn gas_arms_no_weiterleitung_window() {
let (empfangen, _) = run(
StoerungsmeldungState::default(),
receive(Sparte::Gas, Messtechnik::KmeOhneRlm),
);
let (_, out) = run(
empfangen,
StoerungsmeldungCommand::DispatchAntwort {
pid: pid(23_004),
status_code: None,
message_ref: MessageRef::new("INS-2"),
sent_at: now(),
messtechnik: Messtechnik::KmeOhneRlm,
},
);
assert!(
!out.deadlines
.iter()
.any(|d| d.label == WEITERLEITUNG_WINDOW_LABEL)
);
}
#[test]
fn accompanying_messages_do_not_decide_the_process() {
let (empfangen, _) = run(
StoerungsmeldungState::default(),
receive(Sparte::Gas, Messtechnik::KmeOhneRlm),
);
for p in INSRPT_INFORMATIONS_PIDS {
let (after, _) = run(
empfangen.clone(),
StoerungsmeldungCommand::ReceiveAntwort {
pid: pid(*p),
sender: MarktpartnerCode::new("9900001000003"),
message_ref: MessageRef::new("INS-4"),
},
);
assert_eq!(after.label(), "StoerungsmeldungEmpfangen", "PID {p}");
}
}
#[test]
fn gas_does_not_branch_on_messtechnik() {
for mt in [
Messtechnik::KmeOhneRlm,
Messtechnik::RlmOderImsNs,
Messtechnik::RlmOderImsMsHs,
] {
assert_eq!(antwort_werktage(Sparte::Gas, mt), 3);
assert_eq!(ergebnis_werktage(Sparte::Gas, mt), 7);
}
assert_eq!(antwort_werktage(Sparte::Strom, Messtechnik::KmeOhneRlm), 3);
assert_eq!(
antwort_werktage(Sparte::Strom, Messtechnik::RlmOderImsMsHs),
1
);
assert_eq!(
ergebnis_werktage(Sparte::Strom, Messtechnik::RlmOderImsMsHs),
2
);
}
}