use mako_engine::{
error::WorkflowError,
outbox::PendingOutbox,
types::{BillingPeriod, MarktpartnerCode, MessageRef, Pruefidentifikator},
workflow::{CommandPayload, EventPayload, Workflow, WorkflowOutput},
};
use mako_pruefung::mabis::Korrekturposition;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ListenTyp {
Lieferantenclearingliste,
Bilanzierungsgebietsclearingliste,
LfAacl,
DzuListe,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ListenFamilie {
pub liste: u32,
pub antwort: u32,
pub typ: ListenTyp,
pub sender_ebd: &'static [(&'static str, &'static str)],
pub empfaenger_rolle: &'static str,
}
impl ListenFamilie {
#[must_use]
pub fn antwort_ebd(&self, sender_rolle: &str) -> Option<&'static str> {
self.sender_ebd
.iter()
.find(|(r, _)| *r == sender_rolle)
.map(|(_, ebd)| *ebd)
}
pub fn sender_rollen(&self) -> impl Iterator<Item = &'static str> + '_ {
self.sender_ebd.iter().map(|(r, _)| *r)
}
}
pub const LISTEN_FAMILIEN: &[ListenFamilie] = &[
ListenFamilie {
liste: 55065,
antwort: 55066,
typ: ListenTyp::Lieferantenclearingliste,
sender_ebd: &[("NB", "E_0047"), ("ÜNB", "E_0004")],
empfaenger_rolle: "LF",
},
ListenFamilie {
liste: 55195,
antwort: 55196,
typ: ListenTyp::Bilanzierungsgebietsclearingliste,
sender_ebd: &[("ÜNB", "E_0017")],
empfaenger_rolle: "NB",
},
ListenFamilie {
liste: 55201,
antwort: 55202,
typ: ListenTyp::LfAacl,
sender_ebd: &[("NB", "E_0097")],
empfaenger_rolle: "LF",
},
ListenFamilie {
liste: 55223,
antwort: 55224,
typ: ListenTyp::DzuListe,
sender_ebd: &[("ÜNB", "E_0070")],
empfaenger_rolle: "NB",
},
];
#[must_use]
pub fn familie_for(liste: u32) -> Option<&'static ListenFamilie> {
LISTEN_FAMILIEN.iter().find(|f| f.liste == liste)
}
#[must_use]
pub fn all_pids() -> Vec<u32> {
let mut v: Vec<u32> = LISTEN_FAMILIEN
.iter()
.flat_map(|f| [f.liste, f.antwort])
.collect();
v.sort_unstable();
v
}
pub const WORKFLOW_NAME: &str = "mabis-listenabgleich";
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ListenabgleichData {
pub pruefidentifikator: Pruefidentifikator,
pub typ: ListenTyp,
pub sender: MarktpartnerCode,
pub receiver: MarktpartnerCode,
pub billing_period: BillingPeriod,
pub message_ref: MessageRef,
pub mabis_zaehlpunkt: String,
pub zeitreihen_version: String,
pub listennummer: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum ListenabgleichEvent {
ListeErhalten {
pruefidentifikator: Pruefidentifikator,
typ: ListenTyp,
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
billing_period: BillingPeriod,
message_ref: MessageRef,
mabis_zaehlpunkt: String,
zeitreihen_version: String,
listennummer: String,
},
KorrekturGesendet {
antwort_pid: Pruefidentifikator,
korrekturen: u32,
},
GesamtAblehnungGesendet {
antwort_pid: Pruefidentifikator,
antwortcode: String,
pruefschritt: u16,
},
ValidationFailed {
reason: String,
},
}
impl EventPayload for ListenabgleichEvent {
fn event_type(&self) -> &'static str {
match self {
Self::ListeErhalten { .. } => "MabisListeErhalten",
Self::KorrekturGesendet { .. } => "MabisKorrekturGesendet",
Self::GesamtAblehnungGesendet { .. } => "MabisGesamtAblehnungGesendet",
Self::ValidationFailed { .. } => "MabisListenabgleichValidationFailed",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(tag = "status", content = "data")]
pub enum ListenabgleichState {
#[default]
New,
ListeErhalten(Box<ListenabgleichData>),
Abgeglichen {
typ: ListenTyp,
korrekturen: u32,
},
Abgelehnt {
typ: ListenTyp,
antwortcode: String,
},
ValidationFailed {
reason: String,
},
}
impl ListenabgleichState {
#[must_use]
pub fn label(&self) -> &'static str {
match self {
Self::New => "New",
Self::ListeErhalten(_) => "ListeErhalten",
Self::Abgeglichen { .. } => "Abgeglichen",
Self::Abgelehnt { .. } => "Abgelehnt",
Self::ValidationFailed { .. } => "ValidationFailed",
}
}
}
#[derive(Clone)]
pub enum ListenabgleichCommand {
ReceiveListe {
pid: Pruefidentifikator,
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
billing_period: BillingPeriod,
message_ref: MessageRef,
mabis_zaehlpunkt: String,
zeitreihen_version: String,
listennummer: String,
validation_passed: bool,
validation_errors: Vec<String>,
},
SendKorrektur {
sender_rolle: String,
positionen: Vec<Korrekturposition>,
},
SendGesamtAblehnung {
sender_rolle: String,
abonnement_bestellt: Option<bool>,
zeitraum_plausibel: Option<bool>,
mabis_zp_passt: Option<bool>,
version_zugelassen: Option<bool>,
innerhalb_clearingphase: Option<bool>,
},
}
impl CommandPayload for ListenabgleichCommand {}
fn answer_shape<'a>(
data: &ListenabgleichData,
sender_rolle: &str,
) -> Result<(&'a ListenFamilie, &'static str, Pruefidentifikator), WorkflowError> {
let familie = familie_for(data.pruefidentifikator.as_u32()).ok_or_else(|| {
WorkflowError::rejected(format!(
"no family for recorded list {}",
data.pruefidentifikator
))
})?;
let antwort_pid = Pruefidentifikator::new(familie.antwort).map_err(|e| {
WorkflowError::rejected(format!("invalid Antwort PID {}: {e}", familie.antwort))
})?;
let ebd = familie.antwort_ebd(sender_rolle).ok_or_else(|| {
WorkflowError::rejected(format!(
"{sender_rolle} verteilt die Liste {} nicht; zulässig: {:?}",
familie.liste,
familie.sender_rollen().collect::<Vec<_>>()
))
})?;
Ok((familie, ebd, antwort_pid))
}
pub struct MabisListenabgleichWorkflow;
impl Workflow for MabisListenabgleichWorkflow {
type State = ListenabgleichState;
type Event = ListenabgleichEvent;
type Command = ListenabgleichCommand;
fn apply(state: Self::State, event: &Self::Event) -> Self::State {
match event {
ListenabgleichEvent::ListeErhalten {
pruefidentifikator,
typ,
mabis_zaehlpunkt,
zeitreihen_version,
listennummer,
sender,
receiver,
billing_period,
message_ref,
} => ListenabgleichState::ListeErhalten(Box::new(ListenabgleichData {
mabis_zaehlpunkt: mabis_zaehlpunkt.clone(),
zeitreihen_version: zeitreihen_version.clone(),
listennummer: listennummer.clone(),
pruefidentifikator: *pruefidentifikator,
typ: *typ,
sender: sender.clone(),
receiver: receiver.clone(),
billing_period: billing_period.clone(),
message_ref: message_ref.clone(),
})),
ListenabgleichEvent::KorrekturGesendet { korrekturen, .. } => match state {
ListenabgleichState::ListeErhalten(d) => ListenabgleichState::Abgeglichen {
typ: d.typ,
korrekturen: *korrekturen,
},
other => other,
},
ListenabgleichEvent::GesamtAblehnungGesendet { antwortcode, .. } => match state {
ListenabgleichState::ListeErhalten(d) => ListenabgleichState::Abgelehnt {
typ: d.typ,
antwortcode: antwortcode.clone(),
},
other => other,
},
ListenabgleichEvent::ValidationFailed { reason } => {
ListenabgleichState::ValidationFailed {
reason: reason.clone(),
}
}
}
}
fn handle(
state: &Self::State,
command: Self::Command,
) -> Result<WorkflowOutput<Self::Event>, WorkflowError> {
match command {
ListenabgleichCommand::ReceiveListe {
pid,
mabis_zaehlpunkt,
zeitreihen_version,
listennummer,
sender,
receiver,
billing_period,
message_ref,
validation_passed,
validation_errors,
} => {
if !matches!(state, ListenabgleichState::New) {
return Ok(vec![].into());
}
let Some(familie) = familie_for(pid.as_u32()) else {
return Err(WorkflowError::rejected(format!(
"PID {pid} is not a MaBiS Listenabgleich list; expected one of {:?}",
LISTEN_FAMILIEN.iter().map(|f| f.liste).collect::<Vec<_>>()
)));
};
if !validation_passed {
return Ok(vec![ListenabgleichEvent::ValidationFailed {
reason: validation_errors.join("; "),
}]
.into());
}
Ok(vec![ListenabgleichEvent::ListeErhalten {
pruefidentifikator: pid,
mabis_zaehlpunkt,
zeitreihen_version,
listennummer,
typ: familie.typ,
sender,
receiver,
billing_period,
message_ref,
}]
.into())
}
ListenabgleichCommand::SendKorrektur {
sender_rolle,
positionen,
} => {
let ListenabgleichState::ListeErhalten(data) = state else {
return Err(WorkflowError::rejected(format!(
"SendKorrektur requires state ListeErhalten, got {}",
state.label()
)));
};
let (familie, ebd, antwort_pid) = answer_shape(data, &sender_rolle)?;
let mut eintraege = Vec::with_capacity(positionen.len());
for pos in &positionen {
let (code, _) = mako_pruefung::mabis::korrekturcode(ebd, pos.grund)
.ok_or_else(|| {
WorkflowError::rejected(format!(
"{ebd} veröffentlicht keinen Code für {:?}",
pos.grund
))
})?;
eintraege.push((pos.malo.clone(), code));
}
let korrekturen = u32::try_from(eintraege.len()).unwrap_or(u32::MAX);
let outbox = PendingOutbox::new(
"UTILMD",
data.sender.as_str(),
serde_json::json!({
"pid": familie.antwort,
"sender": data.receiver.as_str(),
"receiver": data.sender.as_str(),
"antwort_codeliste": ebd,
"mabis_zaehlpunkt": data.mabis_zaehlpunkt,
"zeitreihen_version": data.zeitreihen_version,
"listennummer": format!("{}-K", data.listennummer),
"referenz_listennummer": data.listennummer,
"korrekturen": korrekturen,
"positionen": eintraege
.iter()
.map(|(malo, code)| serde_json::json!({
"malo": malo,
"antwort_code": code.code,
"bedeutung": code.bedeutung,
}))
.collect::<Vec<_>>(),
"billing_period": data.billing_period.as_str(),
}),
);
Ok(WorkflowOutput {
events: vec![ListenabgleichEvent::KorrekturGesendet {
antwort_pid,
korrekturen,
}],
outbox: vec![outbox],
deadlines: vec![],
})
}
ListenabgleichCommand::SendGesamtAblehnung {
sender_rolle,
abonnement_bestellt,
zeitraum_plausibel,
mabis_zp_passt,
version_zugelassen,
innerhalb_clearingphase,
} => {
let ListenabgleichState::ListeErhalten(data) = state else {
return Err(WorkflowError::rejected(format!(
"SendGesamtAblehnung requires state ListeErhalten, got {}",
state.label()
)));
};
let (familie, ebd, antwort_pid) = answer_shape(data, &sender_rolle)?;
let pruefung = mako_pruefung::mabis::ListenPruefung {
abonnement_bestellt,
zeitraum_plausibel,
mabis_zp_passt,
version_zugelassen,
innerhalb_clearingphase,
positionen: &[],
};
let antwort = match mako_pruefung::mabis::pruefe_liste(ebd, &pruefung) {
mako_pruefung::mabis::ListenEntscheidung::GesamtAblehnung(a) => *a,
mako_pruefung::mabis::ListenEntscheidung::Korrekturliste(_) => {
return Err(WorkflowError::rejected(format!(
"{ebd} refuses no whole-list Prüfschritt on these facts — the Liste {} is assessable and owes a Korrekturliste",
familie.liste
)));
}
mako_pruefung::mabis::ListenEntscheidung::Eskalation {
grund,
pruefschritt,
} => {
return Err(WorkflowError::rejected(format!(
"{ebd} Prüfschritt {pruefschritt} is unanswered: {grund}"
)));
}
};
let outbox = PendingOutbox::new(
"UTILMD",
data.sender.as_str(),
serde_json::json!({
"pid": familie.antwort,
"sender": data.receiver.as_str(),
"receiver": data.sender.as_str(),
"antwort_code": antwort.code,
"antwort_codeliste": ebd,
"mabis_zaehlpunkt": data.mabis_zaehlpunkt,
"zeitreihen_version": data.zeitreihen_version,
"listennummer": format!("{}-A", data.listennummer),
"referenz_listennummer": data.listennummer,
"korrekturen": 0,
"positionen": [],
"bedeutung": antwort.bedeutung,
"billing_period": data.billing_period.as_str(),
}),
);
Ok(WorkflowOutput {
events: vec![ListenabgleichEvent::GesamtAblehnungGesendet {
antwort_pid,
antwortcode: antwort.code.clone(),
pruefschritt: antwort.pruefschritt,
}],
outbox: vec![outbox],
deadlines: vec![],
})
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mp(s: &str) -> MarktpartnerCode {
MarktpartnerCode::new(s)
}
fn pos(n: usize, grund: mako_pruefung::mabis::Korrekturgrund) -> Korrekturposition {
Korrekturposition {
malo: format!("5123869678{n}"),
grund,
}
}
fn korrektur(rolle: &str, positionen: Vec<Korrekturposition>) -> ListenabgleichCommand {
ListenabgleichCommand::SendKorrektur {
sender_rolle: rolle.to_owned(),
positionen,
}
}
fn ablehnung(rolle: &str) -> ListenabgleichCommand {
ListenabgleichCommand::SendGesamtAblehnung {
sender_rolle: rolle.to_owned(),
abonnement_bestellt: Some(true),
zeitraum_plausibel: Some(true),
mabis_zp_passt: Some(true),
version_zugelassen: Some(true),
innerhalb_clearingphase: Some(true),
}
}
fn receive(pid: u32) -> ListenabgleichCommand {
ListenabgleichCommand::ReceiveListe {
pid: Pruefidentifikator::new(pid).expect("valid PID"),
mabis_zaehlpunkt: "DE0004096999000000000000000000009".to_owned(),
zeitreihen_version: "20270115T090000000".to_owned(),
listennummer: "LST-1".to_owned(),
sender: mp("9900123456789"),
receiver: mp("9900987654321"),
billing_period: BillingPeriod::new("2026-07"),
message_ref: MessageRef::new("MSG-1"),
validation_passed: true,
validation_errors: vec![],
}
}
fn after(commands: &[ListenabgleichCommand]) -> ListenabgleichState {
let mut state = ListenabgleichState::default();
for cmd in commands {
let out = MabisListenabgleichWorkflow::handle(&state, cmd.clone())
.expect("the setup commands must succeed");
state = fold_from(state, &out.events);
}
state
}
fn fold_from(
start: ListenabgleichState,
events: &[ListenabgleichEvent],
) -> ListenabgleichState {
events
.iter()
.fold(start, MabisListenabgleichWorkflow::apply)
}
fn fold(events: &[ListenabgleichEvent]) -> ListenabgleichState {
events.iter().fold(ListenabgleichState::default(), |s, e| {
MabisListenabgleichWorkflow::apply(s, e)
})
}
#[test]
fn no_list_pid_is_also_a_reply_pid() {
for f in LISTEN_FAMILIEN {
assert!(
familie_for(f.antwort).is_none(),
"{} is a reply PID but also registered as a list — receiving it \
would ask for a correction to a correction",
f.antwort
);
}
assert_eq!(all_pids().len(), LISTEN_FAMILIEN.len() * 2);
}
#[test]
fn each_list_answers_with_its_own_pid() {
assert_eq!(familie_for(55065).unwrap().antwort, 55066);
assert_eq!(familie_for(55195).unwrap().antwort, 55196);
assert_eq!(familie_for(55201).unwrap().antwort, 55202);
assert_eq!(familie_for(55223).unwrap().antwort, 55224);
}
#[test]
fn the_lieferantenclearingliste_is_not_record_only() {
assert!(familie_for(55065).is_some());
assert!(!crate::clearingliste::CLEARINGLISTE_PIDS.contains(&55065));
}
#[test]
fn the_55066_ebd_depends_on_who_sent_the_list() {
let f = familie_for(55065).unwrap();
assert_eq!(f.antwort_ebd("NB"), Some("E_0047"));
assert_eq!(f.antwort_ebd("ÜNB"), Some("E_0004"));
assert_eq!(f.antwort_ebd("BIKO"), None, "never sent by the BIKO");
}
#[test]
fn every_family_names_an_ebd_for_every_sender_it_admits() {
for f in LISTEN_FAMILIEN {
assert!(!f.sender_ebd.is_empty(), "{} has no sender", f.liste);
for rolle in f.sender_rollen() {
let ebd = f.antwort_ebd(rolle).expect("declared sender");
assert!(ebd.starts_with("E_0"), "{} → {ebd}", f.liste);
}
}
}
#[test]
fn a_clean_reconciliation_still_sends_a_reply() {
let out =
MabisListenabgleichWorkflow::handle(&ListenabgleichState::New, receive(55223)).unwrap();
let state = fold(&out.events);
assert_eq!(state.label(), "ListeErhalten");
let out = MabisListenabgleichWorkflow::handle(&state, korrektur("ÜNB", vec![]))
.expect("clean reply");
assert_eq!(out.outbox.len(), 1, "a clean list still owes a reply");
assert_eq!(out.outbox[0].payload["pid"], 55224);
assert_eq!(out.outbox[0].payload["korrekturen"], 0);
}
#[test]
fn corrections_are_carried_into_the_terminal_state() {
let out =
MabisListenabgleichWorkflow::handle(&ListenabgleichState::New, receive(55201)).unwrap();
let state = fold(&out.events);
let out = MabisListenabgleichWorkflow::handle(
&state,
korrektur(
"NB",
(1..=3)
.map(|n| pos(n, mako_pruefung::mabis::Korrekturgrund::DatenFehlerhaft))
.collect(),
),
)
.unwrap();
assert_eq!(out.outbox[0].payload["pid"], 55202);
let final_state = out
.events
.iter()
.fold(state, MabisListenabgleichWorkflow::apply);
match final_state {
ListenabgleichState::Abgeglichen { typ, korrekturen } => {
assert_eq!(typ, ListenTyp::LfAacl);
assert_eq!(korrekturen, 3);
}
other => panic!("expected Abgeglichen, got {}", other.label()),
}
}
#[test]
fn the_ebd_follows_the_distributor_not_the_answer_pid() {
let out =
MabisListenabgleichWorkflow::handle(&ListenabgleichState::New, receive(55065)).unwrap();
let state = fold(&out.events);
let positionen = vec![pos(
1,
mako_pruefung::mabis::Korrekturgrund::DatenFehlerhaft,
)];
let code = |rolle: &str| {
let out =
MabisListenabgleichWorkflow::handle(&state, korrektur(rolle, positionen.clone()))
.expect("both roles distribute 55065");
(
out.outbox[0].payload["antwort_codeliste"]
.as_str()
.unwrap()
.to_owned(),
out.outbox[0].payload["positionen"][0]["antwort_code"]
.as_str()
.unwrap()
.to_owned(),
)
};
assert_eq!(code("NB"), ("E_0047".to_owned(), "A07".to_owned()));
assert_eq!(code("ÜNB"), ("E_0004".to_owned(), "A06".to_owned()));
}
#[test]
fn the_reply_goes_back_to_the_distributor() {
let out =
MabisListenabgleichWorkflow::handle(&ListenabgleichState::New, receive(55195)).unwrap();
let state = fold(&out.events);
let out = MabisListenabgleichWorkflow::handle(
&state,
korrektur(
"ÜNB",
vec![pos(
1,
mako_pruefung::mabis::Korrekturgrund::DatenFehlerhaft,
)],
),
)
.unwrap();
assert_eq!(out.outbox[0].payload["antwort_codeliste"], "E_0017");
assert_eq!(
out.outbox[0].recipient.as_ref(),
"9900123456789",
"the Korrekturliste travels back up the axis the list came down"
);
}
#[test]
fn a_reply_before_a_list_is_rejected() {
let err =
MabisListenabgleichWorkflow::handle(&ListenabgleichState::New, korrektur("NB", vec![]))
.expect_err("must reject");
assert!(format!("{err}").contains("ListeErhalten"), "{err}");
}
#[test]
fn a_record_only_clearingliste_pid_is_not_accepted_here() {
let err = MabisListenabgleichWorkflow::handle(&ListenabgleichState::New, receive(55069))
.expect_err("must reject");
assert!(
format!("{err}").contains("not a MaBiS Listenabgleich"),
"{err}"
);
}
#[test]
fn validation_failure_is_terminal_and_owes_nothing() {
let cmd = ListenabgleichCommand::ReceiveListe {
pid: Pruefidentifikator::new(55195).expect("valid PID"),
mabis_zaehlpunkt: "DE0004096999000000000000000000009".to_owned(),
zeitreihen_version: "20270115T090000000".to_owned(),
listennummer: "LST-1".to_owned(),
sender: mp("9900123456789"),
receiver: mp("9900987654321"),
billing_period: BillingPeriod::new("2026-07"),
message_ref: MessageRef::new("MSG-1"),
validation_passed: false,
validation_errors: vec!["SG6 LOC missing".to_owned()],
};
let out = MabisListenabgleichWorkflow::handle(&ListenabgleichState::New, cmd).unwrap();
assert!(out.outbox.is_empty());
assert_eq!(fold(&out.events).label(), "ValidationFailed");
}
#[test]
fn an_unordered_abonnement_refuses_the_whole_list() {
let state = after(&[receive(55_065)]);
let out = MabisListenabgleichWorkflow::handle(
&state,
ListenabgleichCommand::SendGesamtAblehnung {
sender_rolle: "ÜNB".to_owned(),
abonnement_bestellt: Some(false),
zeitraum_plausibel: Some(true),
mabis_zp_passt: Some(true),
version_zugelassen: Some(true),
innerhalb_clearingphase: Some(true),
},
)
.expect("an unordered Abonnement refuses the list");
let payload = &out.outbox[0].payload;
assert_eq!(payload["korrekturen"], 0);
assert_eq!(payload["positionen"].as_array().map(Vec::len), Some(0));
let code = payload["antwort_code"]
.as_str()
.expect("a code was resolved");
let entry = mako_pruefung::mabis::lookup(
payload["antwort_codeliste"]
.as_str()
.expect("the tree is named"),
code,
)
.expect("the tree publishes the code it answered with");
assert_eq!(
entry.cluster,
mako_pruefung::codes::Cluster::AblehnungDerGesamtenListe,
"{code} is not a whole-list refusal"
);
let next = out
.events
.iter()
.fold(state.clone(), MabisListenabgleichWorkflow::apply);
assert_eq!(next.label(), "Abgelehnt");
}
#[test]
fn an_assessable_list_may_not_be_refused_entire() {
let state = after(&[receive(55_065)]);
let err = MabisListenabgleichWorkflow::handle(&state, ablehnung("NB"))
.expect_err("an assessable list owes a Korrekturliste");
assert!(
format!("{err}").contains("Korrekturliste"),
"the refusal must say what the list is owed instead: {err}"
);
}
#[test]
fn an_unanswered_pruefschritt_escalates_rather_than_refusing() {
let state = after(&[receive(55_065)]);
let err = MabisListenabgleichWorkflow::handle(
&state,
ListenabgleichCommand::SendGesamtAblehnung {
sender_rolle: "ÜNB".to_owned(),
abonnement_bestellt: None,
zeitraum_plausibel: Some(true),
mabis_zp_passt: Some(true),
version_zugelassen: Some(true),
innerhalb_clearingphase: Some(true),
},
)
.expect_err("an unanswered Prüfschritt is not a refusal");
assert!(
format!("{err}").contains("unanswered"),
"the caller must be told which question is open: {err}"
);
}
#[test]
fn a_fact_the_tree_does_not_ask_refuses_nothing() {
let state = after(&[receive(55_065)]);
let err = MabisListenabgleichWorkflow::handle(
&state,
ListenabgleichCommand::SendGesamtAblehnung {
sender_rolle: "NB".to_owned(),
abonnement_bestellt: Some(false),
zeitraum_plausibel: Some(true),
mabis_zp_passt: Some(true),
version_zugelassen: Some(true),
innerhalb_clearingphase: Some(true),
},
)
.expect_err("E_0047 asks no Abonnement question");
assert!(format!("{err}").contains("Korrekturliste"), "{err}");
let out = MabisListenabgleichWorkflow::handle(
&state,
ListenabgleichCommand::SendGesamtAblehnung {
sender_rolle: "NB".to_owned(),
abonnement_bestellt: None,
zeitraum_plausibel: Some(true),
mabis_zp_passt: Some(true),
version_zugelassen: Some(false),
innerhalb_clearingphase: Some(true),
},
)
.expect("an inadmissible version refuses the list");
assert_eq!(out.outbox[0].payload["antwort_codeliste"], "E_0047");
assert_eq!(out.outbox[0].payload["korrekturen"], 0);
}
#[test]
fn the_refusal_resolves_its_tree_from_the_sender_role() {
let state = after(&[receive(55_065)]);
let err = MabisListenabgleichWorkflow::handle(&state, ablehnung("MSB"))
.expect_err("an MSB distributes no Clearingliste");
assert!(format!("{err}").contains("MSB"), "{err}");
}
}