use mako_engine::{
error::WorkflowError,
ids::DeadlineId,
types::{MarktpartnerCode, MessageRef, Pruefidentifikator},
workflow::{CommandPayload, EventPayload, Workflow, WorkflowOutput},
};
pub const WIM_INVOIC_PIDS: &[u32] = &[31009];
pub const WIM_REMADV_PIDS: &[u32] = &[33001, 33002];
pub const WIM_COMDIS_ABLEHNUNG_PID: u32 = 29001;
pub const WORKFLOW_NAME: &str = "wim-rechnung";
pub const WIM_RECHNUNG_WINDOW_LABEL: &str = "wim-invoic-settlement-deadline";
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum WimRechnungEvent {
InvoicReceived {
invoice_ref: MessageRef,
sender: MarktpartnerCode,
recipient: MarktpartnerCode,
document_date: String,
pruefidentifikator: Pruefidentifikator,
},
Rejected {
reason: String,
},
DeadlineExpired {
deadline_id: DeadlineId,
label: Box<str>,
},
Settled,
Disputed {
reason: String,
},
RemadvReceived {
pid: Pruefidentifikator,
remadv_ref: MessageRef,
sender: MarktpartnerCode,
is_confirmed: bool,
},
ComdisAbLehnungReceived {
comdis_ref: MessageRef,
},
}
impl EventPayload for WimRechnungEvent {
fn event_type(&self) -> &'static str {
match self {
Self::InvoicReceived { .. } => "WimRechnungInvoicReceived",
Self::Rejected { .. } => "WimRechnungRejected",
Self::DeadlineExpired { .. } => "WimRechnungDeadlineExpired",
Self::Settled => "WimRechnungSettled",
Self::Disputed { .. } => "WimRechnungDisputed",
Self::RemadvReceived { .. } => "WimRechnungRemadvReceived",
Self::ComdisAbLehnungReceived { .. } => "WimRechnungComdisAbLehnungReceived",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum WimRechnungCommand {
ReceiveInvoic {
invoice_ref: MessageRef,
sender: MarktpartnerCode,
recipient: MarktpartnerCode,
document_date: String,
pruefidentifikator: Pruefidentifikator,
validation_passed: bool,
validation_errors: Vec<String>,
},
Settle,
Dispute {
reason: String,
},
TimeoutExpired {
deadline_id: DeadlineId,
label: Box<str>,
},
ReceiveRemadv {
pid: Pruefidentifikator,
remadv_ref: MessageRef,
sender: MarktpartnerCode,
},
ReceiveComdis {
comdis_ref: MessageRef,
},
}
impl CommandPayload for WimRechnungCommand {}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub enum WimRechnungState {
#[default]
New,
PendingSettlement {
invoice_ref: MessageRef,
pruefidentifikator: Pruefidentifikator,
},
Settled,
Disputed {
reason: String,
},
Rejected {
reason: String,
},
PaymentConfirmed,
PaymentDisputed {
remadv_pid: Pruefidentifikator,
},
ComdisRejected,
}
pub struct WimRechnungWorkflow;
impl Workflow for WimRechnungWorkflow {
type Command = WimRechnungCommand;
type Event = WimRechnungEvent;
type State = WimRechnungState;
fn apply(state: Self::State, event: &Self::Event) -> Self::State {
match event {
WimRechnungEvent::InvoicReceived {
invoice_ref,
pruefidentifikator,
..
} => WimRechnungState::PendingSettlement {
invoice_ref: invoice_ref.clone(),
pruefidentifikator: *pruefidentifikator,
},
WimRechnungEvent::Rejected { reason } => WimRechnungState::Rejected {
reason: reason.clone(),
},
WimRechnungEvent::Settled => WimRechnungState::Settled,
WimRechnungEvent::Disputed { reason } => WimRechnungState::Disputed {
reason: reason.clone(),
},
WimRechnungEvent::DeadlineExpired { label, .. } => match state {
WimRechnungState::Settled
| WimRechnungState::Disputed { .. }
| WimRechnungState::Rejected { .. }
| WimRechnungState::PaymentConfirmed
| WimRechnungState::PaymentDisputed { .. }
| WimRechnungState::ComdisRejected => state,
_ => WimRechnungState::Rejected {
reason: format!("settlement deadline expired: {label}"),
},
},
WimRechnungEvent::RemadvReceived {
pid, is_confirmed, ..
} => {
if *is_confirmed {
WimRechnungState::PaymentConfirmed
} else {
WimRechnungState::PaymentDisputed { remadv_pid: *pid }
}
}
WimRechnungEvent::ComdisAbLehnungReceived { .. } => WimRechnungState::ComdisRejected,
}
}
fn handle(
state: &Self::State,
command: Self::Command,
) -> Result<WorkflowOutput<Self::Event>, WorkflowError> {
match command {
WimRechnungCommand::ReceiveInvoic {
invoice_ref,
sender,
recipient,
document_date,
pruefidentifikator,
validation_passed,
validation_errors,
} => {
if !matches!(state, WimRechnungState::New) {
return Err(WorkflowError::invalid_state("New", format!("{state:?}")));
}
if !WIM_INVOIC_PIDS.contains(&pruefidentifikator.as_u32()) {
return Err(WorkflowError::rejected(format!(
"expected a WiM INVOIC PID (31003 or 31009), got {pruefidentifikator}"
)));
}
let events = if validation_passed {
vec![WimRechnungEvent::InvoicReceived {
invoice_ref,
sender,
recipient,
document_date,
pruefidentifikator,
}]
} else {
vec![WimRechnungEvent::Rejected {
reason: validation_errors.join("; "),
}]
};
Ok(WorkflowOutput::events(events))
}
WimRechnungCommand::Settle => {
if !matches!(state, WimRechnungState::PendingSettlement { .. }) {
return Err(WorkflowError::invalid_state(
"PendingSettlement",
format!("{state:?}"),
));
}
Ok(WorkflowOutput::events(vec![WimRechnungEvent::Settled]))
}
WimRechnungCommand::Dispute { reason } => {
if !matches!(state, WimRechnungState::PendingSettlement { .. }) {
return Err(WorkflowError::invalid_state(
"PendingSettlement",
format!("{state:?}"),
));
}
Ok(WorkflowOutput::events(vec![WimRechnungEvent::Disputed {
reason,
}]))
}
WimRechnungCommand::TimeoutExpired { deadline_id, label } => {
if !matches!(state, WimRechnungState::PendingSettlement { .. }) {
return Err(WorkflowError::invalid_state(
"PendingSettlement",
format!("{state:?}"),
));
}
Ok(WorkflowOutput::events(vec![
WimRechnungEvent::DeadlineExpired { deadline_id, label },
]))
}
WimRechnungCommand::ReceiveRemadv {
pid,
remadv_ref,
sender,
} => {
if !matches!(
state,
WimRechnungState::Settled | WimRechnungState::PendingSettlement { .. }
) {
return Err(WorkflowError::invalid_state(
"Settled|PendingSettlement",
format!("{state:?}"),
));
}
if !WIM_REMADV_PIDS.contains(&pid.as_u32()) {
return Err(WorkflowError::rejected(format!(
"expected a WiM REMADV PID (33001 or 33002), got {pid}",
)));
}
let is_confirmed = pid.as_u32() == 33001;
Ok(WorkflowOutput::events(vec![
WimRechnungEvent::RemadvReceived {
pid,
remadv_ref,
sender,
is_confirmed,
},
]))
}
WimRechnungCommand::ReceiveComdis { comdis_ref } => {
if matches!(
state,
WimRechnungState::New
| WimRechnungState::Rejected { .. }
| WimRechnungState::ComdisRejected
) {
return Err(WorkflowError::invalid_state(
"Settled|PendingSettlement",
format!("{state:?}"),
));
}
Ok(WorkflowOutput::events(vec![
WimRechnungEvent::ComdisAbLehnungReceived { comdis_ref },
]))
}
}
}
}