use crate::{AnyMessage, EdiEnergyMessage, EdiEnergyReport, Release};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct InterchangeHeader {
pub sender_id: Box<str>,
pub sender_qualifier: Box<str>,
pub receiver_id: Box<str>,
pub receiver_qualifier: Box<str>,
pub transmission_datetime: Option<time::OffsetDateTime>,
pub control_ref: Box<str>,
pub syntax_id: Box<str>,
pub syntax_version: u8,
pub test_indicator: bool,
}
impl InterchangeHeader {
#[must_use]
pub fn transmission_date(&self) -> Option<time::Date> {
self.transmission_datetime.map(time::OffsetDateTime::date)
}
#[cfg(any(
feature = "utilmd",
feature = "mscons",
feature = "aperak",
feature = "contrl",
feature = "invoic",
feature = "remadv",
feature = "orders",
feature = "iftsta",
feature = "insrpt",
feature = "reqote",
feature = "partin",
feature = "ordchg",
feature = "ordrsp",
feature = "quotes",
feature = "comdis",
feature = "pricat",
feature = "utilts",
))]
#[must_use]
pub(crate) fn from_edifact_envelope(env: edifact_rs::InterchangeEnvelope) -> Self {
use time::{Date, Month, OffsetDateTime, Time, UtcOffset};
let transmission_datetime = (|| -> Option<OffsetDateTime> {
let date_str = &env.date;
let time_str = env.time.as_deref().unwrap_or("");
let d: Date = match date_str.len() {
6 => {
let yy: i32 = date_str[0..2].parse().ok()?;
let mm: u8 = date_str[2..4].parse().ok()?;
let dd: u8 = date_str[4..6].parse().ok()?;
Date::from_calendar_date(2000 + yy, Month::try_from(mm).ok()?, dd).ok()?
}
8 => {
let yyyy: i32 = date_str[0..4].parse().ok()?;
let mm: u8 = date_str[4..6].parse().ok()?;
let dd: u8 = date_str[6..8].parse().ok()?;
Date::from_calendar_date(yyyy, Month::try_from(mm).ok()?, dd).ok()?
}
_ => return None,
};
let t: Time = match time_str.len() {
4 => {
let hh: u8 = time_str[0..2].parse().ok()?;
let mi: u8 = time_str[2..4].parse().ok()?;
Time::from_hms(hh, mi, 0).ok()?
}
6 => {
let hh: u8 = time_str[0..2].parse().ok()?;
let mi: u8 = time_str[2..4].parse().ok()?;
let ss: u8 = time_str[4..6].parse().ok()?;
Time::from_hms(hh, mi, ss).ok()?
}
_ => Time::MIDNIGHT,
};
Some(OffsetDateTime::new_utc(d, t).to_offset(UtcOffset::UTC))
})();
let syntax_version: u8 = env.syntax_version.parse().unwrap_or(3);
Self {
sender_id: env.sender_id.into_boxed_str(),
sender_qualifier: env.sender_qualifier.into_boxed_str(),
receiver_id: env.recipient_id.into_boxed_str(),
receiver_qualifier: env.recipient_qualifier.into_boxed_str(),
transmission_datetime,
control_ref: env.control_ref.into_boxed_str(),
syntax_id: env.syntax_identifier.into_boxed_str(),
syntax_version,
test_indicator: env.test_indicator,
}
}
}
#[derive(Debug, Clone)]
pub struct ReceiptContext<'m> {
pub original_sender: &'m str,
pub original_receiver: &'m str,
pub message_ref: &'m str,
pub release: Release,
pub transmission_date: Option<time::Date>,
}
#[derive(Debug)]
pub struct MessageEnvelope {
pub message: AnyMessage,
pub header: InterchangeHeader,
pub message_index: usize,
}
impl MessageEnvelope {
pub fn validate(&self) -> Result<EdiEnergyReport, crate::Error> {
EdiEnergyMessage::validate(&self.message)
}
#[must_use]
pub fn is_wire_code_acceptable_on(
&self,
date: time::Date,
registry: &crate::registry::ReleaseRegistry,
) -> bool {
let Some(mt) = self.message.try_message_type() else {
return false;
};
let Ok(release) = EdiEnergyMessage::detect_release(&self.message) else {
return false;
};
registry.is_acceptable_on(mt, release, date)
}
#[must_use]
pub fn is_wire_code_acceptable_on_global(&self, date: time::Date) -> bool {
self.is_wire_code_acceptable_on(date, crate::registry::ReleaseRegistry::global())
}
#[must_use]
pub fn sender_party_id(&self) -> Option<&str> {
extract_13digit_party_id(&self.header.sender_id)
}
#[must_use]
pub fn receiver_party_id(&self) -> Option<&str> {
extract_13digit_party_id(&self.header.receiver_id)
}
#[must_use]
pub fn transmission_date(&self) -> Option<time::Date> {
self.header.transmission_date()
}
#[must_use]
pub fn receipt_context(&self) -> ReceiptContext<'_> {
let release = EdiEnergyMessage::detect_release(&self.message)
.cloned()
.unwrap_or_else(|_| Release::new(""));
ReceiptContext {
original_sender: &self.header.sender_id,
original_receiver: &self.header.receiver_id,
message_ref: EdiEnergyMessage::message_ref(&self.message),
release,
transmission_date: self.header.transmission_date(),
}
}
}
#[derive(Debug)]
pub struct ParsedInterchange {
pub header: InterchangeHeader,
pub messages: Vec<MessageEnvelope>,
pub trailer_ref: Box<str>,
pub declared_message_count: usize,
}
impl ParsedInterchange {
#[must_use]
pub fn message_count(&self) -> usize {
self.messages.len()
}
#[must_use]
pub fn count_matches_declared(&self) -> bool {
self.messages.len() == self.declared_message_count
}
#[must_use]
pub fn control_refs_match(&self) -> bool {
self.trailer_ref == self.header.control_ref
}
#[must_use]
pub fn is_structurally_valid(&self) -> bool {
self.count_matches_declared() && self.control_refs_match()
}
}
fn extract_13digit_party_id(id: &str) -> Option<&str> {
if id.len() == 13 && id.bytes().all(|b| b.is_ascii_digit()) {
Some(id)
} else {
None
}
}