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 {
61 data_dir: DataDir,
62 bundles: Mutex<HashMap<String, DataBundle>>,
63}
64
65impl Mapper {
66 pub fn from_data_dir(data_dir: DataDir) -> Result<Self, MapperError> {
71 let mapper = Self {
72 data_dir,
73 bundles: Mutex::new(HashMap::new()),
74 };
75 let eager_fvs: Vec<String> = mapper.data_dir.eager_fvs().to_vec();
76 for fv in &eager_fvs {
77 mapper.ensure_bundle_loaded(fv)?;
78 }
79 Ok(mapper)
80 }
81
82 fn ensure_bundle_loaded(&self, fv: &str) -> Result<(), MapperError> {
84 let mut bundles = self.bundles.lock().unwrap();
85 if bundles.contains_key(fv) {
86 return Ok(());
87 }
88 let path = self.data_dir.bundle_path(fv);
89 if !path.exists() {
90 return Err(MapperError::BundleNotFound { fv: fv.to_string() });
91 }
92 let bundle = DataBundle::load(&path)?;
93 bundles.insert(fv.to_string(), bundle);
94 Ok(())
95 }
96
97 pub fn conversion_service(
101 &self,
102 fv: &str,
103 variant: &str,
104 ) -> Result<ConversionService, MapperError> {
105 self.ensure_bundle_loaded(fv)?;
106 let bundles = self.bundles.lock().unwrap();
107 let bundle = bundles.get(fv).unwrap();
108 let vc = bundle
109 .variant(variant)
110 .ok_or_else(|| MapperError::VariantNotFound {
111 fv: fv.to_string(),
112 variant: variant.to_string(),
113 })?;
114 let mig = vc
115 .mig_schema
116 .as_ref()
117 .ok_or_else(|| MapperError::VariantNotFound {
118 fv: fv.to_string(),
119 variant: format!("{variant} (no MIG schema in bundle)"),
120 })?;
121 Ok(ConversionService::from_mig(mig.clone()))
122 }
123
124 pub fn engine(&self, fv: &str, variant: &str, pid: &str) -> Result<MappingEngine, MapperError> {
128 self.ensure_bundle_loaded(fv)?;
129 let bundles = self.bundles.lock().unwrap();
130 let bundle = bundles.get(fv).unwrap();
131 let vc = bundle
132 .variant(variant)
133 .ok_or_else(|| MapperError::VariantNotFound {
134 fv: fv.to_string(),
135 variant: variant.to_string(),
136 })?;
137 let pid_key = format!("pid_{pid}");
138 let defs = vc
139 .combined_defs
140 .get(&pid_key)
141 .ok_or_else(|| MapperError::PidNotFound {
142 fv: fv.to_string(),
143 variant: variant.to_string(),
144 pid: pid.to_string(),
145 })?;
146 Ok(MappingEngine::from_definitions(defs.clone()))
147 }
148
149 pub fn validate_pid(
154 &self,
155 json: &serde_json::Value,
156 fv: &str,
157 variant: &str,
158 pid: &str,
159 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
160 self.ensure_bundle_loaded(fv)?;
161 let bundles = self.bundles.lock().unwrap();
162 let bundle = bundles.get(fv).unwrap();
163 let vc = bundle
164 .variant(variant)
165 .ok_or_else(|| MapperError::VariantNotFound {
166 fv: fv.to_string(),
167 variant: variant.to_string(),
168 })?;
169 let pid_key = format!("pid_{pid}");
170 let requirements =
171 vc.pid_requirements
172 .get(&pid_key)
173 .ok_or_else(|| MapperError::PidNotFound {
174 fv: fv.to_string(),
175 variant: variant.to_string(),
176 pid: pid.to_string(),
177 })?;
178
179 Ok(mig_bo4e::pid_validation::validate_pid_json_transaction(
180 json,
181 requirements,
182 ))
183 }
184
185 pub fn validate_pid_struct(
197 &self,
198 value: &impl serde::Serialize,
199 fv: &str,
200 variant: &str,
201 pid: &str,
202 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
203 let json = serde_json::to_value(value).map_err(|e| {
204 MapperError::Mapping(mig_bo4e::MappingError::TypeConversion(e.to_string()))
205 })?;
206 self.validate_pid(&json, fv, variant, pid)
207 }
208
209 pub fn validate_pid_with_conditions(
217 &self,
218 json: &serde_json::Value,
219 fv: &str,
220 variant: &str,
221 pid: &str,
222 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
223 self.ensure_bundle_loaded(fv)?;
224 let bundles = self.bundles.lock().unwrap();
225 let bundle = bundles.get(fv).unwrap();
226 let vc = bundle
227 .variant(variant)
228 .ok_or_else(|| MapperError::VariantNotFound {
229 fv: fv.to_string(),
230 variant: variant.to_string(),
231 })?;
232 let pid_key = format!("pid_{pid}");
233
234 let requirements =
235 vc.pid_requirements
236 .get(&pid_key)
237 .ok_or_else(|| MapperError::PidNotFound {
238 fv: fv.to_string(),
239 variant: variant.to_string(),
240 pid: pid.to_string(),
241 })?;
242
243 let evaluator = crate::evaluator_factory::create_evaluator(variant, fv);
245
246 if let Some(evaluator) = evaluator {
247 let defs = vc
249 .combined_defs
250 .get(&pid_key)
251 .ok_or_else(|| MapperError::PidNotFound {
252 fv: fv.to_string(),
253 variant: variant.to_string(),
254 pid: pid.to_string(),
255 })?;
256 let engine = MappingEngine::from_definitions(defs.clone());
257 let tree = engine.map_all_reverse(json, None);
258
259 let segments = crate::tree_to_segments::tree_to_owned_segments(&tree);
261
262 Ok(crate::evaluator_factory::validate_with_boxed_evaluator(
264 evaluator.as_ref(),
265 json,
266 requirements,
267 pid,
268 &segments,
269 ))
270 } else {
271 Ok(mig_bo4e::pid_validation::validate_pid_json_transaction(
273 json,
274 requirements,
275 ))
276 }
277 }
278
279 pub fn to_edifact(
305 &self,
306 msg_stammdaten: &serde_json::Value,
307 tx_stammdaten: &[serde_json::Value],
308 fv: &str,
309 variant: &str,
310 pid: &str,
311 ) -> Result<String, MapperError> {
312 self.ensure_bundle_loaded(fv)?;
313 let bundles = self.bundles.lock().unwrap();
314 let bundle = bundles.get(fv).unwrap();
315 let vc = bundle
316 .variant(variant)
317 .ok_or_else(|| MapperError::VariantNotFound {
318 fv: fv.to_string(),
319 variant: variant.to_string(),
320 })?;
321
322 let tx_group = vc
323 .tx_group(pid)
324 .ok_or_else(|| MapperError::PidNotFound {
325 fv: fv.to_string(),
326 variant: variant.to_string(),
327 pid: pid.to_string(),
328 })?;
329
330 let msg_engine = vc.msg_engine();
331 let tx_engine =
332 vc.tx_engine(pid)
333 .ok_or_else(|| MapperError::PidNotFound {
334 fv: fv.to_string(),
335 variant: variant.to_string(),
336 pid: pid.to_string(),
337 })?;
338
339 let filtered_mig =
340 vc.filtered_mig(pid)
341 .ok_or_else(|| MapperError::NoMigSchema {
342 fv: fv.to_string(),
343 variant: variant.to_string(),
344 })?;
345
346 let transaktionen: Vec<mig_bo4e::model::MappedTransaktion> = tx_stammdaten
348 .iter()
349 .map(|tx| mig_bo4e::model::MappedTransaktion {
350 stammdaten: tx.clone(),
351 nesting_info: Default::default(),
352 })
353 .collect();
354 let mapped = mig_bo4e::model::MappedMessage {
355 stammdaten: msg_stammdaten.clone(),
356 transaktionen,
357 nesting_info: Default::default(),
358 };
359
360 let tree = MappingEngine::map_interchange_reverse(
362 &msg_engine,
363 &tx_engine,
364 &mapped,
365 tx_group,
366 Some(&filtered_mig),
367 );
368
369 let disassembler =
371 mig_assembly::disassembler::Disassembler::new(&filtered_mig);
372 let segments = disassembler.disassemble(&tree);
373
374 let delimiters = edifact_primitives::EdifactDelimiters::default();
376 Ok(mig_assembly::renderer::render_edifact(
377 &segments,
378 &delimiters,
379 ))
380 }
381
382 pub fn to_edifact_struct(
388 &self,
389 nachricht: &impl serde::Serialize,
390 fv: &str,
391 variant: &str,
392 pid: &str,
393 ) -> Result<String, MapperError> {
394 let json = serde_json::to_value(nachricht)
395 .map_err(|e| MapperError::Serialization(e.to_string()))?;
396
397 let msg_stammdaten = json
398 .get("stammdaten")
399 .cloned()
400 .unwrap_or(serde_json::Value::Object(Default::default()));
401
402 let tx_stammdaten: Vec<serde_json::Value> = json
403 .get("transaktionen")
404 .and_then(|v| v.as_array())
405 .cloned()
406 .unwrap_or_default();
407
408 self.to_edifact(&msg_stammdaten, &tx_stammdaten, fv, variant, pid)
409 }
410
411 pub fn from_edifact<M, T>(
429 &self,
430 edifact: &str,
431 fv: &str,
432 variant: &str,
433 pid: &str,
434 ) -> Result<mig_bo4e::model::Interchange<M, T>, MapperError>
435 where
436 M: serde::de::DeserializeOwned,
437 T: serde::de::DeserializeOwned,
438 {
439 self.ensure_bundle_loaded(fv)?;
440 let bundles = self.bundles.lock().unwrap();
441 let bundle = bundles.get(fv).unwrap();
442 let vc = bundle
443 .variant(variant)
444 .ok_or_else(|| MapperError::VariantNotFound {
445 fv: fv.to_string(),
446 variant: variant.to_string(),
447 })?;
448
449 let tx_group = vc
450 .tx_group(pid)
451 .ok_or_else(|| MapperError::PidNotFound {
452 fv: fv.to_string(),
453 variant: variant.to_string(),
454 pid: pid.to_string(),
455 })?;
456
457 let msg_engine = vc.msg_engine();
458 let tx_engine =
459 vc.tx_engine(pid)
460 .ok_or_else(|| MapperError::PidNotFound {
461 fv: fv.to_string(),
462 variant: variant.to_string(),
463 pid: pid.to_string(),
464 })?;
465
466 let filtered_mig =
467 vc.filtered_mig(pid)
468 .ok_or_else(|| MapperError::NoMigSchema {
469 fv: fv.to_string(),
470 variant: variant.to_string(),
471 })?;
472
473 let svc = ConversionService::from_mig(filtered_mig);
475 let (chunks, trees) = svc.convert_interchange_to_trees(edifact)?;
476
477 let tree = trees
478 .first()
479 .ok_or_else(|| MapperError::Assembly(
480 mig_assembly::AssemblyError::ParseError("No messages in interchange".to_string()),
481 ))?;
482
483 let interchangedaten =
485 mig_bo4e::model::extract_interchangedaten(&chunks.envelope);
486 let msg_chunk = chunks.messages.first().ok_or_else(|| {
487 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
488 "No message chunks".to_string(),
489 ))
490 })?;
491 let (unh_ref, nachrichten_typ) =
492 mig_bo4e::model::extract_unh_fields(&msg_chunk.unh);
493 let nachrichtendaten = mig_bo4e::model::Nachrichtendaten {
494 unh_referenz: unh_ref,
495 nachrichten_typ,
496 };
497
498 MappingEngine::map_interchange_typed::<M, T>(
500 &msg_engine,
501 &tx_engine,
502 tree,
503 tx_group,
504 true,
505 nachrichtendaten,
506 interchangedaten,
507 )
508 .map_err(|e| MapperError::Serialization(e.to_string()))
509 }
510
511 pub fn detect_pid(&self, edifact: &str) -> Result<String, MapperError> {
523 let segments = mig_assembly::tokenize::parse_to_segments(edifact.as_bytes())?;
524 let chunks = mig_assembly::split_messages(segments)?;
525 let msg_chunk =
526 chunks
527 .messages
528 .first()
529 .ok_or_else(|| MapperError::Assembly(
530 mig_assembly::AssemblyError::ParseError(
531 "No messages found in EDIFACT content".to_string(),
532 ),
533 ))?;
534 let msg_segments = msg_chunk.message_segments();
535 mig_assembly::pid_detect::detect_pid(&msg_segments).map_err(MapperError::Assembly)
536 }
537
538 pub fn association_code(&self, fv: &str, variant: &str) -> Result<String, MapperError> {
549 let meta = self.message_metadata(fv, variant)?;
550 Ok(meta.association_code)
551 }
552
553 pub fn message_metadata(
558 &self,
559 fv: &str,
560 variant: &str,
561 ) -> Result<MessageMetadata, MapperError> {
562 self.ensure_bundle_loaded(fv)?;
563 let bundles = self.bundles.lock().unwrap();
564 let bundle = bundles.get(fv).unwrap();
565 let vc = bundle
566 .variant(variant)
567 .ok_or_else(|| MapperError::VariantNotFound {
568 fv: fv.to_string(),
569 variant: variant.to_string(),
570 })?;
571 let mig = vc
572 .mig_schema
573 .as_ref()
574 .ok_or_else(|| MapperError::NoMigSchema {
575 fv: fv.to_string(),
576 variant: variant.to_string(),
577 })?;
578 Ok(MessageMetadata {
579 message_type: mig.message_type.clone(),
580 release: release_code_for_message_type(&mig.message_type),
581 association_code: mig.version.clone(),
582 })
583 }
584
585 pub fn to_edifact_interchange(
609 &self,
610 envelope: &InterchangeEnvelope,
611 messages: &[InterchangeMessage],
612 ) -> Result<String, MapperError> {
613 let delimiters = edifact_primitives::EdifactDelimiters::default();
614 let sep = delimiters.component as char;
615 let elem = delimiters.element as char;
616 let seg_term = delimiters.segment as char;
617
618 let mut output = String::new();
619
620 output.push_str(&format!(
622 "UNA{}{}{}{}{}{}",
623 sep, elem, delimiters.decimal as char, delimiters.release as char, ' ', seg_term, ));
630
631 let now = chrono::Utc::now();
633 let date_str = now.format("%y%m%d").to_string();
634 let time_str = now.format("%H%M").to_string();
635 let sender = &envelope.sender;
636 let receiver = &envelope.receiver;
637 let interchange_ref = &envelope.interchange_ref;
638 output.push_str(&format!(
639 "UNB{elem}UNOC{sep}3{elem}{sid}{sep}{sq}{elem}{rid}{sep}{rq}{elem}{date_str}{sep}{time_str}{elem}{interchange_ref}{seg_term}",
640 sid = sender.id,
641 sq = sender.qualifier,
642 rid = receiver.id,
643 rq = receiver.qualifier,
644 ));
645
646 let mut message_count = 0u32;
647
648 for msg in messages {
649 let meta = self.message_metadata(&msg.fv, &msg.variant)?;
650
651 let body = self.to_edifact(
653 &msg.msg_stammdaten,
654 &msg.tx_stammdaten,
655 &msg.fv,
656 &msg.variant,
657 &msg.pid,
658 )?;
659
660 let body_seg_count = body
662 .split(seg_term)
663 .filter(|s: &&str| !s.is_empty())
664 .count();
665 let segment_count = body_seg_count + 2;
667
668 output.push_str(&format!(
670 "UNH{elem}{ref}{elem}{msg_type}{sep}D{sep}{release}{sep}UN{sep}{assoc}{seg_term}",
671 ref = msg.message_ref,
672 msg_type = meta.message_type,
673 release = meta.release,
674 assoc = meta.association_code,
675 ));
676
677 output.push_str(&body);
679
680 output.push_str(&format!(
682 "UNT{elem}{segment_count}{elem}{ref}{seg_term}",
683 ref = msg.message_ref,
684 ));
685
686 message_count += 1;
687 }
688
689 output.push_str(&format!(
691 "UNZ{elem}{message_count}{elem}{interchange_ref}{seg_term}",
692 ));
693
694 Ok(output)
695 }
696
697 pub fn loaded_format_versions(&self) -> Vec<String> {
699 self.bundles.lock().unwrap().keys().cloned().collect()
700 }
701
702 pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError> {
706 self.ensure_bundle_loaded(fv)?;
707 let bundles = self.bundles.lock().unwrap();
708 let bundle = bundles.get(fv).unwrap();
709 Ok(bundle.variants.keys().cloned().collect())
710 }
711}
712
713#[derive(Debug, Clone)]
715pub struct MessageMetadata {
716 pub message_type: String,
718 pub release: String,
720 pub association_code: String,
722}
723
724#[derive(Debug, Clone)]
726pub struct InterchangeEnvelope {
727 pub sender: EdifactParty,
729 pub receiver: EdifactParty,
731 pub interchange_ref: String,
733}
734
735#[derive(Debug, Clone)]
737pub struct EdifactParty {
738 pub id: String,
740 pub qualifier: String,
742}
743
744impl EdifactParty {
745 pub fn bdew(id: &str) -> Self {
747 Self {
748 id: id.to_string(),
749 qualifier: "500".to_string(),
750 }
751 }
752
753 pub fn gs1(id: &str) -> Self {
755 Self {
756 id: id.to_string(),
757 qualifier: "14".to_string(),
758 }
759 }
760}
761
762#[derive(Debug, Clone)]
765pub struct InterchangeMessage {
766 pub message_ref: String,
768 pub msg_stammdaten: serde_json::Value,
770 pub tx_stammdaten: Vec<serde_json::Value>,
772 pub fv: String,
774 pub variant: String,
776 pub pid: String,
778}
779
780fn release_code_for_message_type(msg_type: &str) -> String {
784 match msg_type {
785 "APERAK" => "07B",
786 "COMDIS" => "17A",
787 "CONTRL" => "04B",
788 "IFTSTA" => "18A",
789 "INSRPT" => "18A",
790 "INVOIC" => "06A",
791 "MSCONS" => "04B",
792 "ORDCHG" => "09B",
793 "ORDERS" => "09B",
794 "ORDRSP" => "10A",
795 "PARTIN" => "20B",
796 "PRICAT" => "20B",
797 "QUOTES" => "10A",
798 "REMADV" => "05A",
799 "REQOTE" => "10A",
800 "UTILMD" => "11A",
801 "UTILTS" => "18A",
802 _ => "04B", }
804 .to_string()
805}
806
807
808#[cfg(test)]
809mod tests {
810 use super::*;
811 use std::path::Path;
812
813 fn data_dir() -> Option<std::path::PathBuf> {
814 let dist = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../dist");
816 if dist.join("edifact-data-FV2504.bin").exists() {
817 return Some(dist);
818 }
819 let cache = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cache/mappings");
820 if cache.join("FV2504").exists() {
821 return Some(cache);
822 }
823 eprintln!("Skipping test: no DataBundle files found");
824 None
825 }
826
827 #[test]
828 fn test_to_edifact_produces_edifact_output() {
829 let Some(data_dir) = data_dir() else {
830 return;
831 };
832 let mapper =
833 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
834
835 let msg_stammdaten = serde_json::json!({
836 "marktteilnehmer": [{
837 "marktrolle": "MS",
838 "rollencodenummer": "9900123456789",
839 "codepflegeCode": "293"
840 }]
841 });
842 let tx_stammdaten = serde_json::json!({
843 "prozessdaten": {
844 "pruefidentifikator": "55001",
845 "vorgangId": "ABC123",
846 "transaktionsgrund": "E01"
847 }
848 });
849
850 let result = mapper.to_edifact(
851 &msg_stammdaten,
852 &[tx_stammdaten],
853 "FV2504",
854 "UTILMD_Strom",
855 "55001",
856 );
857 assert!(result.is_ok(), "to_edifact failed: {:?}", result.err());
858 let edifact = result.unwrap();
859 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
860 assert!(edifact.contains("NAD"), "Should contain NAD segment");
862 assert!(edifact.contains("IDE"), "Should contain IDE segment");
864 }
865
866 #[test]
867 fn test_to_edifact_struct_produces_edifact_output() {
868 let Some(data_dir) = data_dir() else {
869 return;
870 };
871 let mapper =
872 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
873
874 let nachricht = serde_json::json!({
875 "stammdaten": {
876 "marktteilnehmer": [{
877 "marktrolle": "MS",
878 "rollencodenummer": "9900123456789",
879 "codepflegeCode": "293"
880 }]
881 },
882 "transaktionen": [{
883 "prozessdaten": {
884 "pruefidentifikator": "55001",
885 "vorgangId": "ABC123"
886 }
887 }]
888 });
889
890 let result = mapper.to_edifact_struct(&nachricht, "FV2504", "UTILMD_Strom", "55001");
891 assert!(
892 result.is_ok(),
893 "to_edifact_struct failed: {:?}",
894 result.err()
895 );
896 let edifact = result.unwrap();
897 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
898 }
899
900 #[test]
901 fn test_to_edifact_invalid_fv_returns_error() {
902 let Some(data_dir) = data_dir() else {
903 return;
904 };
905 let mapper =
906 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
907
908 let result = mapper.to_edifact(
909 &serde_json::json!({}),
910 &[serde_json::json!({})],
911 "FV9999",
912 "UTILMD_Strom",
913 "55001",
914 );
915 assert!(result.is_err());
916 }
917
918 #[test]
919 fn test_to_edifact_invalid_variant_returns_error() {
920 let Some(data_dir) = data_dir() else {
921 return;
922 };
923 let mapper =
924 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
925
926 let result = mapper.to_edifact(
927 &serde_json::json!({}),
928 &[serde_json::json!({})],
929 "FV2504",
930 "NONEXISTENT",
931 "55001",
932 );
933 assert!(result.is_err());
934 }
935
936 #[test]
937 fn test_to_edifact_invalid_pid_returns_error() {
938 let Some(data_dir) = data_dir() else {
939 return;
940 };
941 let mapper =
942 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
943
944 let result = mapper.to_edifact(
945 &serde_json::json!({}),
946 &[serde_json::json!({})],
947 "FV2504",
948 "UTILMD_Strom",
949 "99999",
950 );
951 assert!(result.is_err());
952 }
953
954 #[test]
955 fn test_association_code() {
956 let Some(data_dir) = data_dir() else {
957 return;
958 };
959 let mapper =
960 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
961
962 let code = mapper.association_code("FV2504", "UTILMD_Strom").unwrap();
963 assert_eq!(code, "S2.1");
964
965 let code = mapper.association_code("FV2504", "MSCONS").unwrap();
966 assert_eq!(code, "2.4c");
967 }
968
969 #[test]
970 fn test_message_metadata() {
971 let Some(data_dir) = data_dir() else {
972 return;
973 };
974 let mapper =
975 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
976
977 let meta = mapper.message_metadata("FV2504", "UTILMD_Strom").unwrap();
978 assert_eq!(meta.message_type, "UTILMD");
979 assert_eq!(meta.release, "11A");
980 assert_eq!(meta.association_code, "S2.1");
981 }
982
983 #[test]
984 fn test_to_edifact_interchange() {
985 let Some(data_dir) = data_dir() else {
986 return;
987 };
988 let mapper =
989 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
990
991 let result = mapper.to_edifact_interchange(
992 &InterchangeEnvelope {
993 sender: EdifactParty::bdew("9900000000003"),
994 receiver: EdifactParty::bdew("9900000000001"),
995 interchange_ref: "REF001".to_string(),
996 },
997 &[InterchangeMessage {
998 message_ref: "MSG001".to_string(),
999 msg_stammdaten: serde_json::json!({
1000 "marktteilnehmer": [{
1001 "marktrolle": "MS",
1002 "rollencodenummer": "9900123456789",
1003 "codepflegeCode": "293"
1004 }]
1005 }),
1006 tx_stammdaten: vec![serde_json::json!({
1007 "prozessdaten": {
1008 "pruefidentifikator": "55001",
1009 "vorgangId": "ABC123",
1010 "transaktionsgrund": "E01"
1011 }
1012 })],
1013 fv: "FV2504".to_string(),
1014 variant: "UTILMD_Strom".to_string(),
1015 pid: "55001".to_string(),
1016 }],
1017 );
1018 assert!(
1019 result.is_ok(),
1020 "to_edifact_interchange failed: {:?}",
1021 result.err()
1022 );
1023 let edifact = result.unwrap();
1024
1025 assert!(edifact.starts_with("UNA:+.? '"), "Should start with UNA");
1027 assert!(edifact.contains("UNB+UNOC:3+9900000000003:500+9900000000001:500+"),
1028 "Should contain UNB with sender/receiver");
1029 assert!(edifact.contains("UNH+MSG001+UTILMD:D:11A:UN:S2.1'"),
1030 "Should contain UNH with correct S009");
1031 assert!(edifact.contains("NAD"), "Should contain body NAD segment");
1032 assert!(edifact.contains("UNT+"), "Should contain UNT");
1033 assert!(edifact.contains("+MSG001'"), "UNT should reference message ref");
1034 assert!(edifact.contains("UNZ+1+REF001'"), "Should contain UNZ with count and ref");
1035 }
1036
1037 #[test]
1038 fn test_detect_pid_from_rff_z13() {
1039 let Some(data_dir) = data_dir() else {
1040 return;
1041 };
1042 let mapper =
1043 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1044
1045 let edifact = "\
1046 UNB+UNOC:3+9978842000002:500+9900269000000:500+250331:1329+REF001'\
1047 UNH+MSG001+UTILMD:D:11A:UN:S2.1'\
1048 BGM+E01+DOC001'\
1049 DTM+137:202503311329?+00:303'\
1050 NAD+MS+9978842000002::293'\
1051 NAD+MR+9900269000000::293'\
1052 IDE+24+TX001'\
1053 DTM+92:202505312200?+00:303'\
1054 DTM+93:202512312300?+00:303'\
1055 STS+7++E01+ZW4+E03'\
1056 LOC+Z16+12345678900'\
1057 RFF+Z13:55001'\
1058 UNT+12+MSG001'\
1059 UNZ+1+REF001'";
1060
1061 let pid = mapper.detect_pid(edifact).unwrap();
1062 assert_eq!(pid, "55001");
1063 }
1064
1065 #[test]
1066 fn test_detect_pid_no_messages_returns_error() {
1067 let Some(data_dir) = data_dir() else {
1068 return;
1069 };
1070 let mapper =
1071 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1072
1073 let edifact = "UNB+UNOC:3+SENDER:500+RECEIVER:500+250401:1200+REF'\
1074 UNZ+0+REF'";
1075 assert!(mapper.detect_pid(edifact).is_err());
1076 }
1077}