#![allow(clippy::doc_markdown)]
use serde::{Deserialize, Serialize};
use time::Date;
use uuid::Uuid;
use crate::{
domain::{MaloId, MarktpartnerId, MeloId, ProcessStatus, Sparte},
error::MdmError,
};
fn unix_epoch() -> time::OffsetDateTime {
time::OffsetDateTime::UNIX_EPOCH
}
mod date_iso {
use serde::{Deserialize, Deserializer, Serializer};
use time::Date;
use time::macros::format_description;
#[expect(clippy::trivially_copy_pass_by_ref)]
pub fn serialize<S: Serializer>(date: &Date, s: S) -> Result<S::Ok, S::Error> {
let fmt = format_description!("[year]-[month]-[day]");
s.serialize_str(&date.format(fmt).map_err(serde::ser::Error::custom)?)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Date, D::Error> {
let raw = String::deserialize(d)?;
let fmt = format_description!("[year]-[month]-[day]");
Date::parse(&raw, fmt).map_err(serde::de::Error::custom)
}
pub mod opt {
use serde::{Deserialize, Deserializer, Serializer};
use time::Date;
use time::macros::format_description;
#[expect(clippy::trivially_copy_pass_by_ref, clippy::ref_option)]
pub fn serialize<S: Serializer>(date: &Option<Date>, s: S) -> Result<S::Ok, S::Error> {
match date {
Some(d) => {
let fmt = format_description!("[year]-[month]-[day]");
s.serialize_some(&d.format(fmt).map_err(serde::ser::Error::custom)?)
}
None => s.serialize_none(),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Date>, D::Error> {
let raw: Option<String> = Option::deserialize(d)?;
match raw {
Some(s) => {
let fmt = format_description!("[year]-[month]-[day]");
Date::parse(&s, fmt)
.map(Some)
.map_err(serde::de::Error::custom)
}
None => Ok(None),
}
}
}
}
pub type MaloPayload = serde_json::Value;
pub type MeloPayload = serde_json::Value;
pub type ContractPayload = serde_json::Value;
fn default_bo4e_version() -> String {
"v202607.0.0".to_owned()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lokationszuordnung {
pub zuordnungstyp: String,
pub rollencodenummer: String,
#[serde(with = "date_iso")]
pub valid_from: Date,
#[serde(default, with = "date_iso::opt")]
pub valid_to: Option<Date>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaloRecord {
pub malo_id: MaloId,
pub sparte: Sparte,
pub netzebene: Option<String>,
pub bilanzierungsgebiet: Option<String>,
pub gasqualitaet: Option<String>,
pub energierichtung: Option<String>,
pub bilanzierungsmethode: Option<String>,
pub regelzone: Option<String>,
pub fallgruppe: Option<String>,
pub version: i64,
pub data: MaloPayload,
pub lokationszuordnung: Vec<Lokationszuordnung>,
pub updated_at: time::OffsetDateTime,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeloRecord {
pub melo_id: MeloId,
pub malo_id: Option<MaloId>,
pub netzebene_messung: Option<String>,
pub regelzone: Option<String>,
pub standorteigenschaften: Option<serde_json::Value>,
pub version: i64,
pub data: MeloPayload,
pub updated_at: time::OffsetDateTime,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContractRecord {
pub contract_id: String,
pub malo_id: Option<MaloId>,
pub sparte: Sparte,
pub vertragsart: String,
pub version: i64,
pub data: ContractPayload,
#[serde(default, with = "date_iso::opt")]
pub valid_from: Option<Date>,
#[serde(default, with = "date_iso::opt")]
pub valid_to: Option<Date>,
pub created_at: time::OffsetDateTime,
pub updated_at: time::OffsetDateTime,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subscription {
pub subscriber_id: String,
pub webhook_url: String,
pub webhook_secret: Option<String>,
pub roles: Vec<String>,
pub event_types: Vec<String>,
pub sparten: Vec<String>,
pub active: bool,
pub version: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PartnerRecord {
pub mp_id: MarktpartnerId,
pub display_name: Option<String>,
pub marktrolle: Option<String>,
pub sparte: Option<Sparte>,
pub rollencodetyp: Option<String>,
pub makoadresse: Vec<String>,
pub channels: serde_json::Value,
#[serde(default)]
pub version: i64,
#[serde(default = "unix_epoch", with = "time::serde::rfc3339")]
pub updated_at: time::OffsetDateTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorrelationEntry {
pub process_id: Uuid,
pub workflow_name: Option<String>,
pub pid: Option<i32>,
pub malo_id: Option<MaloId>,
pub melo_id: Option<MeloId>,
pub contract_id: Option<String>,
pub erp_contract_id: Option<String>,
pub erp_order_id: Option<String>,
pub edifact_conv_id: Option<Uuid>,
pub marktrolle: Option<String>,
pub format_version: Option<String>,
pub status: ProcessStatus,
pub initiated_at: time::OffsetDateTime,
pub completed_at: Option<time::OffsetDateTime>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageResult<T> {
pub items: Vec<T>,
pub total: u64,
pub page: u32,
pub size: u32,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct MaloFilter {
pub sparte: Option<Sparte>,
pub zuordnungstyp: Option<String>,
pub rollencodenummer: Option<String>,
pub fallgruppe: Option<String>,
pub bilanzierungsmethode: Option<String>,
pub regelzone: Option<String>,
pub page: u32,
pub size: u32,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct CorrelationFilter {
pub erp_order_id: Option<String>,
pub malo_id: Option<MaloId>,
pub status: Option<ProcessStatus>,
}
#[allow(async_fn_in_trait)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct MaloTypedFields {
pub malo_id: String,
pub netzebene: Option<String>,
pub bilanzierungsgebiet: Option<String>,
pub gasqualitaet: Option<String>,
pub energierichtung: Option<String>,
pub bilanzierungsmethode: Option<String>,
pub fallgruppe: Option<String>,
pub regelzone: Option<String>,
}
#[allow(async_fn_in_trait)]
pub trait MaloRepository: Send + Sync {
async fn upsert(
&self,
malo_id: &MaloId,
sparte: Sparte,
data: MaloPayload,
lokationszuordnung: Vec<Lokationszuordnung>,
if_match: Option<i64>,
bo4e_version: &str,
) -> Result<i64, MdmError>;
async fn patch_typenmerkmal(
&self,
malo_id: &MaloId,
bilanzierungsmethode: Option<&str>,
fallgruppe: Option<&str>,
) -> Result<(), MdmError>;
async fn find(&self, malo_id: &MaloId, at: Date) -> Result<Option<MaloRecord>, MdmError>;
async fn list(&self, filter: MaloFilter, at: Date) -> Result<PageResult<MaloRecord>, MdmError>;
}
#[allow(async_fn_in_trait)]
pub trait MeloRepository: Send + Sync {
async fn upsert(
&self,
melo_id: &MeloId,
malo_id: Option<&MaloId>,
data: MeloPayload,
if_match: Option<i64>,
bo4e_version: &str,
) -> Result<i64, MdmError>;
async fn find(&self, melo_id: &MeloId) -> Result<Option<MeloRecord>, MdmError>;
}
#[allow(async_fn_in_trait)]
pub trait ContractRepository: Send + Sync {
#[allow(clippy::too_many_arguments)]
async fn upsert(
&self,
contract_id: &str,
malo_id: Option<&MaloId>,
sparte: Sparte,
vertragsart: &str,
data: ContractPayload,
valid_from: Option<Date>,
valid_to: Option<Date>,
if_match: Option<i64>,
bo4e_version: &str,
) -> Result<i64, MdmError>;
async fn find(&self, contract_id: &str) -> Result<Option<ContractRecord>, MdmError>;
async fn find_active_by_malo(
&self,
malo_id: &MaloId,
at: Date,
) -> Result<Vec<ContractRecord>, MdmError>;
}
#[allow(async_fn_in_trait)]
pub trait SubscriptionRepository: Send + Sync {
async fn upsert(&self, sub: Subscription) -> Result<i64, MdmError>;
async fn find(&self, subscriber_id: &str) -> Result<Option<Subscription>, MdmError>;
async fn list_active(&self) -> Result<Vec<Subscription>, MdmError>;
async fn list_matching(
&self,
event_type: &str,
role: &str,
sparte: Option<&str>,
) -> Result<Vec<Subscription>, MdmError>;
}
#[allow(async_fn_in_trait)]
pub trait CorrelationIndex: Send + Sync {
async fn insert(&self, entry: CorrelationEntry) -> Result<(), MdmError>;
async fn update_status(
&self,
process_id: Uuid,
status: ProcessStatus,
completed_at: Option<time::OffsetDateTime>,
) -> Result<(), MdmError>;
async fn update_edifact_conv_id(&self, process_id: Uuid, conv_id: Uuid)
-> Result<(), MdmError>;
async fn find_by_erp_order_id(
&self,
erp_order_id: &str,
) -> Result<Option<CorrelationEntry>, MdmError>;
async fn find_by_process_id(
&self,
process_id: Uuid,
) -> Result<Option<CorrelationEntry>, MdmError>;
async fn list(&self, filter: CorrelationFilter) -> Result<Vec<CorrelationEntry>, MdmError>;
}
#[allow(async_fn_in_trait)]
pub trait PartnerRepository: Send + Sync {
async fn upsert(&self, partner: PartnerRecord) -> Result<i64, MdmError>;
async fn find(&self, id: &MarktpartnerId) -> Result<Option<PartnerRecord>, MdmError>;
async fn list(&self) -> Result<Vec<PartnerRecord>, MdmError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PreisblattSource {
Api,
Mako,
}
impl std::fmt::Display for PreisblattSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PreisblattSource::Api => f.write_str("api"),
PreisblattSource::Mako => f.write_str("mako"),
}
}
}
impl std::str::FromStr for PreisblattSource {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"api" => Ok(PreisblattSource::Api),
"mako" => Ok(PreisblattSource::Mako),
other => Err(format!("unknown PreisblattSource: {other:?}")),
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PreisblattRecord {
pub nb_mp_id: String,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub source: PreisblattSource,
pub created_at: time::OffsetDateTime,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait PreisblattRepository: Send + Sync {
async fn upsert(
&self,
nb_mp_id: &str,
data: serde_json::Value,
bo4e_version: &str,
source: PreisblattSource,
) -> Result<(), MdmError>;
async fn find_for_date(
&self,
nb_mp_id: &str,
billing_date: &str,
) -> Result<Option<PreisblattRecord>, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PreisblattMessungRecord {
pub msb_mp_id: String,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub source: PreisblattSource,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub auf_abschlaege: Vec<serde_json::Value>,
pub created_at: time::OffsetDateTime,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait PreisblattMessungRepository: Send + Sync {
async fn upsert_messung(
&self,
msb_mp_id: &str,
data: serde_json::Value,
bo4e_version: &str,
source: PreisblattSource,
) -> Result<(), MdmError>;
async fn find_messung_for_date(
&self,
msb_mp_id: &str,
billing_date: &str,
) -> Result<Option<PreisblattMessungRecord>, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PreisblattKaRecord {
pub nb_mp_id: String,
pub sparte: String,
pub kundengruppe_ka: Option<String>,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub source: PreisblattSource,
pub created_at: time::OffsetDateTime,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait PreisblattKaRepository: Send + Sync {
async fn upsert_ka(
&self,
nb_mp_id: &str,
sparte: &str,
kundengruppe_ka: Option<&str>,
data: serde_json::Value,
bo4e_version: &str,
source: PreisblattSource,
) -> Result<(), MdmError>;
async fn find_ka_for_date(
&self,
nb_mp_id: &str,
sparte: &str,
kundengruppe_ka: Option<&str>,
billing_date: &str,
) -> Result<Option<PreisblattKaRecord>, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PreisblattDienstleistungRecord {
pub msb_mp_id: String,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub source: PreisblattSource,
pub created_at: time::OffsetDateTime,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait PreisblattDienstleistungRepository: Send + Sync {
async fn upsert_dienstleistung(
&self,
msb_mp_id: &str,
data: serde_json::Value,
bo4e_version: &str,
source: PreisblattSource,
) -> Result<(), MdmError>;
async fn find_dienstleistung_for_date(
&self,
msb_mp_id: &str,
billing_date: &str,
) -> Result<Option<PreisblattDienstleistungRecord>, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PreisblattHardwareRecord {
pub msb_mp_id: String,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub source: PreisblattSource,
pub created_at: time::OffsetDateTime,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait PreisblattHardwareRepository: Send + Sync {
async fn upsert_hardware(
&self,
msb_mp_id: &str,
data: serde_json::Value,
bo4e_version: &str,
source: PreisblattSource,
) -> Result<(), MdmError>;
async fn find_hardware_for_date(
&self,
msb_mp_id: &str,
billing_date: &str,
) -> Result<Option<PreisblattHardwareRecord>, MdmError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PriCatDispatchState {
Pending,
Queued,
Done,
Error,
}
impl std::fmt::Display for PriCatDispatchState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pending => write!(f, "pending"),
Self::Queued => write!(f, "queued"),
Self::Done => write!(f, "done"),
Self::Error => write!(f, "error"),
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PriCatVersion {
pub id: uuid::Uuid,
pub nb_mp_id: String,
pub tenant: String,
pub valid_from: time::Date,
pub valid_to: Option<time::Date>,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub source: PreisblattSource,
pub dispatch_state: PriCatDispatchState,
pub dispatch_error: Option<String>,
pub created_at: time::OffsetDateTime,
pub updated_at: time::OffsetDateTime,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PriCatDispatchEntry {
pub id: uuid::Uuid,
pub pricat_version_id: uuid::Uuid,
pub nb_mp_id: String,
pub lf_mp_id: String,
pub tenant: String,
pub process_id: Option<uuid::Uuid>,
pub dispatched_at: time::OffsetDateTime,
pub outcome: String,
pub error_detail: Option<String>,
}
#[allow(async_fn_in_trait)]
pub trait PriCatRepository: Send + Sync {
#[allow(clippy::too_many_arguments)]
async fn upsert_version(
&self,
nb_mp_id: &str,
tenant: &str,
valid_from: time::Date,
valid_to: Option<time::Date>,
data: serde_json::Value,
bo4e_version: &str,
source: PreisblattSource,
) -> Result<uuid::Uuid, MdmError>;
async fn list_versions(
&self,
nb_mp_id: &str,
tenant: &str,
) -> Result<Vec<PriCatVersion>, MdmError>;
async fn find_latest(
&self,
nb_mp_id: &str,
tenant: &str,
) -> Result<Option<PriCatVersion>, MdmError>;
async fn list_pending(&self, tenant: &str) -> Result<Vec<PriCatVersion>, MdmError>;
async fn mark_queued(&self, id: uuid::Uuid) -> Result<(), MdmError>;
async fn mark_done(&self, id: uuid::Uuid) -> Result<(), MdmError>;
async fn mark_error(&self, id: uuid::Uuid, error: &str) -> Result<(), MdmError>;
async fn log_dispatch(&self, entry: PriCatDispatchEntry) -> Result<(), MdmError>;
async fn dispatch_log(
&self,
pricat_version_id: uuid::Uuid,
) -> Result<Vec<PriCatDispatchEntry>, MdmError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BillingSchedule {
#[default]
Monthly,
Quarterly,
Annually,
}
impl std::fmt::Display for BillingSchedule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Monthly => write!(f, "MONTHLY"),
Self::Quarterly => write!(f, "QUARTERLY"),
Self::Annually => write!(f, "ANNUALLY"),
}
}
}
impl std::str::FromStr for BillingSchedule {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_uppercase().as_str() {
"MONTHLY" => Ok(Self::Monthly),
"QUARTERLY" => Ok(Self::Quarterly),
"ANNUALLY" => Ok(Self::Annually),
other => Err(format!("unknown BillingSchedule '{other}'")),
}
}
}
impl BillingSchedule {
#[must_use]
pub fn from_str_or_default(s: &str) -> Self {
s.parse().unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NbContractRecord {
pub contract_id: String,
pub malo_id: crate::domain::MaloId,
pub nb_mp_id: String,
pub sparte: crate::domain::Sparte,
pub netzebene: String,
pub bilanzierungsmethode: String,
pub billing_schedule: BillingSchedule,
#[serde(with = "date_iso")]
pub valid_from: time::Date,
#[serde(with = "date_iso::opt")]
pub valid_to: Option<time::Date>,
#[serde(default)]
pub data: serde_json::Value,
#[serde(default)]
pub vertragsart: Option<String>,
#[serde(default)]
pub vertragsstatus: Option<String>,
pub tenant: String,
pub version: i64,
}
#[allow(async_fn_in_trait)]
pub trait NbContractRepository: Send + Sync {
#[must_use]
async fn upsert(&self, rec: NbContractRecord) -> Result<i64, MdmError>;
#[must_use]
async fn find(&self, contract_id: &str) -> Result<Option<NbContractRecord>, MdmError>;
#[must_use]
async fn find_active(
&self,
malo_id: &str,
date: time::Date,
tenant: &str,
) -> Result<Option<NbContractRecord>, MdmError>;
#[must_use]
async fn list_by_nb(
&self,
nb_mp_id: &str,
tenant: &str,
) -> Result<Vec<NbContractRecord>, MdmError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum LieferStatus {
Beliefert,
Unbeliefert,
Grundversorgung,
Ersatzversorgung,
Ruhend,
Stillgelegt,
}
impl std::fmt::Display for LieferStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Beliefert => write!(f, "Beliefert"),
Self::Unbeliefert => write!(f, "Unbeliefert"),
Self::Grundversorgung => write!(f, "Grundversorgung"),
Self::Ersatzversorgung => write!(f, "Ersatzversorgung"),
Self::Ruhend => write!(f, "Ruhend"),
Self::Stillgelegt => write!(f, "Stillgelegt"),
}
}
}
impl std::str::FromStr for LieferStatus {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Beliefert" => Ok(Self::Beliefert),
"Unbeliefert" => Ok(Self::Unbeliefert),
"Grundversorgung" => Ok(Self::Grundversorgung),
"Ersatzversorgung" => Ok(Self::Ersatzversorgung),
"Ruhend" => Ok(Self::Ruhend),
"Stillgelegt" => Ok(Self::Stillgelegt),
other => Err(format!("unknown LieferStatus '{other}'")),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersorgungsStatusRecord {
pub malo_id: MaloId,
pub lieferstatus: LieferStatus,
pub lf_mp_id: Option<String>,
pub lf_mp_id_next: Option<String>,
#[serde(default, with = "date_iso::opt")]
pub lf_next_lieferbeginn: Option<Date>,
#[serde(default, with = "date_iso::opt")]
pub lieferbeginn: Option<Date>,
#[serde(default, with = "date_iso::opt")]
pub lieferende: Option<Date>,
pub msb_mp_id: Option<String>,
pub nb_mp_id: String,
pub last_process_id: Option<Uuid>,
pub updated_at: time::OffsetDateTime,
pub tenant: String,
pub version: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersorgungsStatusHistoryRecord {
pub id: i64,
pub malo_id: MaloId,
pub tenant: String,
pub lieferstatus: LieferStatus,
pub lf_mp_id: Option<String>,
pub lf_mp_id_next: Option<String>,
#[serde(default, with = "date_iso::opt")]
pub lf_next_lieferbeginn: Option<Date>,
#[serde(default, with = "date_iso::opt")]
pub lieferbeginn: Option<Date>,
#[serde(default, with = "date_iso::opt")]
pub lieferende: Option<Date>,
pub msb_mp_id: Option<String>,
pub nb_mp_id: String,
pub last_process_id: Option<Uuid>,
pub version: i64,
pub valid_from: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait VersorgungsStatusRepository: Send + Sync {
#[must_use]
async fn upsert(
&self,
rec: VersorgungsStatusRecord,
if_version: Option<i64>,
) -> Result<i64, MdmError>;
#[must_use]
async fn find(
&self,
malo_id: &MaloId,
tenant: &str,
) -> Result<Option<VersorgungsStatusRecord>, MdmError>;
#[must_use]
async fn find_at(
&self,
malo_id: &MaloId,
tenant: &str,
at: Date,
) -> Result<Option<VersorgungsStatusRecord>, MdmError>;
#[must_use]
async fn find_history(
&self,
malo_id: &MaloId,
tenant: &str,
page: u32,
size: u32,
) -> Result<PageResult<VersorgungsStatusHistoryRecord>, MdmError>;
#[must_use]
async fn list_by_tenant(
&self,
tenant: &str,
page: u32,
size: u32,
) -> Result<PageResult<VersorgungsStatusRecord>, MdmError>;
#[must_use]
async fn announce_lf_next(
&self,
malo_id: &MaloId,
tenant: &str,
lf_mp_id_next: &str,
lf_next_lieferbeginn: Option<Date>,
nb_mp_id: &str,
process_id: Option<Uuid>,
) -> Result<(), MdmError>;
#[must_use]
async fn confirm_supply(
&self,
malo_id: &MaloId,
tenant: &str,
process_id: Option<Uuid>,
) -> Result<(), MdmError>;
#[must_use]
async fn end_supply(
&self,
malo_id: &MaloId,
tenant: &str,
nb_mp_id: &str,
process_id: Option<Uuid>,
) -> Result<(), MdmError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NeLoRecord {
pub nelo_id: String,
pub tenant: String,
pub name: Option<String>,
pub sparte: Sparte,
pub netzebene: Option<String>,
pub nb_mp_id: String,
pub steuerkanal: Option<bool>,
pub eigenschaft_msb_lokation: Option<String>,
pub grundzustaendiger_msb_codenr: Option<String>,
pub data: serde_json::Value,
pub version: i64,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait NeLoRepository: Send + Sync {
#[must_use]
async fn upsert(&self, rec: NeLoRecord, if_match: Option<i64>) -> Result<i64, MdmError>;
#[must_use]
async fn find(&self, nelo_id: &str, tenant: &str) -> Result<Option<NeLoRecord>, MdmError>;
#[must_use]
async fn list_by_nb(
&self,
nb_mp_id: &str,
tenant: &str,
page: u32,
size: u32,
) -> Result<PageResult<NeLoRecord>, MdmError>;
#[must_use]
async fn list_by_tenant(
&self,
tenant: &str,
page: u32,
size: u32,
) -> Result<PageResult<NeLoRecord>, MdmError>;
}
#[derive(Clone)]
pub struct AppState<Ma, Me, Co, Su, Ci, Pa>
where
Ma: MaloRepository + Clone,
Me: MeloRepository + Clone,
Co: ContractRepository + Clone,
Su: SubscriptionRepository + Clone,
Ci: CorrelationIndex + Clone,
Pa: PartnerRepository + Clone,
{
pub malo_repo: Ma,
pub melo_repo: Me,
pub contract_repo: Co,
pub subscription_repo: Su,
pub correlation_index: Ci,
pub partner_repo: Pa,
#[cfg(feature = "makod-client")]
pub makod_client: std::sync::Arc<crate::makod_client::MakodClient>,
pub event_tx: tokio::sync::mpsc::UnboundedSender<serde_json::Value>,
pub tenant_gln: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaloGridRecord {
pub malo_id: MaloId,
pub nb_mp_id: String,
pub bilanzierungsgebiet: Option<String>,
pub netzgebiet: Option<String>,
pub sparte: Sparte,
pub source: String,
#[serde(with = "time::serde::rfc3339")]
pub updated_at: time::OffsetDateTime,
#[serde(default)]
pub tenant: String,
}
#[allow(async_fn_in_trait)]
pub trait MaloGridRepository: Send + Sync {
#[must_use]
async fn upsert(&self, rec: MaloGridRecord) -> Result<(), MdmError>;
#[must_use]
async fn find(
&self,
malo_id: &MaloId,
tenant: &str,
) -> Result<Option<MaloGridRecord>, MdmError>;
#[must_use]
async fn list_by_nb(
&self,
nb_mp_id: &str,
tenant: &str,
) -> Result<Vec<MaloGridRecord>, MdmError>;
#[must_use]
async fn delete(&self, malo_id: &MaloId, tenant: &str) -> Result<(), MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SteuerbareRessourceRecord {
pub sr_id: String,
pub tenant: String,
pub malo_id: Option<String>,
pub melo_id: Option<String>,
pub data: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub konfigurationsprodukte: Option<serde_json::Value>,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub version: i64,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait SteuerbareRessourceRepository: Send + Sync {
#[allow(clippy::too_many_arguments)]
async fn upsert_sr(
&self,
sr_id: &str,
tenant: &str,
malo_id: Option<&str>,
melo_id: Option<&str>,
data: serde_json::Value,
bo4e_version: &str,
konfigurationsprodukte: Option<serde_json::Value>,
) -> Result<(), MdmError>;
async fn find_sr(
&self,
sr_id: &str,
tenant: &str,
) -> Result<Option<SteuerbareRessourceRecord>, MdmError>;
async fn list_sr_by_malo(
&self,
malo_id: &str,
tenant: &str,
) -> Result<Vec<SteuerbareRessourceRecord>, MdmError>;
async fn replace_sr_konfigurationsprodukte(
&self,
sr_id: &str,
tenant: &str,
konfigurationsprodukte: serde_json::Value,
) -> Result<bool, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TechnischeRessourceRecord {
pub tr_id: String,
pub tenant: String,
pub malo_id: Option<String>,
pub melo_id: Option<String>,
pub tr_typ: Option<String>,
pub ist_fernschaltbar: Option<bool>,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub version: i64,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait TechnischeRessourceRepository: Send + Sync {
#[allow(clippy::too_many_arguments)]
async fn upsert_tr(
&self,
tr_id: &str,
tenant: &str,
malo_id: Option<&str>,
melo_id: Option<&str>,
tr_typ: Option<&str>,
ist_fernschaltbar: Option<bool>,
data: serde_json::Value,
bo4e_version: &str,
) -> Result<(), MdmError>;
async fn find_tr(
&self,
tr_id: &str,
tenant: &str,
) -> Result<Option<TechnischeRessourceRecord>, MdmError>;
async fn list_tr_by_malo(
&self,
malo_id: &str,
tenant: &str,
) -> Result<Vec<TechnischeRessourceRecord>, MdmError>;
async fn list_tr_by_melo(
&self,
melo_id: &str,
tenant: &str,
) -> Result<Vec<TechnischeRessourceRecord>, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LokationszuordnungEdge {
pub id: uuid::Uuid,
pub tenant: String,
pub von_id: String,
pub von_typ: String,
pub nach_id: String,
pub nach_typ: String,
pub valid_from: Option<time::Date>,
pub valid_to: Option<time::Date>,
pub data: serde_json::Value,
#[serde(default)]
pub depth: i32,
}
#[allow(async_fn_in_trait)]
pub trait LokationszuordnungRepository: Send + Sync {
#[allow(clippy::too_many_arguments)]
async fn upsert_edge(
&self,
tenant: &str,
von_id: &str,
von_typ: &str,
nach_id: &str,
nach_typ: &str,
valid_from: Option<time::Date>,
valid_to: Option<time::Date>,
data: serde_json::Value,
) -> Result<uuid::Uuid, MdmError>;
async fn find_graph(
&self,
tenant: &str,
root_id: &str,
at_date: Option<time::Date>,
) -> Result<Vec<LokationszuordnungEdge>, MdmError>;
async fn list_edges_from(
&self,
tenant: &str,
von_id: &str,
at_date: Option<time::Date>,
) -> Result<Vec<LokationszuordnungEdge>, MdmError>;
async fn delete_edge(
&self,
tenant: &str,
von_id: &str,
nach_id: &str,
) -> Result<bool, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ZaehlerRecord {
pub zaehler_id: String,
pub tenant: String,
pub melo_id: String,
pub zaehler_typ: Option<String>,
pub eichung_bis: Option<time::Date>,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub version: i64,
pub updated_at: time::OffsetDateTime,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct GeraetRecord {
pub geraet_id: String,
pub tenant: String,
pub zaehler_id: String,
pub geraet_typ: Option<String>,
pub data: serde_json::Value,
#[serde(default = "default_bo4e_version")]
pub bo4e_version: String,
pub version: i64,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait DeviceRepository: Send + Sync {
#[allow(clippy::too_many_arguments)]
async fn upsert_zaehler(
&self,
zaehler_id: &str,
tenant: &str,
melo_id: &str,
zaehler_typ: Option<&str>,
eichung_bis: Option<time::Date>,
data: serde_json::Value,
bo4e_version: &str,
) -> Result<(), MdmError>;
async fn list_zaehler_by_melo(
&self,
melo_id: &str,
tenant: &str,
) -> Result<Vec<ZaehlerRecord>, MdmError>;
async fn find_zaehler(
&self,
zaehler_id: &str,
tenant: &str,
) -> Result<Option<ZaehlerRecord>, MdmError>;
async fn upsert_geraet(
&self,
geraet_id: &str,
tenant: &str,
zaehler_id: &str,
geraet_typ: Option<&str>,
data: serde_json::Value,
bo4e_version: &str,
) -> Result<(), MdmError>;
async fn list_geraete_by_zaehler(
&self,
zaehler_id: &str,
tenant: &str,
) -> Result<Vec<GeraetRecord>, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ZaehlzeitRegisterRecord {
pub id: uuid::Uuid,
pub zaehler_id: String,
pub tenant: String,
pub bezeichnung: String,
pub zaehlerauspraegung: String,
pub obis_kennzahl: Option<String>,
#[serde(default = "default_kwh")]
pub einheit: String,
pub valid_from: time::Date,
pub valid_to: Option<time::Date>,
pub updated_at: time::OffsetDateTime,
}
fn default_kwh() -> String {
"KWH".to_owned()
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ZaehlzeitSaisonRecord {
pub id: uuid::Uuid,
pub register_id: uuid::Uuid,
pub saison: String,
pub wochentage: serde_json::Value,
pub zeit_von: String,
pub zeit_bis: String,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait ZaehlzeitRepository: Send + Sync {
async fn upsert_register(&self, rec: &ZaehlzeitRegisterRecord) -> Result<(), MdmError>;
async fn list_registers_by_zaehler(
&self,
zaehler_id: &str,
tenant: &str,
) -> Result<Vec<ZaehlzeitRegisterRecord>, MdmError>;
async fn upsert_saison(&self, rec: &ZaehlzeitSaisonRecord) -> Result<(), MdmError>;
async fn list_saisons_by_register(
&self,
register_id: uuid::Uuid,
tenant: &str,
) -> Result<Vec<ZaehlzeitSaisonRecord>, MdmError>;
async fn resolve_tariff_zone(
&self,
zaehler_id: &str,
tenant: &str,
local_datetime: time::PrimitiveDateTime,
) -> Result<Option<String>, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MmmaPreisGasRecord {
pub price_month: time::Date,
pub marktgebiet: String,
pub mehr_ct_kwh: rust_decimal::Decimal,
pub minder_ct_kwh: rust_decimal::Decimal,
pub source: String,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait MmmaPreisGasRepository: Send + Sync {
async fn upsert_gas(
&self,
price_month: time::Date,
marktgebiet: &str,
mehr_ct_kwh: rust_decimal::Decimal,
minder_ct_kwh: rust_decimal::Decimal,
source: &str,
) -> Result<(), MdmError>;
async fn find_gas(
&self,
price_month: time::Date,
marktgebiet: &str,
) -> Result<Option<MmmaPreisGasRecord>, MdmError>;
async fn list_gas(&self, limit: i64) -> Result<Vec<MmmaPreisGasRecord>, MdmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MmmPreisStromRecord {
pub price_month: time::Date,
pub unb_mp_id: String,
pub mehr_ct_kwh: rust_decimal::Decimal,
pub minder_ct_kwh: rust_decimal::Decimal,
pub source: String,
pub updated_at: time::OffsetDateTime,
}
#[allow(async_fn_in_trait)]
pub trait MmmPreisStromRepository: Send + Sync {
async fn upsert_strom(
&self,
price_month: time::Date,
unb_mp_id: &str,
mehr_ct_kwh: rust_decimal::Decimal,
minder_ct_kwh: rust_decimal::Decimal,
source: &str,
) -> Result<(), MdmError>;
async fn find_strom(
&self,
price_month: time::Date,
unb_mp_id: &str,
) -> Result<Option<MmmPreisStromRecord>, MdmError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NbEnergiemixRecord {
pub nb_mp_id: String,
pub gueltig_fuer: i16,
pub energiemix: serde_json::Value,
pub eeg_einspeisung_kwh: Option<i64>,
pub gesamtentnahme_kwh: Option<i64>,
#[serde(with = "time::serde::rfc3339::option", default)]
pub updated_at: Option<time::OffsetDateTime>,
}
#[allow(async_fn_in_trait)]
pub trait NbEnergiemixRepository: Send + Sync {
async fn upsert_energiemix(
&self,
tenant: &str,
nb_mp_id: &str,
gueltig_fuer: i16,
energiemix: serde_json::Value,
eeg_einspeisung_kwh: Option<i64>,
gesamtentnahme_kwh: Option<i64>,
) -> Result<(), MdmError>;
async fn find_energiemix(
&self,
tenant: &str,
nb_mp_id: &str,
year: Option<i16>,
) -> Result<Option<NbEnergiemixRecord>, MdmError>;
async fn list_energiemix_years(
&self,
tenant: &str,
nb_mp_id: &str,
) -> Result<Vec<i16>, MdmError>;
}