1use mig_assembly::assembler::AssembledSegment;
10use mig_types::segment::OwnedSegment;
11use serde::{Deserialize, Serialize};
12use std::collections::{BTreeMap, HashMap};
13
14pub use bo4e_edifact_types::{
16 is_wrapped_transaktion, DynamicInterchange, DynamicNachricht, DynamicTransaktion, Interchange,
17 Interchangedaten, Nachricht, Nachrichtendaten, Transaktion,
18};
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct MappedTransaktion {
27 pub stammdaten: serde_json::Value,
30
31 #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
38 pub transaktionsdaten: serde_json::Value,
39
40 #[serde(skip)]
47 pub nesting_info: HashMap<String, Vec<usize>>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct MappedMessage {
57 pub stammdaten: serde_json::Value,
59
60 #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
66 pub nachricht_meta: serde_json::Value,
67
68 pub transaktionen: Vec<MappedTransaktion>,
70
71 #[serde(skip)]
77 pub nesting_info: HashMap<String, Vec<usize>>,
78
79 #[serde(skip)]
89 pub inter_group_segments: BTreeMap<usize, Vec<AssembledSegment>>,
90}
91
92impl MappedMessage {
93 pub fn into_dynamic_nachricht(self, nachrichtendaten: Nachrichtendaten) -> DynamicNachricht {
98 let mut nachrichtendaten = nachrichtendaten;
102 if let serde_json::Value::Object(fields) = self.nachricht_meta {
103 nachrichtendaten.nachricht = fields;
104 }
105
106 Nachricht {
107 nachrichtendaten,
108 stammdaten: self.stammdaten,
109 transaktionen: self
110 .transaktionen
111 .into_iter()
112 .map(|t| Transaktion {
113 transaktionsdaten: t.transaktionsdaten,
114 stammdaten: t.stammdaten,
115 })
116 .collect(),
117 }
118 }
119}
120
121pub const TX_METADATA_ENTITY: &str = "prozessdaten";
128
129pub const MSG_METADATA_ENTITY: &str = "nachricht";
131
132pub fn take_entity(from: &mut serde_json::Value, key: &str) -> serde_json::Value {
134 from.as_object_mut()
135 .and_then(|m| m.remove(key))
136 .unwrap_or(serde_json::Value::Null)
137}
138
139pub fn restore_entity(into: &mut serde_json::Value, key: &str, value: &serde_json::Value) {
144 if value.is_null() {
145 return;
146 }
147 if let Some(m) = into.as_object_mut() {
148 m.insert(key.to_string(), value.clone());
149 }
150}
151
152pub fn restore_message_metadata(msg_stammdaten: &mut serde_json::Value, nd: &Nachrichtendaten) {
159 if nd.nachricht.is_empty() {
160 return;
161 }
162 let value = serde_json::Value::Object(nd.nachricht.clone());
163 restore_entity(msg_stammdaten, MSG_METADATA_ENTITY, &value);
164}
165
166pub fn extract_unh_fields(unh: &OwnedSegment) -> (String, String) {
168 let referenz = unh.get_element(0).to_string();
169 let typ = unh.get_component(1, 0).to_string();
170 (referenz, typ)
171}
172
173pub fn extract_interchangedaten(envelope: &[OwnedSegment]) -> Interchangedaten {
175 let mut result = Interchangedaten::default();
176
177 for seg in envelope {
178 if seg.is("UNB") {
179 let val = |s: &str| {
180 if s.is_empty() {
181 None
182 } else {
183 Some(s.to_string())
184 }
185 };
186 result.syntax_kennung = val(seg.get_component(0, 0));
187 result.absender_code = val(seg.get_component(1, 0));
188 result.empfaenger_code = val(seg.get_component(2, 0));
189 result.datum = val(seg.get_component(3, 0));
190 result.zeit = val(seg.get_component(3, 1));
191 result.interchange_ref = val(seg.get_element(4));
192 }
193 }
194
195 result
196}
197
198pub fn extract_nachrichtendaten(envelope: &[OwnedSegment]) -> serde_json::Value {
202 let data = extract_interchangedaten(envelope);
203 serde_json::to_value(&data).unwrap_or_default()
204}
205
206fn normalize_unb_datum(datum: &str) -> &str {
211 if datum.len() == 8 && datum.as_bytes().iter().all(|b| b.is_ascii_digit()) {
212 &datum[2..]
213 } else {
214 datum
215 }
216}
217
218pub fn rebuild_unb_from_interchangedaten(data: &Interchangedaten) -> OwnedSegment {
224 let syntax = data.syntax_kennung.as_deref().unwrap_or("UNOC");
225 let sender = data.absender_code.as_deref().unwrap_or("");
226 let receiver = data.empfaenger_code.as_deref().unwrap_or("");
227 let datum = normalize_unb_datum(data.datum.as_deref().unwrap_or(""));
228 let zeit = data.zeit.as_deref().unwrap_or("");
229 let interchange_ref = data.interchange_ref.as_deref().unwrap_or("00000");
230
231 OwnedSegment {
232 id: "UNB".to_string(),
233 elements: vec![
234 vec![syntax.to_string(), "3".to_string()],
235 vec![sender.to_string(), "500".to_string()],
236 vec![receiver.to_string(), "500".to_string()],
237 vec![datum.to_string(), zeit.to_string()],
238 vec![interchange_ref.to_string()],
239 ],
240 segment_number: 0,
241 }
242}
243
244pub fn rebuild_unb(nachrichtendaten: &serde_json::Value) -> OwnedSegment {
250 let syntax = nachrichtendaten
251 .get("syntaxKennung")
252 .and_then(|v| v.as_str())
253 .unwrap_or("UNOC");
254 let sender = nachrichtendaten
255 .get("absenderCode")
256 .and_then(|v| v.as_str())
257 .unwrap_or("");
258 let receiver = nachrichtendaten
259 .get("empfaengerCode")
260 .and_then(|v| v.as_str())
261 .unwrap_or("");
262 let datum_raw = nachrichtendaten
263 .get("datum")
264 .and_then(|v| v.as_str())
265 .unwrap_or("");
266 let datum = normalize_unb_datum(datum_raw);
267 let zeit = nachrichtendaten
268 .get("zeit")
269 .and_then(|v| v.as_str())
270 .unwrap_or("");
271 let interchange_ref = nachrichtendaten
272 .get("interchangeRef")
273 .and_then(|v| v.as_str())
274 .unwrap_or("00000");
275
276 OwnedSegment {
277 id: "UNB".to_string(),
278 elements: vec![
279 vec![syntax.to_string(), "3".to_string()],
280 vec![sender.to_string(), "500".to_string()],
281 vec![receiver.to_string(), "500".to_string()],
282 vec![datum.to_string(), zeit.to_string()],
283 vec![interchange_ref.to_string()],
284 ],
285 segment_number: 0,
286 }
287}
288
289pub fn rebuild_unh(
300 referenz: &str,
301 nachrichten_typ: &str,
302 release: &str,
303 association: &str,
304) -> OwnedSegment {
305 OwnedSegment {
306 id: "UNH".to_string(),
307 elements: vec![
308 vec![referenz.to_string()],
309 vec![
310 nachrichten_typ.to_string(),
311 "D".to_string(),
312 release.to_string(),
313 "UN".to_string(),
314 association.to_string(),
315 ],
316 ],
317 segment_number: 0,
318 }
319}
320
321pub fn release_code_for_message_type(msg_type: &str) -> &'static str {
328 match msg_type {
329 "APERAK" => "07B",
330 "COMDIS" => "17A",
331 "CONTRL" => "3",
333 "IFTSTA" => "18A",
334 "INSRPT" => "10A",
335 "INVOIC" => "06A",
336 "MSCONS" => "04B",
337 "ORDCHG" => "20B",
338 "ORDERS" => "09B",
339 "ORDRSP" => "10A",
340 "PARTIN" => "20B",
341 "PRICAT" => "20B",
342 "QUOTES" => "10A",
343 "REMADV" => "05A",
344 "REQOTE" => "10A",
345 "UTILMD" => "11A",
346 "UTILTS" => "18A",
347 _ => "04B", }
349}
350
351pub fn rebuild_unt(segment_count: usize, referenz: &str) -> OwnedSegment {
356 OwnedSegment {
357 id: "UNT".to_string(),
358 elements: vec![vec![segment_count.to_string()], vec![referenz.to_string()]],
359 segment_number: 0,
360 }
361}
362
363pub fn rebuild_unz(message_count: usize, interchange_ref: &str) -> OwnedSegment {
367 OwnedSegment {
368 id: "UNZ".to_string(),
369 elements: vec![
370 vec![message_count.to_string()],
371 vec![interchange_ref.to_string()],
372 ],
373 segment_number: 0,
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[test]
382 fn test_mapped_transaktion_serde_roundtrip() {
383 let tx = MappedTransaktion {
384 transaktionsdaten: serde_json::Value::Null,
385 stammdaten: serde_json::json!({
386 "prozessdaten": {
387 "vorgangId": "TX001",
388 "transaktionsgrund": "E01"
389 },
390 "marktlokation": { "marktlokationsId": "DE000111222333" }
391 }),
392 nesting_info: Default::default(),
393 };
394
395 let json = serde_json::to_string(&tx).unwrap();
396 let de: MappedTransaktion = serde_json::from_str(&json).unwrap();
397 assert_eq!(
398 de.stammdaten["prozessdaten"]["vorgangId"].as_str().unwrap(),
399 "TX001"
400 );
401 assert!(de.stammdaten["marktlokation"].is_object());
402 }
403
404 #[test]
405 fn test_dynamic_nachricht_serde_roundtrip() {
406 let msg: DynamicNachricht = Nachricht {
407 nachrichtendaten: Nachrichtendaten {
408 unh_referenz: "00001".to_string(),
409 nachrichten_typ: "UTILMD".to_string(),
410 nachricht: Default::default(),
411 },
412 stammdaten: serde_json::json!({
413 "marktteilnehmer": [
414 { "marktrolle": "MS", "rollencodenummer": "9900123" }
415 ]
416 }),
417 transaktionen: vec![Transaktion {
418 transaktionsdaten: serde_json::Value::Null,
419 stammdaten: serde_json::json!({}),
420 }],
421 };
422
423 let json = serde_json::to_string(&msg).unwrap();
424 let de: DynamicNachricht = serde_json::from_str(&json).unwrap();
425 assert_eq!(de.nachrichtendaten.unh_referenz, "00001");
426 assert_eq!(de.nachrichtendaten.nachrichten_typ, "UTILMD");
427 assert_eq!(de.transaktionen.len(), 1);
428 }
429
430 #[test]
431 fn test_dynamic_interchange_serde_roundtrip() {
432 let interchange: DynamicInterchange = Interchange {
433 interchangedaten: Interchangedaten {
434 absender_code: Some("9900123456789".to_string()),
435 empfaenger_code: Some("9900987654321".to_string()),
436 ..Default::default()
437 },
438 nachrichten: vec![Nachricht {
439 nachrichtendaten: Nachrichtendaten {
440 unh_referenz: "00001".to_string(),
441 nachrichten_typ: "UTILMD".to_string(),
442 nachricht: Default::default(),
443 },
444 stammdaten: serde_json::json!({}),
445 transaktionen: vec![],
446 }],
447 };
448
449 let json = serde_json::to_string_pretty(&interchange).unwrap();
450 let de: DynamicInterchange = serde_json::from_str(&json).unwrap();
451 assert_eq!(de.nachrichten.len(), 1);
452 assert_eq!(de.nachrichten[0].nachrichtendaten.unh_referenz, "00001");
453 }
454
455 #[test]
456 fn test_extract_interchangedaten_from_segments() {
457 let envelope = vec![OwnedSegment {
458 id: "UNB".to_string(),
459 elements: vec![
460 vec!["UNOC".to_string(), "3".to_string()],
461 vec!["9900123456789".to_string(), "500".to_string()],
462 vec!["9900987654321".to_string(), "500".to_string()],
463 vec!["210101".to_string(), "1200".to_string()],
464 vec!["REF001".to_string()],
465 ],
466 segment_number: 0,
467 }];
468
469 let data = extract_interchangedaten(&envelope);
470 assert_eq!(data.absender_code.as_deref(), Some("9900123456789"));
471 assert_eq!(data.empfaenger_code.as_deref(), Some("9900987654321"));
472 assert_eq!(data.interchange_ref.as_deref(), Some("REF001"));
473 assert_eq!(data.syntax_kennung.as_deref(), Some("UNOC"));
474 assert_eq!(data.datum.as_deref(), Some("210101"));
475 assert_eq!(data.zeit.as_deref(), Some("1200"));
476 }
477
478 #[test]
479 fn test_extract_envelope_from_segments_json() {
480 let envelope = vec![OwnedSegment {
481 id: "UNB".to_string(),
482 elements: vec![
483 vec!["UNOC".to_string(), "3".to_string()],
484 vec!["9900123456789".to_string(), "500".to_string()],
485 vec!["9900987654321".to_string(), "500".to_string()],
486 vec!["210101".to_string(), "1200".to_string()],
487 vec!["REF001".to_string()],
488 ],
489 segment_number: 0,
490 }];
491
492 let nd = extract_nachrichtendaten(&envelope);
493 assert_eq!(nd["absenderCode"].as_str().unwrap(), "9900123456789");
494 assert_eq!(nd["empfaengerCode"].as_str().unwrap(), "9900987654321");
495 assert_eq!(nd["interchangeRef"].as_str().unwrap(), "REF001");
496 assert_eq!(nd["syntaxKennung"].as_str().unwrap(), "UNOC");
497 assert_eq!(nd["datum"].as_str().unwrap(), "210101");
498 assert_eq!(nd["zeit"].as_str().unwrap(), "1200");
499 }
500
501 #[test]
502 fn test_extract_unh_fields() {
503 let unh = OwnedSegment {
504 id: "UNH".to_string(),
505 elements: vec![
506 vec!["MSG001".to_string()],
507 vec![
508 "UTILMD".to_string(),
509 "D".to_string(),
510 "11A".to_string(),
511 "UN".to_string(),
512 "S2.1".to_string(),
513 ],
514 ],
515 segment_number: 0,
516 };
517
518 let (referenz, typ) = extract_unh_fields(&unh);
519 assert_eq!(referenz, "MSG001");
520 assert_eq!(typ, "UTILMD");
521 }
522
523 #[test]
524 fn test_rebuild_unb_from_interchangedaten_typed() {
525 let data = Interchangedaten {
526 syntax_kennung: Some("UNOC".to_string()),
527 absender_code: Some("9900123456789".to_string()),
528 empfaenger_code: Some("9900987654321".to_string()),
529 datum: Some("210101".to_string()),
530 zeit: Some("1200".to_string()),
531 interchange_ref: Some("REF001".to_string()),
532 };
533
534 let unb = rebuild_unb_from_interchangedaten(&data);
535 assert_eq!(unb.id, "UNB");
536 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
537 assert_eq!(unb.elements[1][0], "9900123456789");
538 assert_eq!(unb.elements[2][0], "9900987654321");
539 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
540 assert_eq!(unb.elements[4], vec!["REF001"]);
541 }
542
543 #[test]
544 fn test_rebuild_unb_from_nachrichtendaten() {
545 let nd = serde_json::json!({
546 "syntaxKennung": "UNOC",
547 "absenderCode": "9900123456789",
548 "empfaengerCode": "9900987654321",
549 "datum": "210101",
550 "zeit": "1200",
551 "interchangeRef": "REF001"
552 });
553
554 let unb = rebuild_unb(&nd);
555 assert_eq!(unb.id, "UNB");
556 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
557 assert_eq!(unb.elements[1][0], "9900123456789");
558 assert_eq!(unb.elements[2][0], "9900987654321");
559 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
560 assert_eq!(unb.elements[4], vec!["REF001"]);
561 }
562
563 #[test]
564 fn test_rebuild_unb_defaults() {
565 let nd = serde_json::json!({});
566 let unb = rebuild_unb(&nd);
567 assert_eq!(unb.id, "UNB");
568 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
569 }
570
571 #[test]
572 fn test_rebuild_unh() {
573 let unh = rebuild_unh("00001", "UTILMD", "11A", "S2.1");
574 assert_eq!(unh.id, "UNH");
575 assert_eq!(unh.elements[0], vec!["00001"]);
576 assert_eq!(unh.elements[1][0], "UTILMD");
577 assert_eq!(unh.elements[1][1], "D");
578 assert_eq!(unh.elements[1][2], "11A");
579 assert_eq!(unh.elements[1][3], "UN");
580 assert_eq!(unh.elements[1][4], "S2.1");
581 }
582
583 #[test]
584 fn test_rebuild_unh_uses_the_given_release_and_association() {
585 let unh = rebuild_unh("00001", "UTILMD", "11A", "G1.0a");
588 assert_eq!(unh.elements[1], vec!["UTILMD", "D", "11A", "UN", "G1.0a"]);
589
590 let unh = rebuild_unh(
591 "00001",
592 "MSCONS",
593 release_code_for_message_type("MSCONS"),
594 "2.4c",
595 );
596 assert_eq!(unh.elements[1], vec!["MSCONS", "D", "04B", "UN", "2.4c"]);
597 }
598
599 #[test]
600 fn test_rebuild_unt() {
601 let unt = rebuild_unt(25, "00001");
602 assert_eq!(unt.id, "UNT");
603 assert_eq!(unt.elements[0], vec!["25"]);
604 assert_eq!(unt.elements[1], vec!["00001"]);
605 }
606
607 #[test]
608 fn test_rebuild_unz() {
609 let unz = rebuild_unz(1, "REF001");
610 assert_eq!(unz.id, "UNZ");
611 assert_eq!(unz.elements[0], vec!["1"]);
612 assert_eq!(unz.elements[1], vec!["REF001"]);
613 }
614
615 #[test]
616 fn test_roundtrip_interchangedaten_rebuild() {
617 let original = OwnedSegment {
618 id: "UNB".to_string(),
619 elements: vec![
620 vec!["UNOC".to_string(), "3".to_string()],
621 vec!["9900123456789".to_string(), "500".to_string()],
622 vec!["9900987654321".to_string(), "500".to_string()],
623 vec!["210101".to_string(), "1200".to_string()],
624 vec!["REF001".to_string()],
625 ],
626 segment_number: 0,
627 };
628
629 let data = extract_interchangedaten(&[original]);
630 let rebuilt = rebuild_unb_from_interchangedaten(&data);
631 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
632 assert_eq!(rebuilt.elements[1][0], "9900123456789");
633 assert_eq!(rebuilt.elements[2][0], "9900987654321");
634 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
635 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
636 }
637
638 #[test]
639 fn test_roundtrip_nachrichtendaten_rebuild() {
640 let original = OwnedSegment {
641 id: "UNB".to_string(),
642 elements: vec![
643 vec!["UNOC".to_string(), "3".to_string()],
644 vec!["9900123456789".to_string(), "500".to_string()],
645 vec!["9900987654321".to_string(), "500".to_string()],
646 vec!["210101".to_string(), "1200".to_string()],
647 vec!["REF001".to_string()],
648 ],
649 segment_number: 0,
650 };
651
652 let nd = extract_nachrichtendaten(&[original]);
653 let rebuilt = rebuild_unb(&nd);
654 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
655 assert_eq!(rebuilt.elements[1][0], "9900123456789");
656 assert_eq!(rebuilt.elements[2][0], "9900987654321");
657 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
658 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
659 }
660
661 #[test]
662 fn test_rebuild_unb_normalizes_ccyymmdd_to_yymmdd() {
663 let data = Interchangedaten {
665 syntax_kennung: Some("UNOC".to_string()),
666 absender_code: Some("9900000000003".to_string()),
667 empfaenger_code: Some("9900000000001".to_string()),
668 datum: Some("20260409".to_string()), zeit: Some("0725".to_string()),
670 interchange_ref: Some("00004".to_string()),
671 };
672
673 let unb = rebuild_unb_from_interchangedaten(&data);
674 assert_eq!(unb.elements[3], vec!["260409", "0725"]); let nd = serde_json::json!({
678 "syntaxKennung": "UNOC",
679 "absenderCode": "9900000000003",
680 "empfaengerCode": "9900000000001",
681 "datum": "20260409",
682 "zeit": "0725",
683 "interchangeRef": "00004"
684 });
685 let unb_json = rebuild_unb(&nd);
686 assert_eq!(unb_json.elements[3], vec!["260409", "0725"]);
687 }
688
689 #[test]
690 fn test_rebuild_unb_preserves_yymmdd() {
691 let data = Interchangedaten {
693 datum: Some("260409".to_string()),
694 zeit: Some("0725".to_string()),
695 ..Default::default()
696 };
697 let unb = rebuild_unb_from_interchangedaten(&data);
698 assert_eq!(unb.elements[3], vec!["260409", "0725"]);
699 }
700
701 #[test]
702 fn test_into_dynamic_nachricht() {
703 let mapped = MappedMessage {
704 nachricht_meta: serde_json::Value::Null,
705 stammdaten: serde_json::json!({"marktteilnehmer": []}),
706 transaktionen: vec![MappedTransaktion {
707 transaktionsdaten: serde_json::json!({"vorgangId": "1"}),
708 stammdaten: serde_json::json!({}),
709 nesting_info: Default::default(),
710 }],
711 nesting_info: Default::default(),
712 inter_group_segments: Default::default(),
713 };
714
715 let nd = Nachrichtendaten {
716 unh_referenz: "00001".to_string(),
717 nachrichten_typ: "UTILMD".to_string(),
718 nachricht: Default::default(),
719 };
720
721 let nachricht = mapped.into_dynamic_nachricht(nd);
722 assert_eq!(nachricht.nachrichtendaten.unh_referenz, "00001");
723 assert_eq!(nachricht.transaktionen.len(), 1);
724 assert_eq!(
726 nachricht.transaktionen[0].transaktionsdaten["vorgangId"],
727 "1"
728 );
729 }
730}