use std::marker::PhantomData;
use edifact_rs::Writer;
use crate::AgencyCode;
use crate::{Error, Release};
use super::{Set, Unset, bytes_to_segments};
#[derive(Debug, Clone)]
struct RemadvBuilderInner {
release: Release,
sender_id: Option<String>,
receiver_id: Option<String>,
sender_agency: Option<AgencyCode>,
receiver_agency: Option<AgencyCode>,
message_ref: String,
document_code: Option<String>,
document_id: Option<String>,
document_date: Option<String>,
pruefidentifikator: Option<String>,
waehrung: String,
rechnung: Option<Rechnungsbezug>,
abweichungsgruende: Vec<Abweichungsgrund>,
positionsfehler: Vec<Positionsfehler>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Positionsfehler {
pub positionsnummer: u16,
pub gruende: Vec<Abweichungsgrund>,
pub erlaeuterung: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rechnungsbezug {
pub dokumentenart: String,
pub rechnungsnummer: String,
pub faelliger_betrag: String,
pub ueberweisungsbetrag: String,
pub rechnungsdatum: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Abweichungsgrund {
pub code: String,
pub ebd: Option<String>,
}
impl Abweichungsgrund {
#[must_use]
pub fn new(code: impl Into<String>, ebd: impl Into<String>) -> Self {
Self {
code: code.into(),
ebd: Some(ebd.into()),
}
}
}
#[derive(Debug, Clone)]
#[must_use = "Builder must be consumed via .build() or .serialize()"]
pub struct RemadvBuilder<S = Unset, R = Unset> {
_ph: PhantomData<fn() -> (S, R)>,
inner: RemadvBuilderInner,
}
impl RemadvBuilder<Unset, Unset> {
pub fn new(release: Release) -> Self {
Self {
_ph: PhantomData,
inner: RemadvBuilderInner {
release,
sender_id: None,
receiver_id: None,
sender_agency: None,
receiver_agency: None,
message_ref: "1".to_owned(),
document_code: None,
document_id: None,
document_date: None,
pruefidentifikator: None,
waehrung: "EUR".to_owned(),
rechnung: None,
abweichungsgruende: Vec::new(),
positionsfehler: Vec::new(),
},
}
}
}
impl<S, R> RemadvBuilder<S, R> {
fn transition<S2, R2>(self) -> RemadvBuilder<S2, R2> {
RemadvBuilder {
_ph: PhantomData,
inner: self.inner,
}
}
pub fn sender(mut self, id: impl Into<String>) -> RemadvBuilder<Set, R> {
self.inner.sender_id = Some(id.into());
self.transition()
}
pub fn receiver(mut self, id: impl Into<String>) -> RemadvBuilder<S, Set> {
self.inner.receiver_id = Some(id.into());
self.transition()
}
pub fn pruefidentifikator(mut self, pid: impl Into<String>) -> Self {
self.inner.pruefidentifikator = Some(pid.into());
self
}
pub fn rechnungsbezug(mut self, bezug: Rechnungsbezug) -> Self {
self.inner.rechnung = Some(bezug);
self
}
pub fn positionsfehler(mut self, fehler: Vec<Positionsfehler>) -> Self {
self.inner.positionsfehler = fehler;
self
}
pub fn waehrung(mut self, code: impl Into<String>) -> Self {
self.inner.waehrung = code.into();
self
}
pub fn sender_agency(mut self, agency: crate::AgencyCode) -> Self {
self.inner.sender_agency = Some(agency);
self
}
pub fn receiver_agency(mut self, agency: crate::AgencyCode) -> Self {
self.inner.receiver_agency = Some(agency);
self
}
pub fn document_code(mut self, code: impl Into<String>) -> Self {
self.inner.document_code = Some(code.into());
self
}
pub fn document_id(mut self, id: impl Into<String>) -> Self {
self.inner.document_id = Some(id.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 abweichungsgrund(mut self, grund: Abweichungsgrund) -> Self {
self.inner.abweichungsgruende.push(grund);
self
}
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
let dtm_val = self
.inner
.document_date
.as_deref()
.map_or_else(super::now_ccyymmddhhmm, str::to_owned);
let mut buf = Vec::new();
let mut w = Writer::new(&mut buf);
let code = self.inner.document_code.as_deref().unwrap_or("239");
let doc_id = self
.inner
.document_id
.as_deref()
.unwrap_or(&self.inner.message_ref);
emit_comp!(
w,
"UNH",
[&self.inner.message_ref],
["REMADV", "D", "05A", "UN", self.inner.release.as_str()]
);
emit_seg!(w, "BGM", code, doc_id);
emit_comp!(w, "DTM", ["137", &super::ccyymmddhhmm_utc(&dtm_val), "303"]);
if let Some(pid) = &self.inner.pruefidentifikator {
emit_comp!(w, "RFF", ["Z13", pid]);
}
if let Some(id) = &self.inner.sender_id {
emit_comp!(
w,
"NAD",
["MS"],
[id, "", super::agency_for(self.inner.sender_agency, id)]
);
}
if let Some(id) = &self.inner.receiver_id {
emit_comp!(
w,
"NAD",
["MR"],
[id, "", super::agency_for(self.inner.receiver_agency, id)]
);
}
emit_comp!(w, "CUX", ["2", &self.inner.waehrung, "11"]);
if let Some(r) = &self.inner.rechnung {
emit_comp!(w, "DOC", [&r.dokumentenart], [&r.rechnungsnummer]);
emit_comp!(w, "MOA", ["9", &r.faelliger_betrag]);
emit_comp!(w, "MOA", ["12", &r.ueberweisungsbetrag]);
emit_comp!(
w,
"DTM",
["137", &super::ccyymmddhhmm_utc(&r.rechnungsdatum), "303"]
);
}
for grund in &self.inner.abweichungsgruende {
if let Some(ebd) = grund.ebd.as_deref() {
emit_seg!(w, "AJT", &grund.code, ebd);
} else {
emit_seg!(w, "AJT", &grund.code);
}
}
for pf in &self.inner.positionsfehler {
emit_seg!(w, "DLI", "1", &pf.positionsnummer.to_string());
for grund in &pf.gruende {
if let Some(ebd) = grund.ebd.as_deref() {
emit_seg!(w, "AJT", &grund.code, ebd);
} else {
emit_seg!(w, "AJT", &grund.code);
}
}
if let Some(text) = pf.erlaeuterung.as_deref() {
emit_comp!(w, "FTX", ["ABO"], [], [], [text]);
}
}
emit_seg!(w, "UNS", "S");
let summe = self
.inner
.rechnung
.as_ref()
.map_or("0", |r| r.ueberweisungsbetrag.as_str());
emit_comp!(w, "MOA", ["12", summe]);
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 RemadvBuilder<Set, Set> {
pub fn build(self) -> Result<crate::messages::remadv::RemadvMessage, 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()?)?;
Ok(crate::messages::remadv::RemadvMessage::from_parts(
segments,
message_ref.as_str(),
assoc_code.as_str(),
None,
))
}
}