use mako_engine::types::Pruefidentifikator;
use mako_engine::{
error::WorkflowError,
ids::DeadlineId,
outbox::PendingOutbox,
types::{MaLo, MarktpartnerCode, MessageRef},
workflow::{CommandPayload, EventPayload, Workflow, WorkflowOutput},
};
pub const WORKFLOW_NAME: &str = "gpke-sperrung";
pub const SPERRUNG_PIDS: &[u32] = &[17115, 17116, 17117];
pub const ORDCHG_STORNIERUNG_PIDS: &[u32] = &[39000, 39001];
pub const MSB_ANTWORT_PIDS: &[u32] = &[19118, 19119];
pub const SPERRUNG_WINDOW_LABEL: &str = "gpke-sperrung-window";
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum SperrungEvent {
AnweisungErhalten {
location_id: MaLo,
sender: MarktpartnerCode,
document_date: String,
message_ref: MessageRef,
pruefidentifikator: Pruefidentifikator,
},
ValidationPassed {
message_ref: MessageRef,
},
AusfuehrungBestaetigt {
durchgefuehrt: bool,
reason: Option<String>,
},
StornierungErhalten {
pruefidentifikator: Pruefidentifikator,
sender: MarktpartnerCode,
message_ref: MessageRef,
},
MsbAntwortErhalten {
pruefidentifikator: Pruefidentifikator,
is_confirmed: bool,
message_ref: MessageRef,
},
Rejected {
reason: String,
},
DeadlineExpired {
deadline_id: DeadlineId,
label: Box<str>,
},
}
impl EventPayload for SperrungEvent {
fn event_type(&self) -> &'static str {
match self {
Self::AnweisungErhalten { .. } => "SperrungAnweisungErhalten",
Self::ValidationPassed { .. } => "SperrungValidationPassed",
Self::AusfuehrungBestaetigt { .. } => "SperrungAusfuehrungBestaetigt",
Self::StornierungErhalten { .. } => "SperrungStornierungErhalten",
Self::MsbAntwortErhalten { .. } => "SperrungMsbAntwortErhalten",
Self::Rejected { .. } => "SperrungRejected",
Self::DeadlineExpired { .. } => "SperrungDeadlineExpired",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SperrungData {
pub location_id: MaLo,
pub sender: MarktpartnerCode,
pub document_date: String,
pub pruefidentifikator: Pruefidentifikator,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "status", content = "data")]
#[derive(Default)]
pub enum SperrungState {
#[default]
New,
AnweisungErhalten(SperrungData),
ValidationPassed(SperrungData),
Ausgefuehrt(SperrungData),
Storniert(SperrungData),
Rejected {
reason: String,
},
}
impl SperrungState {
#[must_use]
pub fn label(&self) -> &'static str {
match self {
Self::New => "New",
Self::AnweisungErhalten(_) => "AnweisungErhalten",
Self::ValidationPassed(_) => "ValidationPassed",
Self::Ausgefuehrt(_) => "Ausgefuehrt",
Self::Storniert(_) => "Storniert",
Self::Rejected { .. } => "Rejected",
}
}
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(
self,
Self::Ausgefuehrt(_) | Self::Storniert(_) | Self::Rejected { .. }
)
}
#[must_use]
pub fn sperrung_data(&self) -> Option<&SperrungData> {
match self {
Self::AnweisungErhalten(d)
| Self::ValidationPassed(d)
| Self::Ausgefuehrt(d)
| Self::Storniert(d) => Some(d),
Self::New | Self::Rejected { .. } => None,
}
}
}
#[derive(Clone)]
pub enum SperrungCommand {
ReceiveSperrung {
pid: Pruefidentifikator,
sender: MarktpartnerCode,
receiver: MarktpartnerCode,
location_id: MaLo,
document_date: String,
message_ref: MessageRef,
validation_passed: bool,
validation_errors: Vec<String>,
},
BestaetigueSperrung {
durchgefuehrt: bool,
reason: Option<String>,
},
ReceiveStornierung {
pid: Pruefidentifikator,
sender: MarktpartnerCode,
message_ref: MessageRef,
},
ReceiveMsbAntwort {
pid: Pruefidentifikator,
is_confirmed: bool,
message_ref: MessageRef,
},
TimeoutExpired {
deadline_id: DeadlineId,
label: Box<str>,
},
}
impl CommandPayload for SperrungCommand {}
pub struct GpkeSperrungWorkflow;
impl Workflow for GpkeSperrungWorkflow {
type State = SperrungState;
type Event = SperrungEvent;
type Command = SperrungCommand;
fn on_deadline(
deadline: &mako_engine::deadline::Deadline,
state: &Self::State,
) -> Option<Self::Command> {
match (deadline.label(), state) {
(
SPERRUNG_WINDOW_LABEL,
SperrungState::AnweisungErhalten(_) | SperrungState::ValidationPassed(_),
) => Some(SperrungCommand::TimeoutExpired {
deadline_id: deadline.deadline_id(),
label: deadline.label().into(),
}),
_ => None,
}
}
fn apply(state: Self::State, event: &Self::Event) -> Self::State {
match event {
SperrungEvent::AnweisungErhalten {
location_id,
sender,
document_date,
pruefidentifikator,
..
} => SperrungState::AnweisungErhalten(SperrungData {
location_id: location_id.clone(),
sender: sender.clone(),
document_date: document_date.clone(),
pruefidentifikator: *pruefidentifikator,
}),
SperrungEvent::ValidationPassed { .. } => match state {
SperrungState::AnweisungErhalten(data) => SperrungState::ValidationPassed(data),
other => other,
},
SperrungEvent::AusfuehrungBestaetigt {
durchgefuehrt,
reason,
} => {
if *durchgefuehrt {
match state {
SperrungState::ValidationPassed(data) => SperrungState::Ausgefuehrt(data),
other => other,
}
} else {
let msg = reason
.as_deref()
.unwrap_or("Sperrung konnte nicht durchgeführt werden");
SperrungState::Rejected {
reason: msg.to_owned(),
}
}
}
SperrungEvent::Rejected { reason } => SperrungState::Rejected {
reason: reason.clone(),
},
SperrungEvent::StornierungErhalten { .. } => match state {
SperrungState::AnweisungErhalten(data) | SperrungState::ValidationPassed(data) => {
SperrungState::Storniert(data)
}
other => other,
},
SperrungEvent::MsbAntwortErhalten { .. } => {
state
}
SperrungEvent::DeadlineExpired { label, .. } => match state {
SperrungState::Ausgefuehrt(_)
| SperrungState::Storniert(_)
| SperrungState::Rejected { .. } => state,
_ => SperrungState::Rejected {
reason: format!("deadline expired: {label}"),
},
},
}
}
fn handle(
state: &Self::State,
command: Self::Command,
) -> Result<WorkflowOutput<Self::Event>, WorkflowError> {
match command {
SperrungCommand::ReceiveSperrung {
pid,
sender,
receiver,
location_id,
document_date,
message_ref,
validation_passed,
validation_errors,
} => {
if !matches!(state, SperrungState::New) {
return Err(WorkflowError::invalid_state("New", state.label()));
}
if !SPERRUNG_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"expected a Sperrung PID (17115, 17116, or 17117), got {pid}",
)));
}
let sender_mp_id = sender.clone();
let receiver_gln = receiver.clone();
let mut events = vec![SperrungEvent::AnweisungErhalten {
location_id,
sender,
document_date,
message_ref: message_ref.clone(),
pruefidentifikator: pid,
}];
if validation_passed {
events.push(SperrungEvent::ValidationPassed {
message_ref: message_ref.clone(),
});
let outbox = vec![
PendingOutbox::aperak_anerkennung(
receiver_gln.as_str(),
sender_mp_id.as_str(),
message_ref.as_str(),
)
.caused_by(1),
];
Ok(WorkflowOutput::with_outbox(events, outbox))
} else {
let reason = validation_errors.join("; ");
events.push(SperrungEvent::Rejected {
reason: reason.clone(),
});
let outbox = vec![
PendingOutbox::aperak_fehler(
receiver_gln.as_str(),
sender_mp_id.as_str(),
message_ref.as_str(),
mako_engine::erc::codes::Z29,
reason,
)
.caused_by(0),
];
Ok(WorkflowOutput::with_outbox(events, outbox))
}
}
SperrungCommand::BestaetigueSperrung {
durchgefuehrt,
reason,
} => {
if !matches!(state, SperrungState::ValidationPassed(_)) {
return Err(WorkflowError::invalid_state(
"ValidationPassed",
state.label(),
));
}
Ok(vec![SperrungEvent::AusfuehrungBestaetigt {
durchgefuehrt,
reason,
}]
.into())
}
SperrungCommand::TimeoutExpired { deadline_id, label } => {
if state.is_terminal() {
return Ok(WorkflowOutput::events(vec![]));
}
Ok(vec![SperrungEvent::DeadlineExpired { deadline_id, label }].into())
}
SperrungCommand::ReceiveStornierung {
pid,
sender,
message_ref,
} => {
if !ORDCHG_STORNIERUNG_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"expected a Stornierung PID (39000 or 39001), got {pid}",
)));
}
if matches!(
state,
SperrungState::Ausgefuehrt(_)
| SperrungState::Storniert(_)
| SperrungState::Rejected { .. }
) {
return Err(WorkflowError::rejected(format!(
"Stornierung rejected: process is already terminal ({})",
state.label()
)));
}
Ok(vec![SperrungEvent::StornierungErhalten {
pruefidentifikator: pid,
sender,
message_ref,
}]
.into())
}
SperrungCommand::ReceiveMsbAntwort {
pid,
is_confirmed,
message_ref,
} => {
if !MSB_ANTWORT_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"expected an MSB-Antwort PID (19118 or 19119), got {pid}",
)));
}
Ok(vec![SperrungEvent::MsbAntwortErhalten {
pruefidentifikator: pid,
is_confirmed,
message_ref,
}]
.into())
}
}
}
}