use std::marker::PhantomData;
use edifact_rs::Writer;
use crate::AgencyCode;
use crate::{Error, Release};
use super::{Set, Unset, bytes_to_segments};
fn midnight_303(date: &str) -> String {
format!("{date}0000+0000")
}
fn now_303() -> String {
let now = time::OffsetDateTime::now_utc();
let (y, mo, d, h, m) = (
now.year(),
now.month() as u8,
now.day(),
now.hour(),
now.minute(),
);
format!("{y:04}{mo:02}{d:02}{h:02}{m:02}+0000")
}
#[derive(Debug, Clone)]
pub struct UtiltsUsagePeriod {
qualifier: String,
period_id: u32,
usage_from: String,
usage_to: Option<String>,
}
impl UtiltsUsagePeriod {
pub fn new(period_id: u32, usage_from: impl Into<String>) -> Self {
Self {
qualifier: "Z49".to_owned(),
period_id,
usage_from: usage_from.into(),
usage_to: None,
}
}
pub fn qualifier(mut self, q: impl Into<String>) -> Self {
self.qualifier = q.into();
self
}
pub fn usage_to(mut self, datetime: impl Into<String>) -> Self {
self.usage_to = Some(datetime.into());
self
}
}
#[derive(Debug, Clone)]
pub struct UtiltsEnergyAmountRef {
time_period_id: u32,
final_step_id: u32,
}
impl UtiltsEnergyAmountRef {
#[must_use]
pub fn new(time_period_id: u32, final_step_id: u32) -> Self {
Self {
time_period_id,
final_step_id,
}
}
}
#[derive(Debug, Clone)]
pub struct UtiltsCalcStep {
step_id: u32,
time_period_id: Option<u32>,
messlokation_id: Option<String>,
ref_calc_step_id: Option<u32>,
operator: Option<String>,
energy_direction: Option<String>,
loss_factor_trafo: Option<String>,
loss_factor_line: Option<String>,
split_factor: Option<String>,
}
impl UtiltsCalcStep {
#[must_use]
pub fn new(step_id: u32) -> Self {
Self {
step_id,
time_period_id: None,
messlokation_id: None,
ref_calc_step_id: None,
operator: None,
energy_direction: None,
loss_factor_trafo: None,
loss_factor_line: None,
split_factor: None,
}
}
#[must_use]
pub fn time_period(mut self, id: u32) -> Self {
self.time_period_id = Some(id);
self
}
pub fn messlokation(mut self, id: impl Into<String>) -> Self {
self.messlokation_id = Some(id.into());
self
}
#[must_use]
pub fn ref_calc_step(mut self, id: u32) -> Self {
self.ref_calc_step_id = Some(id);
self
}
pub fn operator(mut self, code: impl Into<String>) -> Self {
self.operator = Some(code.into());
self
}
pub fn energy_direction(mut self, code: impl Into<String>) -> Self {
self.energy_direction = Some(code.into());
self
}
pub fn loss_factor_trafo(mut self, value: impl Into<String>) -> Self {
self.loss_factor_trafo = Some(value.into());
self
}
pub fn loss_factor_line(mut self, value: impl Into<String>) -> Self {
self.loss_factor_line = Some(value.into());
self
}
pub fn split_factor(mut self, value: impl Into<String>) -> Self {
self.split_factor = Some(value.into());
self
}
}
#[derive(Debug, Clone)]
pub struct UtiltsDefinitionBlock {
seq_qualifier: String,
change_time: Option<String>,
register_code: Option<String>,
ref_definition_code: Option<String>,
definition_code: Option<String>,
frequency: Option<String>,
transmissibility: Option<String>,
peak_load_detection: Option<String>,
orderable: Option<String>,
switching_action: Option<String>,
}
impl UtiltsDefinitionBlock {
pub fn new(seq_qualifier: impl Into<String>) -> Self {
Self {
seq_qualifier: seq_qualifier.into(),
change_time: None,
register_code: None,
ref_definition_code: None,
definition_code: None,
frequency: None,
transmissibility: None,
peak_load_detection: None,
orderable: None,
switching_action: None,
}
}
pub fn change_time(mut self, datetime: impl Into<String>) -> Self {
self.change_time = Some(datetime.into());
self
}
pub fn register_code(mut self, code: impl Into<String>) -> Self {
self.register_code = Some(code.into());
self
}
pub fn ref_definition_code(mut self, code: impl Into<String>) -> Self {
self.ref_definition_code = Some(code.into());
self
}
pub fn definition_code(mut self, code: impl Into<String>) -> Self {
self.definition_code = Some(code.into());
self
}
pub fn frequency(mut self, code: impl Into<String>) -> Self {
self.frequency = Some(code.into());
self
}
pub fn transmissibility(mut self, code: impl Into<String>) -> Self {
self.transmissibility = Some(code.into());
self
}
pub fn peak_load_detection(mut self, code: impl Into<String>) -> Self {
self.peak_load_detection = Some(code.into());
self
}
pub fn orderable(mut self, code: impl Into<String>) -> Self {
self.orderable = Some(code.into());
self
}
pub fn switching_action(mut self, code: impl Into<String>) -> Self {
self.switching_action = Some(code.into());
self
}
}
#[derive(Debug, Clone)]
pub struct UtiltsVorgang {
pub transaction_id: String,
pub pruefidentifikator: u32,
location_id: Option<String>,
definition_code: Option<String>,
valid_from: Option<String>,
formula_status_code: Option<String>,
formula_status_period: Option<u32>,
ref_transaction_id: Option<String>,
usage_periods: Vec<UtiltsUsagePeriod>,
energy_amount_refs: Vec<UtiltsEnergyAmountRef>,
calc_steps: Vec<UtiltsCalcStep>,
definition_blocks: Vec<UtiltsDefinitionBlock>,
}
impl UtiltsVorgang {
pub fn new(transaction_id: impl Into<String>, pruefidentifikator: u32) -> Self {
Self {
transaction_id: transaction_id.into(),
pruefidentifikator,
location_id: None,
definition_code: None,
valid_from: None,
formula_status_code: None,
formula_status_period: None,
ref_transaction_id: None,
usage_periods: Vec::new(),
energy_amount_refs: Vec::new(),
calc_steps: Vec::new(),
definition_blocks: Vec::new(),
}
}
pub fn location(mut self, malo_id: impl Into<String>) -> Self {
self.location_id = Some(malo_id.into());
self
}
pub fn definition_code(mut self, code: impl Into<String>) -> Self {
self.definition_code = Some(code.into());
self
}
pub fn valid_from(mut self, datetime: impl Into<String>) -> Self {
self.valid_from = Some(datetime.into());
self
}
pub fn formula_status(mut self, code: impl Into<String>, period_id: u32) -> Self {
self.formula_status_code = Some(code.into());
self.formula_status_period = Some(period_id);
self
}
pub fn ref_transaction(mut self, id: impl Into<String>) -> Self {
self.ref_transaction_id = Some(id.into());
self
}
#[must_use]
pub fn add_usage_period(mut self, period: UtiltsUsagePeriod) -> Self {
self.usage_periods.push(period);
self
}
#[must_use]
pub fn add_energy_ref(mut self, r: UtiltsEnergyAmountRef) -> Self {
self.energy_amount_refs.push(r);
self
}
#[must_use]
pub fn add_calc_step(mut self, step: UtiltsCalcStep) -> Self {
self.calc_steps.push(step);
self
}
#[must_use]
pub fn add_definition_block(mut self, block: UtiltsDefinitionBlock) -> Self {
self.definition_blocks.push(block);
self
}
}
#[derive(Debug, Clone)]
struct UtiltsBuilderInner {
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>,
vorgaenge: Vec<UtiltsVorgang>,
}
#[derive(Debug, Clone)]
#[must_use = "Builder must be consumed via .build() or .serialize()"]
pub struct UtiltsBuilder<S = Unset, R = Unset> {
_ph: PhantomData<fn() -> (S, R)>,
inner: UtiltsBuilderInner,
}
impl UtiltsBuilder<Unset, Unset> {
pub fn new(release: Release) -> Self {
Self {
_ph: PhantomData,
inner: UtiltsBuilderInner {
release,
sender_id: None,
receiver_id: None,
sender_agency: AgencyCode::Bdew,
receiver_agency: AgencyCode::Bdew,
message_ref: "1".to_owned(),
document_code: "Z36".to_owned(),
document_id: None,
document_date: None,
vorgaenge: Vec::new(),
},
}
}
}
impl<S, R> UtiltsBuilder<S, R> {
fn transition<S2, R2>(self) -> UtiltsBuilder<S2, R2> {
UtiltsBuilder {
_ph: PhantomData,
inner: self.inner,
}
}
pub fn sender(mut self, id: impl Into<String>) -> UtiltsBuilder<Set, R> {
self.inner.sender_id = Some(id.into());
self.transition()
}
pub fn receiver(mut self, id: impl Into<String>) -> UtiltsBuilder<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 document_datetime(mut self, datetime: impl Into<String>) -> Self {
self.inner.document_date = Some(format!("303:{}", datetime.into()));
self
}
pub fn add_vorgang(mut self, vorgang: UtiltsVorgang) -> Self {
self.inner.vorgaenge.push(vorgang);
self
}
#[allow(clippy::too_many_lines)]
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
let dtm_val = match self.inner.document_date.as_deref() {
Some(d) if d.starts_with("303:") => d["303:".len()..].to_owned(),
Some(d) => midnight_303(d),
None => now_303(),
};
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],
["UTILTS", "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, "303"]);
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()]
);
}
for vorgang in &self.inner.vorgaenge {
emit_seg!(w, "IDE", "24", &vorgang.transaction_id);
if let Some(malo) = &vorgang.location_id {
emit_seg!(w, "LOC", "172", malo);
}
if let Some(code) = &vorgang.definition_code {
emit_seg!(w, "LOC", "Z09", code);
}
if let Some(vf) = &vorgang.valid_from {
emit_comp!(w, "DTM", ["157", vf, "303"]);
}
if let (Some(code), Some(pid)) =
(&vorgang.formula_status_code, &vorgang.formula_status_period)
{
emit_seg!(w, "STS", "Z23", code, &pid.to_string());
}
emit_comp!(w, "RFF", ["Z13", &vorgang.pruefidentifikator.to_string()]);
if let Some(ref_id) = &vorgang.ref_transaction_id {
emit_comp!(w, "RFF", ["TN", ref_id]);
}
for period in &vorgang.usage_periods {
emit_comp!(
w,
"RFF",
[&period.qualifier, "", &period.period_id.to_string()]
);
emit_comp!(w, "DTM", ["Z25", &period.usage_from, "303"]);
if let Some(to) = &period.usage_to {
emit_comp!(w, "DTM", ["Z26", to, "303"]);
}
}
for er in &vorgang.energy_amount_refs {
emit_seg!(w, "SEQ", "Z36");
emit_comp!(w, "RFF", ["Z46", &er.time_period_id.to_string()]);
emit_comp!(w, "RFF", ["Z23", &er.final_step_id.to_string()]);
}
for step in &vorgang.calc_steps {
emit_seg!(w, "SEQ", "Z37", &step.step_id.to_string());
if let Some(tp) = step.time_period_id {
emit_comp!(w, "RFF", ["Z46", &tp.to_string()]);
}
if let Some(malo) = &step.messlokation_id {
emit_comp!(w, "RFF", ["Z19", malo]);
}
if let Some(rs) = step.ref_calc_step_id {
emit_comp!(w, "RFF", ["Z23", &rs.to_string()]);
}
if let Some(op) = &step.operator {
emit_seg!(w, "CCI", "", "", "Z86");
emit_seg!(w, "CAV", op);
}
if let Some(dir) = &step.energy_direction {
emit_seg!(w, "CCI", "", "", "Z87");
emit_seg!(w, "CAV", dir);
}
if let Some(vt) = &step.loss_factor_trafo {
emit_seg!(w, "CCI", "", "", "Z16");
emit_comp!(w, "CAV", ["Z28", "", "", vt]);
}
if let Some(vl) = &step.loss_factor_line {
emit_seg!(w, "CCI", "", "", "ZB2");
emit_comp!(w, "CAV", ["Z28", "", "", vl]);
}
if let Some(af) = &step.split_factor {
emit_seg!(w, "CCI", "", "", "ZG6");
emit_comp!(w, "CAV", ["ZH6", "", "", af]);
}
}
for block in &vorgang.definition_blocks {
emit_seg!(w, "SEQ", &block.seq_qualifier);
if let Some(ct) = &block.change_time {
let dtm_q = match block.seq_qualifier.as_str() {
"Z69" => "Z44",
"Z70" | "Z74" => "Z45",
_ => "Z33",
};
emit_comp!(w, "DTM", [dtm_q, ct, "303"]);
}
if let Some(rc) = &block.register_code {
emit_comp!(w, "RFF", ["Z28", rc]);
}
if let Some(rc) = &block.ref_definition_code {
emit_comp!(w, "RFF", ["Z28", rc]);
}
if let Some(def_code) = &block.definition_code {
let cci_type = match block.seq_qualifier.as_str() {
"Z69" => "Z52",
"Z70" | "Z74" => "Z53",
_ => "Z39",
};
emit_seg!(w, "CCI", cci_type, "", def_code);
if let Some(freq) = &block.frequency {
emit_comp!(w, "CAV", ["ZE0", "", "", freq]);
}
if let Some(tr) = &block.transmissibility {
emit_comp!(w, "CAV", ["ZD5", "", "", tr]);
}
if let Some(pl) = &block.peak_load_detection {
emit_comp!(w, "CAV", ["ZD4", "", "", pl]);
}
if let Some(ord) = &block.orderable {
emit_comp!(w, "CAV", ["ZD7", "", "", ord]);
}
}
if let Some(sa) = &block.switching_action {
emit_seg!(w, "CCI", "Z58", "", sa);
}
}
}
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 UtiltsBuilder<Set, Set> {
pub fn build(self) -> Result<crate::messages::utilts::UtiltsMessage, Error> {
let pid = self.inner.vorgaenge.first().map(|v| v.pruefidentifikator);
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::utilts::UtiltsMessage::from_parts(
segments,
message_ref.as_str(),
assoc_code.as_str(),
pid,
))
}
}