use mako_engine::{
error::WorkflowError,
ids::DeadlineId,
types::{MarktpartnerCode, MessageRef, Pruefidentifikator},
workflow::{CommandPayload, EventPayload, Workflow, WorkflowOutput},
};
pub const PRICAT_PIDS: &[u32] = &[27001, 27002, 27003];
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum PreislisteEvent {
PreislisteErhalten {
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
message_ref: MessageRef,
pruefidentifikator: Pruefidentifikator,
},
ValidationPassed {
message_ref: MessageRef,
},
Aufgezeichnet,
Rejected {
reason: String,
},
}
impl EventPayload for PreislisteEvent {
fn event_type(&self) -> &'static str {
match self {
Self::PreislisteErhalten { .. } => "PreislisteErhalten",
Self::ValidationPassed { .. } => "PreislisteValidationPassed",
Self::Aufgezeichnet => "PreislisteAufgezeichnet",
Self::Rejected { .. } => "PreislisteRejected",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PreislisteData {
pub sender: MarktpartnerCode,
pub receiver: MarktpartnerCode,
pub pruefidentifikator: Pruefidentifikator,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "status", content = "data")]
pub enum PreislisteState {
New,
Eingegangen(PreislisteData),
Aufgezeichnet(PreislisteData),
Rejected {
reason: String,
},
}
impl Default for PreislisteState {
fn default() -> Self {
Self::New
}
}
impl PreislisteState {
#[must_use]
pub fn label(&self) -> &'static str {
match self {
Self::New => "New",
Self::Eingegangen(_) => "Eingegangen",
Self::Aufgezeichnet(_) => "Aufgezeichnet",
Self::Rejected { .. } => "Rejected",
}
}
}
#[derive(Clone)]
pub enum PreislisteCommand {
ReceivePricat {
pid: Pruefidentifikator,
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
message_ref: MessageRef,
validation_passed: bool,
validation_errors: Vec<String>,
},
Aufzeichnen,
TimeoutExpired {
deadline_id: DeadlineId,
label: Box<str>,
},
}
impl CommandPayload for PreislisteCommand {}
pub struct WimPreislisteWorkflow;
impl Workflow for WimPreislisteWorkflow {
type State = PreislisteState;
type Event = PreislisteEvent;
type Command = PreislisteCommand;
fn apply(state: Self::State, event: &Self::Event) -> Self::State {
match event {
PreislisteEvent::PreislisteErhalten {
sender,
receiver,
pruefidentifikator,
..
} => PreislisteState::Eingegangen(PreislisteData {
sender: sender.clone(),
receiver: receiver.clone(),
pruefidentifikator: *pruefidentifikator,
}),
PreislisteEvent::ValidationPassed { .. } => state, PreislisteEvent::Aufgezeichnet => match state {
PreislisteState::Eingegangen(data) => PreislisteState::Aufgezeichnet(data),
other => other,
},
PreislisteEvent::Rejected { reason } => PreislisteState::Rejected {
reason: reason.clone(),
},
}
}
fn handle(
state: &Self::State,
command: Self::Command,
) -> Result<WorkflowOutput<Self::Event>, WorkflowError> {
match command {
PreislisteCommand::ReceivePricat {
pid,
sender,
receiver,
message_ref,
validation_passed,
validation_errors,
} => {
if !matches!(state, PreislisteState::New) {
return Err(WorkflowError::invalid_state("New", state.label()));
}
if !PRICAT_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"expected PRICAT PID (27001–27003), got {pid}",
)));
}
let mut events = vec![PreislisteEvent::PreislisteErhalten {
sender,
receiver,
message_ref: message_ref.clone(),
pruefidentifikator: pid,
}];
if validation_passed {
events.push(PreislisteEvent::ValidationPassed { message_ref });
} else {
events.push(PreislisteEvent::Rejected {
reason: validation_errors.join("; "),
});
}
Ok(events.into())
}
PreislisteCommand::Aufzeichnen => {
if !matches!(state, PreislisteState::Eingegangen(_)) {
return Err(WorkflowError::invalid_state("Eingegangen", state.label()));
}
Ok(vec![PreislisteEvent::Aufgezeichnet].into())
}
PreislisteCommand::TimeoutExpired { .. } => Ok(vec![].into()),
}
}
}
#[cfg(test)]
mod tests {
use mako_engine::workflow::Workflow;
use super::*;
fn pid(code: u32) -> Pruefidentifikator {
Pruefidentifikator::new(code).unwrap()
}
fn mcod(s: &str) -> MarktpartnerCode {
MarktpartnerCode::new(s)
}
fn mref(s: &str) -> MessageRef {
MessageRef::new(s)
}
#[test]
fn pricat_27002_happy_path() {
let out = WimPreislisteWorkflow::handle(
&PreislisteState::New,
PreislisteCommand::ReceivePricat {
pid: pid(27002),
sender: mcod("9900357000004"),
receiver: mcod("4012345000023"),
message_ref: mref("PRICAT-001"),
validation_passed: true,
validation_errors: vec![],
},
)
.unwrap();
assert_eq!(out.events.len(), 2); let state = out
.events
.iter()
.fold(PreislisteState::New, WimPreislisteWorkflow::apply);
assert!(matches!(state, PreislisteState::Eingegangen(_)));
let out = WimPreislisteWorkflow::handle(&state, PreislisteCommand::Aufzeichnen).unwrap();
let state = out.events.iter().fold(state, WimPreislisteWorkflow::apply);
assert!(matches!(state, PreislisteState::Aufgezeichnet(_)));
}
#[test]
fn pricat_wrong_pid_rejected() {
let result = WimPreislisteWorkflow::handle(
&PreislisteState::New,
PreislisteCommand::ReceivePricat {
pid: pid(55001),
sender: mcod("9900357000004"),
receiver: mcod("4012345000023"),
message_ref: mref("X"),
validation_passed: true,
validation_errors: vec![],
},
);
assert!(result.is_err());
}
}