use std::collections::HashMap;
use mako_engine::{envelope::EventEnvelope, projection::Projection};
use crate::zp_lifecycle::{ZpLifecycleEvent, ZpSerie, ZpVorgang};
const EVENT_TYPE_PREFIX: &str = "MabisZp";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ZpAktivierung {
pub mabis_zp_id: String,
pub serie: ZpSerie,
pub billing_period: String,
pub aktiv: bool,
}
#[derive(Debug, Clone)]
struct Anfrage {
mabis_zp_id: String,
serie: ZpSerie,
vorgang: ZpVorgang,
billing_period: String,
}
#[derive(Debug, Default)]
pub struct ZpRegister {
pending: HashMap<String, Anfrage>,
zp: HashMap<String, (ZpAktivierung, u64)>,
last_seq: u64,
unlesbar: usize,
}
impl ZpRegister {
#[must_use]
pub fn alle(&self) -> Vec<&ZpAktivierung> {
let mut out: Vec<&ZpAktivierung> = self.zp.values().map(|(a, _)| a).collect();
out.sort_by(|a, b| a.mabis_zp_id.cmp(&b.mabis_zp_id));
out
}
#[must_use]
pub fn auf_beendeter_serie(&self, am: time::Date) -> Vec<&ZpAktivierung> {
self.alle()
.into_iter()
.filter(|a| a.aktiv && !a.serie.gilt_am(am))
.collect()
}
#[must_use]
pub fn aktive_anzahl(&self) -> usize {
self.zp.values().filter(|(a, _)| a.aktiv).count()
}
#[must_use]
pub fn unlesbar(&self) -> usize {
self.unlesbar
}
fn bestaetigen(&mut self, stream: &str, seq: u64) {
let Some(a) = self.pending.get(stream).cloned() else {
return;
};
let Anfrage {
mabis_zp_id: zp_id,
serie,
vorgang,
billing_period,
} = a;
let entry = self.zp.entry(zp_id.clone());
let aktiv = vorgang == ZpVorgang::Aktivierung;
match entry {
std::collections::hash_map::Entry::Occupied(mut o) => {
if o.get().1 > seq {
return;
}
let (a, s) = o.get_mut();
a.serie = serie;
a.aktiv = aktiv;
a.billing_period = billing_period;
*s = seq;
}
std::collections::hash_map::Entry::Vacant(v) => {
v.insert((
ZpAktivierung {
mabis_zp_id: zp_id,
serie,
billing_period,
aktiv,
},
seq,
));
}
}
}
}
impl Projection for ZpRegister {
fn name(&self) -> &'static str {
"MabisZpRegister"
}
fn handle_event(&mut self, envelope: &EventEnvelope) {
self.last_seq = self.last_seq.max(envelope.sequence_number);
if !envelope.event_type.starts_with(EVENT_TYPE_PREFIX) {
return;
}
let Ok(event) = envelope.decode::<ZpLifecycleEvent>() else {
self.unlesbar += 1;
return;
};
let stream = envelope.stream_id.as_str();
match event {
ZpLifecycleEvent::AnfrageErhalten {
vorgang,
serie,
mabis_zp_id,
billing_period,
..
} => {
self.pending.insert(
stream.to_owned(),
Anfrage {
mabis_zp_id,
serie,
vorgang,
billing_period: billing_period.as_str().to_owned(),
},
);
}
ZpLifecycleEvent::AnfrageGesendet {
vorgang,
serie,
mabis_zp_id,
billing_period,
..
} => {
self.pending.insert(
stream.to_owned(),
Anfrage {
mabis_zp_id: mabis_zp_id.to_string(),
serie,
vorgang,
billing_period: billing_period.as_str().to_owned(),
},
);
}
ZpLifecycleEvent::AntwortGesendet { bestaetigt, .. }
| ZpLifecycleEvent::AntwortErhalten { bestaetigt, .. } => {
if bestaetigt {
self.bestaetigen(stream, envelope.sequence_number);
}
}
ZpLifecycleEvent::Erfasst { .. } => {
self.bestaetigen(stream, envelope.sequence_number);
}
ZpLifecycleEvent::WeiterleitungGesendet { .. }
| ZpLifecycleEvent::ValidationFailed { .. } => {}
}
}
fn last_sequence(&self) -> Option<u64> {
(self.last_seq != 0).then_some(self.last_seq)
}
}
#[cfg(test)]
mod tests {
use super::*;
use mako_engine::workflow::EventPayload as _;
#[test]
fn the_prefix_covers_every_event_type() {
let all = [
ZpLifecycleEvent::Erfasst {
message_ref: mako_engine::types::MessageRef::new("R"),
},
ZpLifecycleEvent::ValidationFailed {
reason: "r".to_owned(),
},
ZpLifecycleEvent::AntwortGesendet {
antwort_pid: mako_engine::types::Pruefidentifikator::new(55_198)
.expect("valid PID"),
ebd: "E_0071".to_owned(),
bestaetigt: true,
grund: None,
},
ZpLifecycleEvent::WeiterleitungGesendet {
weiterleitung_pid: mako_engine::types::Pruefidentifikator::new(55_062)
.expect("valid PID"),
empfaenger: mako_engine::types::MarktpartnerCode::new("9900123456789"),
},
];
for e in &all {
assert!(
e.event_type().starts_with(EVENT_TYPE_PREFIX),
"{} does not start with {EVENT_TYPE_PREFIX} — the register would drop it",
e.event_type()
);
}
}
}