use std::marker::PhantomData;
use edifact_rs::Writer;
use crate::AgencyCode;
use crate::{Error, Release};
use super::{Set, Unset, bytes_to_segments, today_ccyymmdd};
#[derive(Debug, Clone)]
struct IftstaBuilderInner {
release: Release,
sender_id: Option<String>,
receiver_id: Option<String>,
sender_agency: AgencyCode,
receiver_agency: AgencyCode,
message_ref: String,
document_code: String,
document_id: Option<String>,
document_date: Option<String>,
pruefidentifikator: Option<u32>,
sts_category: Option<String>,
sts_reason: Option<String>,
vorgangsnummer: Option<String>,
order_reference: Option<String>,
vertragsende: Option<String>,
}
#[derive(Debug, Clone)]
#[must_use = "Builder must be consumed via .build() or .serialize()"]
pub struct IftstaBuilder<S = Unset, R = Unset> {
_ph: PhantomData<fn() -> (S, R)>,
inner: IftstaBuilderInner,
}
impl IftstaBuilder<Unset, Unset> {
pub fn new(release: Release) -> Self {
Self {
_ph: PhantomData,
inner: IftstaBuilderInner {
release,
sender_id: None,
receiver_id: None,
sender_agency: AgencyCode::Bdew,
receiver_agency: AgencyCode::Bdew,
message_ref: "1".to_owned(),
document_code: "Z03".to_owned(),
document_id: None,
document_date: None,
pruefidentifikator: None,
sts_category: None,
sts_reason: None,
vorgangsnummer: None,
order_reference: None,
vertragsende: None,
},
}
}
}
impl<S, R> IftstaBuilder<S, R> {
fn transition<S2, R2>(self) -> IftstaBuilder<S2, R2> {
IftstaBuilder {
_ph: PhantomData,
inner: self.inner,
}
}
pub fn sender(mut self, id: impl Into<String>) -> IftstaBuilder<Set, R> {
self.inner.sender_id = Some(id.into());
self.transition()
}
pub fn receiver(mut self, id: impl Into<String>) -> IftstaBuilder<S, Set> {
self.inner.receiver_id = Some(id.into());
self.transition()
}
pub fn sender_agency(mut self, agency: crate::AgencyCode) -> Self {
self.inner.sender_agency = agency;
self
}
pub fn receiver_agency(mut self, agency: crate::AgencyCode) -> Self {
self.inner.receiver_agency = agency;
self
}
pub fn document_id(mut self, id: impl Into<String>) -> Self {
self.inner.document_id = Some(id.into());
self
}
pub fn document_code(mut self, code: impl Into<String>) -> Self {
self.inner.document_code = code.into();
self
}
pub fn message_ref(mut self, reference: impl Into<String>) -> Self {
self.inner.message_ref = reference.into();
self
}
pub fn document_date(mut self, date: impl Into<String>) -> Self {
self.inner.document_date = Some(date.into());
self
}
pub fn pruefidentifikator(mut self, pid: u32) -> Self {
self.inner.pruefidentifikator = Some(pid);
self
}
pub fn status(mut self, category: impl Into<String>, reason: impl Into<String>) -> Self {
self.inner.sts_category = Some(category.into());
self.inner.sts_reason = Some(reason.into());
self
}
pub fn vorgangsnummer(mut self, n: impl Into<String>) -> Self {
self.inner.vorgangsnummer = Some(n.into());
self
}
pub fn order_reference(mut self, reference: impl Into<String>) -> Self {
self.inner.order_reference = Some(reference.into());
self
}
pub fn vertragsende(mut self, date: impl Into<String>) -> Self {
self.inner.vertragsende = Some(date.into());
self
}
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
let dtm_val = self
.inner
.document_date
.as_deref()
.map_or_else(today_ccyymmdd, str::to_owned);
let mut buf = Vec::new();
let mut w = Writer::new(&mut buf);
let doc_id = self.inner.document_id.as_deref().unwrap_or("");
emit_comp!(
w,
"UNH",
[&self.inner.message_ref],
["IFTSTA", "D", "18A", "UN", self.inner.release.as_str()]
);
emit_seg!(w, "BGM", &self.inner.document_code, doc_id);
emit_comp!(w, "DTM", ["137", &dtm_val, "102"]);
if let Some(id) = &self.inner.sender_id {
emit_comp!(
w,
"NAD",
["MS"],
[id, "", self.inner.sender_agency.as_str()]
);
}
if let Some(id) = &self.inner.receiver_id {
emit_comp!(
w,
"NAD",
["MR"],
[id, "", self.inner.receiver_agency.as_str()]
);
}
if let Some(vn) = &self.inner.vorgangsnummer {
emit_seg!(w, "CNI", vn);
}
if let (Some(cat), Some(reason)) = (&self.inner.sts_category, &self.inner.sts_reason) {
emit_comp!(w, "STS", [cat], ["", reason]);
}
if let Some(pid) = self.inner.pruefidentifikator {
let pid_str = pid.to_string();
emit_comp!(w, "RFF", ["Z13", &pid_str]);
}
if let Some(order_ref) = &self.inner.order_reference {
emit_comp!(w, "RFF", ["AGI", order_ref]);
}
if let Some(ende) = &self.inner.vertragsende {
emit_comp!(w, "DTM", ["93", ende, "102"]);
}
w.finish_unt(&self.inner.message_ref)
.map_err(Error::Parse)?;
Ok(buf)
}
pub fn serialize(self) -> Result<Vec<u8>, Error> {
self.to_bytes()
}
}
impl IftstaBuilder<Set, Set> {
pub fn build(self) -> Result<crate::messages::iftsta::IftstaMessage, Error> {
let message_ref = self.inner.message_ref.clone();
let assoc_code = self.inner.release.as_str().to_owned();
let segments = bytes_to_segments(&self.to_bytes()?)?;
let pruefidentifikator = self.inner.pruefidentifikator;
Ok(crate::messages::iftsta::IftstaMessage::from_parts(
segments,
message_ref.as_str(),
assoc_code.as_str(),
pruefidentifikator,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::EdiEnergyMessage;
#[test]
fn builds_a_conformant_21042_beendigung() {
let msg = IftstaBuilder::new(Release::new("2.0g"))
.sender("4012345000023")
.receiver("9900555000005")
.document_code("Z09")
.document_id("00021042")
.pruefidentifikator(21042)
.status("Z21", "105")
.vorgangsnummer("1")
.order_reference("BEST-4711")
.vertragsende("20260801")
.build()
.expect("build 21042");
assert_eq!(msg.detect_pruefidentifikator().unwrap().as_u32(), 21042);
let wire = String::from_utf8(msg.serialize().expect("serialize")).unwrap();
assert!(
wire.contains("RFF+Z13:21042"),
"PID in SG15 RFF+Z13: {wire}"
);
assert!(
wire.contains("STS+Z21+:105"),
"STS Z21/105 „beendet\": {wire}"
);
assert!(wire.contains("CNI+1"), "Vorgangsnummer SG14 CNI: {wire}");
assert!(
wire.contains("RFF+AGI:BEST-4711"),
"order ref SG15 RFF+AGI: {wire}"
);
let report = msg.validate().unwrap();
assert!(report.is_valid(), "21042 must be AHB-conformant: {report}");
}
}