1use std::collections::HashMap;
4use std::sync::Mutex;
5
6use mig_assembly::ConversionService;
7use mig_bo4e::engine::DataBundle;
8use mig_bo4e::MappingEngine;
9
10use crate::data_dir::DataDir;
11use crate::error::MapperError;
12
13pub struct Bo4eResult {
15 pub pid: String,
17 pub message_type: String,
19 pub variant: String,
21 pub bo4e: serde_json::Value,
23}
24
25pub struct Mapper {
43 data_dir: DataDir,
44 bundles: Mutex<HashMap<String, DataBundle>>,
45}
46
47impl Mapper {
48 pub fn from_data_dir(data_dir: DataDir) -> Result<Self, MapperError> {
53 let mapper = Self {
54 data_dir,
55 bundles: Mutex::new(HashMap::new()),
56 };
57 let eager_fvs: Vec<String> = mapper.data_dir.eager_fvs().to_vec();
58 for fv in &eager_fvs {
59 mapper.ensure_bundle_loaded(fv)?;
60 }
61 Ok(mapper)
62 }
63
64 fn ensure_bundle_loaded(&self, fv: &str) -> Result<(), MapperError> {
66 let mut bundles = self.bundles.lock().unwrap();
67 if bundles.contains_key(fv) {
68 return Ok(());
69 }
70 let path = self.data_dir.bundle_path(fv);
71 if !path.exists() {
72 return Err(MapperError::BundleNotFound { fv: fv.to_string() });
73 }
74 let bundle = DataBundle::load(&path)?;
75 bundles.insert(fv.to_string(), bundle);
76 Ok(())
77 }
78
79 pub fn conversion_service(
83 &self,
84 fv: &str,
85 variant: &str,
86 ) -> Result<ConversionService, MapperError> {
87 self.ensure_bundle_loaded(fv)?;
88 let bundles = self.bundles.lock().unwrap();
89 let bundle = bundles.get(fv).unwrap();
90 let vc = bundle
91 .variant(variant)
92 .ok_or_else(|| MapperError::VariantNotFound {
93 fv: fv.to_string(),
94 variant: variant.to_string(),
95 })?;
96 let mig = vc
97 .mig_schema
98 .as_ref()
99 .ok_or_else(|| MapperError::VariantNotFound {
100 fv: fv.to_string(),
101 variant: format!("{variant} (no MIG schema in bundle)"),
102 })?;
103 Ok(ConversionService::from_mig(mig.clone()))
104 }
105
106 pub fn engine(&self, fv: &str, variant: &str, pid: &str) -> Result<MappingEngine, MapperError> {
110 self.ensure_bundle_loaded(fv)?;
111 let bundles = self.bundles.lock().unwrap();
112 let bundle = bundles.get(fv).unwrap();
113 let vc = bundle
114 .variant(variant)
115 .ok_or_else(|| MapperError::VariantNotFound {
116 fv: fv.to_string(),
117 variant: variant.to_string(),
118 })?;
119 let pid_key = format!("pid_{pid}");
120 let defs = vc
121 .combined_defs
122 .get(&pid_key)
123 .ok_or_else(|| MapperError::PidNotFound {
124 fv: fv.to_string(),
125 variant: variant.to_string(),
126 pid: pid.to_string(),
127 })?;
128 Ok(MappingEngine::from_definitions(defs.clone()))
129 }
130
131 pub fn validate_pid(
136 &self,
137 json: &serde_json::Value,
138 fv: &str,
139 variant: &str,
140 pid: &str,
141 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
142 self.ensure_bundle_loaded(fv)?;
143 let bundles = self.bundles.lock().unwrap();
144 let bundle = bundles.get(fv).unwrap();
145 let vc = bundle
146 .variant(variant)
147 .ok_or_else(|| MapperError::VariantNotFound {
148 fv: fv.to_string(),
149 variant: variant.to_string(),
150 })?;
151 let pid_key = format!("pid_{pid}");
152 let requirements =
153 vc.pid_requirements
154 .get(&pid_key)
155 .ok_or_else(|| MapperError::PidNotFound {
156 fv: fv.to_string(),
157 variant: variant.to_string(),
158 pid: pid.to_string(),
159 })?;
160
161 Ok(mig_bo4e::pid_validation::validate_pid_json_transaction(
162 json,
163 requirements,
164 ))
165 }
166
167 pub fn validate_pid_struct(
179 &self,
180 value: &impl serde::Serialize,
181 fv: &str,
182 variant: &str,
183 pid: &str,
184 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
185 let json = serde_json::to_value(value).map_err(|e| {
186 MapperError::Mapping(mig_bo4e::MappingError::TypeConversion(e.to_string()))
187 })?;
188 self.validate_pid(&json, fv, variant, pid)
189 }
190
191 pub fn validate_pid_with_conditions(
199 &self,
200 json: &serde_json::Value,
201 fv: &str,
202 variant: &str,
203 pid: &str,
204 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
205 self.ensure_bundle_loaded(fv)?;
206 let bundles = self.bundles.lock().unwrap();
207 let bundle = bundles.get(fv).unwrap();
208 let vc = bundle
209 .variant(variant)
210 .ok_or_else(|| MapperError::VariantNotFound {
211 fv: fv.to_string(),
212 variant: variant.to_string(),
213 })?;
214 let pid_key = format!("pid_{pid}");
215
216 let requirements =
217 vc.pid_requirements
218 .get(&pid_key)
219 .ok_or_else(|| MapperError::PidNotFound {
220 fv: fv.to_string(),
221 variant: variant.to_string(),
222 pid: pid.to_string(),
223 })?;
224
225 let evaluator = crate::evaluator_factory::create_evaluator(variant, fv);
227
228 if let Some(evaluator) = evaluator {
229 let defs = vc
231 .combined_defs
232 .get(&pid_key)
233 .ok_or_else(|| MapperError::PidNotFound {
234 fv: fv.to_string(),
235 variant: variant.to_string(),
236 pid: pid.to_string(),
237 })?;
238 let engine = MappingEngine::from_definitions(defs.clone());
239 let tree = engine.map_all_reverse(json, None);
240
241 let segments = crate::tree_to_segments::tree_to_owned_segments(&tree);
243
244 Ok(crate::evaluator_factory::validate_with_boxed_evaluator(
246 evaluator.as_ref(),
247 json,
248 requirements,
249 pid,
250 &segments,
251 ))
252 } else {
253 Ok(mig_bo4e::pid_validation::validate_pid_json_transaction(
255 json,
256 requirements,
257 ))
258 }
259 }
260
261 pub fn to_edifact(
287 &self,
288 msg_stammdaten: &serde_json::Value,
289 tx_stammdaten: &[serde_json::Value],
290 fv: &str,
291 variant: &str,
292 pid: &str,
293 ) -> Result<String, MapperError> {
294 self.ensure_bundle_loaded(fv)?;
295 let bundles = self.bundles.lock().unwrap();
296 let bundle = bundles.get(fv).unwrap();
297 let vc = bundle
298 .variant(variant)
299 .ok_or_else(|| MapperError::VariantNotFound {
300 fv: fv.to_string(),
301 variant: variant.to_string(),
302 })?;
303
304 let tx_group = vc
305 .tx_group(pid)
306 .ok_or_else(|| MapperError::PidNotFound {
307 fv: fv.to_string(),
308 variant: variant.to_string(),
309 pid: pid.to_string(),
310 })?;
311
312 let msg_engine = vc.msg_engine();
313 let tx_engine =
314 vc.tx_engine(pid)
315 .ok_or_else(|| MapperError::PidNotFound {
316 fv: fv.to_string(),
317 variant: variant.to_string(),
318 pid: pid.to_string(),
319 })?;
320
321 let filtered_mig =
322 vc.filtered_mig(pid)
323 .ok_or_else(|| MapperError::NoMigSchema {
324 fv: fv.to_string(),
325 variant: variant.to_string(),
326 })?;
327
328 let transaktionen: Vec<mig_bo4e::model::MappedTransaktion> = tx_stammdaten
330 .iter()
331 .map(|tx| mig_bo4e::model::MappedTransaktion {
332 stammdaten: tx.clone(),
333 nesting_info: Default::default(),
334 })
335 .collect();
336 let mapped = mig_bo4e::model::MappedMessage {
337 stammdaten: msg_stammdaten.clone(),
338 transaktionen,
339 nesting_info: Default::default(),
340 };
341
342 let tree = MappingEngine::map_interchange_reverse(
344 &msg_engine,
345 &tx_engine,
346 &mapped,
347 tx_group,
348 Some(&filtered_mig),
349 );
350
351 let disassembler =
353 mig_assembly::disassembler::Disassembler::new(&filtered_mig);
354 let segments = disassembler.disassemble(&tree);
355
356 let delimiters = edifact_primitives::EdifactDelimiters::default();
358 Ok(mig_assembly::renderer::render_edifact(
359 &segments,
360 &delimiters,
361 ))
362 }
363
364 pub fn to_edifact_struct(
370 &self,
371 nachricht: &impl serde::Serialize,
372 fv: &str,
373 variant: &str,
374 pid: &str,
375 ) -> Result<String, MapperError> {
376 let json = serde_json::to_value(nachricht)
377 .map_err(|e| MapperError::Serialization(e.to_string()))?;
378
379 let msg_stammdaten = json
380 .get("stammdaten")
381 .cloned()
382 .unwrap_or(serde_json::Value::Object(Default::default()));
383
384 let tx_stammdaten: Vec<serde_json::Value> = json
385 .get("transaktionen")
386 .and_then(|v| v.as_array())
387 .cloned()
388 .unwrap_or_default();
389
390 self.to_edifact(&msg_stammdaten, &tx_stammdaten, fv, variant, pid)
391 }
392
393 pub fn from_edifact<M, T>(
411 &self,
412 edifact: &str,
413 fv: &str,
414 variant: &str,
415 pid: &str,
416 ) -> Result<mig_bo4e::model::Interchange<M, T>, MapperError>
417 where
418 M: serde::de::DeserializeOwned,
419 T: serde::de::DeserializeOwned,
420 {
421 self.ensure_bundle_loaded(fv)?;
422 let bundles = self.bundles.lock().unwrap();
423 let bundle = bundles.get(fv).unwrap();
424 let vc = bundle
425 .variant(variant)
426 .ok_or_else(|| MapperError::VariantNotFound {
427 fv: fv.to_string(),
428 variant: variant.to_string(),
429 })?;
430
431 let tx_group = vc
432 .tx_group(pid)
433 .ok_or_else(|| MapperError::PidNotFound {
434 fv: fv.to_string(),
435 variant: variant.to_string(),
436 pid: pid.to_string(),
437 })?;
438
439 let msg_engine = vc.msg_engine();
440 let tx_engine =
441 vc.tx_engine(pid)
442 .ok_or_else(|| MapperError::PidNotFound {
443 fv: fv.to_string(),
444 variant: variant.to_string(),
445 pid: pid.to_string(),
446 })?;
447
448 let filtered_mig =
449 vc.filtered_mig(pid)
450 .ok_or_else(|| MapperError::NoMigSchema {
451 fv: fv.to_string(),
452 variant: variant.to_string(),
453 })?;
454
455 let svc = ConversionService::from_mig(filtered_mig);
457 let (chunks, trees) = svc.convert_interchange_to_trees(edifact)?;
458
459 let tree = trees
460 .first()
461 .ok_or_else(|| MapperError::Assembly(
462 mig_assembly::AssemblyError::ParseError("No messages in interchange".to_string()),
463 ))?;
464
465 let interchangedaten =
467 mig_bo4e::model::extract_interchangedaten(&chunks.envelope);
468 let msg_chunk = chunks.messages.first().ok_or_else(|| {
469 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
470 "No message chunks".to_string(),
471 ))
472 })?;
473 let (unh_ref, nachrichten_typ) =
474 mig_bo4e::model::extract_unh_fields(&msg_chunk.unh);
475 let nachrichtendaten = mig_bo4e::model::Nachrichtendaten {
476 unh_referenz: unh_ref,
477 nachrichten_typ,
478 };
479
480 MappingEngine::map_interchange_typed::<M, T>(
482 &msg_engine,
483 &tx_engine,
484 tree,
485 tx_group,
486 true,
487 nachrichtendaten,
488 interchangedaten,
489 )
490 .map_err(|e| MapperError::Serialization(e.to_string()))
491 }
492
493 pub fn association_code(&self, fv: &str, variant: &str) -> Result<String, MapperError> {
504 let meta = self.message_metadata(fv, variant)?;
505 Ok(meta.association_code)
506 }
507
508 pub fn message_metadata(
513 &self,
514 fv: &str,
515 variant: &str,
516 ) -> Result<MessageMetadata, MapperError> {
517 self.ensure_bundle_loaded(fv)?;
518 let bundles = self.bundles.lock().unwrap();
519 let bundle = bundles.get(fv).unwrap();
520 let vc = bundle
521 .variant(variant)
522 .ok_or_else(|| MapperError::VariantNotFound {
523 fv: fv.to_string(),
524 variant: variant.to_string(),
525 })?;
526 let mig = vc
527 .mig_schema
528 .as_ref()
529 .ok_or_else(|| MapperError::NoMigSchema {
530 fv: fv.to_string(),
531 variant: variant.to_string(),
532 })?;
533 Ok(MessageMetadata {
534 message_type: mig.message_type.clone(),
535 release: release_code_for_message_type(&mig.message_type),
536 association_code: mig.version.clone(),
537 })
538 }
539
540 pub fn to_edifact_interchange(
564 &self,
565 envelope: &InterchangeEnvelope,
566 messages: &[InterchangeMessage],
567 ) -> Result<String, MapperError> {
568 let delimiters = edifact_primitives::EdifactDelimiters::default();
569 let sep = delimiters.component as char;
570 let elem = delimiters.element as char;
571 let seg_term = delimiters.segment as char;
572
573 let mut output = String::new();
574
575 output.push_str(&format!(
577 "UNA{}{}{}{}{}{}",
578 sep, elem, delimiters.decimal as char, delimiters.release as char, ' ', seg_term, ));
585
586 let now = chrono::Utc::now();
588 let date_str = now.format("%y%m%d").to_string();
589 let time_str = now.format("%H%M").to_string();
590 let sender = &envelope.sender;
591 let receiver = &envelope.receiver;
592 let interchange_ref = &envelope.interchange_ref;
593 output.push_str(&format!(
594 "UNB{elem}UNOC{sep}3{elem}{sid}{sep}{sq}{elem}{rid}{sep}{rq}{elem}{date_str}{sep}{time_str}{elem}{interchange_ref}{seg_term}",
595 sid = sender.id,
596 sq = sender.qualifier,
597 rid = receiver.id,
598 rq = receiver.qualifier,
599 ));
600
601 let mut message_count = 0u32;
602
603 for msg in messages {
604 let meta = self.message_metadata(&msg.fv, &msg.variant)?;
605
606 let body = self.to_edifact(
608 &msg.msg_stammdaten,
609 &msg.tx_stammdaten,
610 &msg.fv,
611 &msg.variant,
612 &msg.pid,
613 )?;
614
615 let body_seg_count = body
617 .split(seg_term)
618 .filter(|s: &&str| !s.is_empty())
619 .count();
620 let segment_count = body_seg_count + 2;
622
623 output.push_str(&format!(
625 "UNH{elem}{ref}{elem}{msg_type}{sep}D{sep}{release}{sep}UN{sep}{assoc}{seg_term}",
626 ref = msg.message_ref,
627 msg_type = meta.message_type,
628 release = meta.release,
629 assoc = meta.association_code,
630 ));
631
632 output.push_str(&body);
634
635 output.push_str(&format!(
637 "UNT{elem}{segment_count}{elem}{ref}{seg_term}",
638 ref = msg.message_ref,
639 ));
640
641 message_count += 1;
642 }
643
644 output.push_str(&format!(
646 "UNZ{elem}{message_count}{elem}{interchange_ref}{seg_term}",
647 ));
648
649 Ok(output)
650 }
651
652 pub fn loaded_format_versions(&self) -> Vec<String> {
654 self.bundles.lock().unwrap().keys().cloned().collect()
655 }
656
657 pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError> {
661 self.ensure_bundle_loaded(fv)?;
662 let bundles = self.bundles.lock().unwrap();
663 let bundle = bundles.get(fv).unwrap();
664 Ok(bundle.variants.keys().cloned().collect())
665 }
666}
667
668#[derive(Debug, Clone)]
670pub struct MessageMetadata {
671 pub message_type: String,
673 pub release: String,
675 pub association_code: String,
677}
678
679#[derive(Debug, Clone)]
681pub struct InterchangeEnvelope {
682 pub sender: EdifactParty,
684 pub receiver: EdifactParty,
686 pub interchange_ref: String,
688}
689
690#[derive(Debug, Clone)]
692pub struct EdifactParty {
693 pub id: String,
695 pub qualifier: String,
697}
698
699impl EdifactParty {
700 pub fn bdew(id: &str) -> Self {
702 Self {
703 id: id.to_string(),
704 qualifier: "500".to_string(),
705 }
706 }
707
708 pub fn gs1(id: &str) -> Self {
710 Self {
711 id: id.to_string(),
712 qualifier: "14".to_string(),
713 }
714 }
715}
716
717#[derive(Debug, Clone)]
720pub struct InterchangeMessage {
721 pub message_ref: String,
723 pub msg_stammdaten: serde_json::Value,
725 pub tx_stammdaten: Vec<serde_json::Value>,
727 pub fv: String,
729 pub variant: String,
731 pub pid: String,
733}
734
735fn release_code_for_message_type(msg_type: &str) -> String {
739 match msg_type {
740 "APERAK" => "07B",
741 "COMDIS" => "17A",
742 "CONTRL" => "04B",
743 "IFTSTA" => "18A",
744 "INSRPT" => "18A",
745 "INVOIC" => "06A",
746 "MSCONS" => "04B",
747 "ORDCHG" => "09B",
748 "ORDERS" => "09B",
749 "ORDRSP" => "10A",
750 "PARTIN" => "20B",
751 "PRICAT" => "20B",
752 "QUOTES" => "10A",
753 "REMADV" => "05A",
754 "REQOTE" => "10A",
755 "UTILMD" => "11A",
756 "UTILTS" => "18A",
757 _ => "04B", }
759 .to_string()
760}
761
762
763#[cfg(test)]
764mod tests {
765 use super::*;
766 use std::path::Path;
767
768 fn data_dir() -> Option<std::path::PathBuf> {
769 let dist = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../dist");
771 if dist.join("edifact-data-FV2504.bin").exists() {
772 return Some(dist);
773 }
774 let cache = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cache/mappings");
775 if cache.join("FV2504").exists() {
776 return Some(cache);
777 }
778 eprintln!("Skipping test: no DataBundle files found");
779 None
780 }
781
782 #[test]
783 fn test_to_edifact_produces_edifact_output() {
784 let Some(data_dir) = data_dir() else {
785 return;
786 };
787 let mapper =
788 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
789
790 let msg_stammdaten = serde_json::json!({
791 "marktteilnehmer": [{
792 "marktrolle": "MS",
793 "rollencodenummer": "9900123456789",
794 "codepflegeCode": "293"
795 }]
796 });
797 let tx_stammdaten = serde_json::json!({
798 "prozessdaten": {
799 "pruefidentifikator": "55001",
800 "vorgangId": "ABC123",
801 "transaktionsgrund": "E01"
802 }
803 });
804
805 let result = mapper.to_edifact(
806 &msg_stammdaten,
807 &[tx_stammdaten],
808 "FV2504",
809 "UTILMD_Strom",
810 "55001",
811 );
812 assert!(result.is_ok(), "to_edifact failed: {:?}", result.err());
813 let edifact = result.unwrap();
814 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
815 assert!(edifact.contains("NAD"), "Should contain NAD segment");
817 assert!(edifact.contains("IDE"), "Should contain IDE segment");
819 }
820
821 #[test]
822 fn test_to_edifact_struct_produces_edifact_output() {
823 let Some(data_dir) = data_dir() else {
824 return;
825 };
826 let mapper =
827 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
828
829 let nachricht = serde_json::json!({
830 "stammdaten": {
831 "marktteilnehmer": [{
832 "marktrolle": "MS",
833 "rollencodenummer": "9900123456789",
834 "codepflegeCode": "293"
835 }]
836 },
837 "transaktionen": [{
838 "prozessdaten": {
839 "pruefidentifikator": "55001",
840 "vorgangId": "ABC123"
841 }
842 }]
843 });
844
845 let result = mapper.to_edifact_struct(&nachricht, "FV2504", "UTILMD_Strom", "55001");
846 assert!(
847 result.is_ok(),
848 "to_edifact_struct failed: {:?}",
849 result.err()
850 );
851 let edifact = result.unwrap();
852 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
853 }
854
855 #[test]
856 fn test_to_edifact_invalid_fv_returns_error() {
857 let Some(data_dir) = data_dir() else {
858 return;
859 };
860 let mapper =
861 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
862
863 let result = mapper.to_edifact(
864 &serde_json::json!({}),
865 &[serde_json::json!({})],
866 "FV9999",
867 "UTILMD_Strom",
868 "55001",
869 );
870 assert!(result.is_err());
871 }
872
873 #[test]
874 fn test_to_edifact_invalid_variant_returns_error() {
875 let Some(data_dir) = data_dir() else {
876 return;
877 };
878 let mapper =
879 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
880
881 let result = mapper.to_edifact(
882 &serde_json::json!({}),
883 &[serde_json::json!({})],
884 "FV2504",
885 "NONEXISTENT",
886 "55001",
887 );
888 assert!(result.is_err());
889 }
890
891 #[test]
892 fn test_to_edifact_invalid_pid_returns_error() {
893 let Some(data_dir) = data_dir() else {
894 return;
895 };
896 let mapper =
897 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
898
899 let result = mapper.to_edifact(
900 &serde_json::json!({}),
901 &[serde_json::json!({})],
902 "FV2504",
903 "UTILMD_Strom",
904 "99999",
905 );
906 assert!(result.is_err());
907 }
908
909 #[test]
910 fn test_association_code() {
911 let Some(data_dir) = data_dir() else {
912 return;
913 };
914 let mapper =
915 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
916
917 let code = mapper.association_code("FV2504", "UTILMD_Strom").unwrap();
918 assert_eq!(code, "S2.1");
919
920 let code = mapper.association_code("FV2504", "MSCONS").unwrap();
921 assert_eq!(code, "2.4c");
922 }
923
924 #[test]
925 fn test_message_metadata() {
926 let Some(data_dir) = data_dir() else {
927 return;
928 };
929 let mapper =
930 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
931
932 let meta = mapper.message_metadata("FV2504", "UTILMD_Strom").unwrap();
933 assert_eq!(meta.message_type, "UTILMD");
934 assert_eq!(meta.release, "11A");
935 assert_eq!(meta.association_code, "S2.1");
936 }
937
938 #[test]
939 fn test_to_edifact_interchange() {
940 let Some(data_dir) = data_dir() else {
941 return;
942 };
943 let mapper =
944 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
945
946 let result = mapper.to_edifact_interchange(
947 &InterchangeEnvelope {
948 sender: EdifactParty::bdew("9900000000003"),
949 receiver: EdifactParty::bdew("9900000000001"),
950 interchange_ref: "REF001".to_string(),
951 },
952 &[InterchangeMessage {
953 message_ref: "MSG001".to_string(),
954 msg_stammdaten: serde_json::json!({
955 "marktteilnehmer": [{
956 "marktrolle": "MS",
957 "rollencodenummer": "9900123456789",
958 "codepflegeCode": "293"
959 }]
960 }),
961 tx_stammdaten: vec![serde_json::json!({
962 "prozessdaten": {
963 "pruefidentifikator": "55001",
964 "vorgangId": "ABC123",
965 "transaktionsgrund": "E01"
966 }
967 })],
968 fv: "FV2504".to_string(),
969 variant: "UTILMD_Strom".to_string(),
970 pid: "55001".to_string(),
971 }],
972 );
973 assert!(
974 result.is_ok(),
975 "to_edifact_interchange failed: {:?}",
976 result.err()
977 );
978 let edifact = result.unwrap();
979
980 assert!(edifact.starts_with("UNA:+.? '"), "Should start with UNA");
982 assert!(edifact.contains("UNB+UNOC:3+9900000000003:500+9900000000001:500+"),
983 "Should contain UNB with sender/receiver");
984 assert!(edifact.contains("UNH+MSG001+UTILMD:D:11A:UN:S2.1'"),
985 "Should contain UNH with correct S009");
986 assert!(edifact.contains("NAD"), "Should contain body NAD segment");
987 assert!(edifact.contains("UNT+"), "Should contain UNT");
988 assert!(edifact.contains("+MSG001'"), "UNT should reference message ref");
989 assert!(edifact.contains("UNZ+1+REF001'"), "Should contain UNZ with count and ref");
990 }
991}