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 DynamicInterchange, DynamicNachricht, Interchange, Interchangedaten, Nachricht,
17 Nachrichtendaten,
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(skip)]
38 pub nesting_info: HashMap<String, Vec<usize>>,
39
40 #[serde(skip)]
50 pub dp_routing: HashMap<String, Vec<serde_json::Map<String, serde_json::Value>>>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct MappedMessage {
60 pub stammdaten: serde_json::Value,
62
63 pub transaktionen: Vec<MappedTransaktion>,
65
66 #[serde(skip)]
72 pub nesting_info: HashMap<String, Vec<usize>>,
73
74 #[serde(skip)]
78 pub dp_routing: HashMap<String, Vec<serde_json::Map<String, serde_json::Value>>>,
79
80 #[serde(skip)]
90 pub inter_group_segments: BTreeMap<usize, Vec<AssembledSegment>>,
91}
92
93impl MappedMessage {
94 pub fn into_dynamic_nachricht(self, nachrichtendaten: Nachrichtendaten) -> DynamicNachricht {
99 Nachricht {
100 nachrichtendaten,
101 stammdaten: self.stammdaten,
102 transaktionen: self
103 .transaktionen
104 .into_iter()
105 .map(|t| t.stammdaten)
106 .collect(),
107 }
108 }
109}
110
111pub fn extract_unh_fields(unh: &OwnedSegment) -> (String, String) {
113 let referenz = unh.get_element(0).to_string();
114 let typ = unh.get_component(1, 0).to_string();
115 (referenz, typ)
116}
117
118pub fn extract_interchangedaten(envelope: &[OwnedSegment]) -> Interchangedaten {
120 let mut result = Interchangedaten::default();
121
122 for seg in envelope {
123 if seg.is("UNB") {
124 let val = |s: &str| {
125 if s.is_empty() {
126 None
127 } else {
128 Some(s.to_string())
129 }
130 };
131 result.syntax_kennung = val(seg.get_component(0, 0));
132 result.absender_code = val(seg.get_component(1, 0));
133 result.empfaenger_code = val(seg.get_component(2, 0));
134 result.datum = val(seg.get_component(3, 0));
135 result.zeit = val(seg.get_component(3, 1));
136 result.interchange_ref = val(seg.get_element(4));
137 }
138 }
139
140 result
141}
142
143pub fn extract_nachrichtendaten(envelope: &[OwnedSegment]) -> serde_json::Value {
147 let data = extract_interchangedaten(envelope);
148 serde_json::to_value(&data).unwrap_or_default()
149}
150
151fn normalize_unb_datum(datum: &str) -> &str {
156 if datum.len() == 8 && datum.as_bytes().iter().all(|b| b.is_ascii_digit()) {
157 &datum[2..]
158 } else {
159 datum
160 }
161}
162
163pub fn rebuild_unb_from_interchangedaten(data: &Interchangedaten) -> OwnedSegment {
169 let syntax = data.syntax_kennung.as_deref().unwrap_or("UNOC");
170 let sender = data.absender_code.as_deref().unwrap_or("");
171 let receiver = data.empfaenger_code.as_deref().unwrap_or("");
172 let datum = normalize_unb_datum(data.datum.as_deref().unwrap_or(""));
173 let zeit = data.zeit.as_deref().unwrap_or("");
174 let interchange_ref = data.interchange_ref.as_deref().unwrap_or("00000");
175
176 OwnedSegment {
177 id: "UNB".to_string(),
178 elements: vec![
179 vec![syntax.to_string(), "3".to_string()],
180 vec![sender.to_string(), "500".to_string()],
181 vec![receiver.to_string(), "500".to_string()],
182 vec![datum.to_string(), zeit.to_string()],
183 vec![interchange_ref.to_string()],
184 ],
185 segment_number: 0,
186 }
187}
188
189pub fn rebuild_unb(nachrichtendaten: &serde_json::Value) -> OwnedSegment {
195 let syntax = nachrichtendaten
196 .get("syntaxKennung")
197 .and_then(|v| v.as_str())
198 .unwrap_or("UNOC");
199 let sender = nachrichtendaten
200 .get("absenderCode")
201 .and_then(|v| v.as_str())
202 .unwrap_or("");
203 let receiver = nachrichtendaten
204 .get("empfaengerCode")
205 .and_then(|v| v.as_str())
206 .unwrap_or("");
207 let datum_raw = nachrichtendaten
208 .get("datum")
209 .and_then(|v| v.as_str())
210 .unwrap_or("");
211 let datum = normalize_unb_datum(datum_raw);
212 let zeit = nachrichtendaten
213 .get("zeit")
214 .and_then(|v| v.as_str())
215 .unwrap_or("");
216 let interchange_ref = nachrichtendaten
217 .get("interchangeRef")
218 .and_then(|v| v.as_str())
219 .unwrap_or("00000");
220
221 OwnedSegment {
222 id: "UNB".to_string(),
223 elements: vec![
224 vec![syntax.to_string(), "3".to_string()],
225 vec![sender.to_string(), "500".to_string()],
226 vec![receiver.to_string(), "500".to_string()],
227 vec![datum.to_string(), zeit.to_string()],
228 vec![interchange_ref.to_string()],
229 ],
230 segment_number: 0,
231 }
232}
233
234pub fn rebuild_unh(
245 referenz: &str,
246 nachrichten_typ: &str,
247 release: &str,
248 association: &str,
249) -> OwnedSegment {
250 OwnedSegment {
251 id: "UNH".to_string(),
252 elements: vec![
253 vec![referenz.to_string()],
254 vec![
255 nachrichten_typ.to_string(),
256 "D".to_string(),
257 release.to_string(),
258 "UN".to_string(),
259 association.to_string(),
260 ],
261 ],
262 segment_number: 0,
263 }
264}
265
266pub fn release_code_for_message_type(msg_type: &str) -> &'static str {
273 match msg_type {
274 "APERAK" => "07B",
275 "COMDIS" => "17A",
276 "CONTRL" => "3",
278 "IFTSTA" => "18A",
279 "INSRPT" => "10A",
280 "INVOIC" => "06A",
281 "MSCONS" => "04B",
282 "ORDCHG" => "20B",
283 "ORDERS" => "09B",
284 "ORDRSP" => "10A",
285 "PARTIN" => "20B",
286 "PRICAT" => "20B",
287 "QUOTES" => "10A",
288 "REMADV" => "05A",
289 "REQOTE" => "10A",
290 "UTILMD" => "11A",
291 "UTILTS" => "18A",
292 _ => "04B", }
294}
295
296pub fn rebuild_unt(segment_count: usize, referenz: &str) -> OwnedSegment {
301 OwnedSegment {
302 id: "UNT".to_string(),
303 elements: vec![vec![segment_count.to_string()], vec![referenz.to_string()]],
304 segment_number: 0,
305 }
306}
307
308pub fn rebuild_unz(message_count: usize, interchange_ref: &str) -> OwnedSegment {
312 OwnedSegment {
313 id: "UNZ".to_string(),
314 elements: vec![
315 vec![message_count.to_string()],
316 vec![interchange_ref.to_string()],
317 ],
318 segment_number: 0,
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_mapped_transaktion_serde_roundtrip() {
328 let tx = MappedTransaktion {
329 stammdaten: serde_json::json!({
330 "prozessdaten": {
331 "vorgangId": "TX001",
332 "transaktionsgrund": "E01"
333 },
334 "marktlokation": { "marktlokationsId": "DE000111222333" }
335 }),
336 nesting_info: Default::default(),
337 dp_routing: Default::default(),
338 };
339
340 let json = serde_json::to_string(&tx).unwrap();
341 let de: MappedTransaktion = serde_json::from_str(&json).unwrap();
342 assert_eq!(
343 de.stammdaten["prozessdaten"]["vorgangId"].as_str().unwrap(),
344 "TX001"
345 );
346 assert!(de.stammdaten["marktlokation"].is_object());
347 }
348
349 #[test]
350 fn test_dynamic_nachricht_serde_roundtrip() {
351 let msg: DynamicNachricht = Nachricht {
352 nachrichtendaten: Nachrichtendaten {
353 unh_referenz: "00001".to_string(),
354 nachrichten_typ: "UTILMD".to_string(),
355 },
356 stammdaten: serde_json::json!({
357 "marktteilnehmer": [
358 { "marktrolle": "MS", "rollencodenummer": "9900123" }
359 ]
360 }),
361 transaktionen: vec![serde_json::json!({})],
362 };
363
364 let json = serde_json::to_string(&msg).unwrap();
365 let de: DynamicNachricht = serde_json::from_str(&json).unwrap();
366 assert_eq!(de.nachrichtendaten.unh_referenz, "00001");
367 assert_eq!(de.nachrichtendaten.nachrichten_typ, "UTILMD");
368 assert_eq!(de.transaktionen.len(), 1);
369 }
370
371 #[test]
372 fn test_dynamic_interchange_serde_roundtrip() {
373 let interchange: DynamicInterchange = Interchange {
374 interchangedaten: Interchangedaten {
375 absender_code: Some("9900123456789".to_string()),
376 empfaenger_code: Some("9900987654321".to_string()),
377 ..Default::default()
378 },
379 nachrichten: vec![Nachricht {
380 nachrichtendaten: Nachrichtendaten {
381 unh_referenz: "00001".to_string(),
382 nachrichten_typ: "UTILMD".to_string(),
383 },
384 stammdaten: serde_json::json!({}),
385 transaktionen: vec![],
386 }],
387 };
388
389 let json = serde_json::to_string_pretty(&interchange).unwrap();
390 let de: DynamicInterchange = serde_json::from_str(&json).unwrap();
391 assert_eq!(de.nachrichten.len(), 1);
392 assert_eq!(de.nachrichten[0].nachrichtendaten.unh_referenz, "00001");
393 }
394
395 #[test]
396 fn test_extract_interchangedaten_from_segments() {
397 let envelope = vec![OwnedSegment {
398 id: "UNB".to_string(),
399 elements: vec![
400 vec!["UNOC".to_string(), "3".to_string()],
401 vec!["9900123456789".to_string(), "500".to_string()],
402 vec!["9900987654321".to_string(), "500".to_string()],
403 vec!["210101".to_string(), "1200".to_string()],
404 vec!["REF001".to_string()],
405 ],
406 segment_number: 0,
407 }];
408
409 let data = extract_interchangedaten(&envelope);
410 assert_eq!(data.absender_code.as_deref(), Some("9900123456789"));
411 assert_eq!(data.empfaenger_code.as_deref(), Some("9900987654321"));
412 assert_eq!(data.interchange_ref.as_deref(), Some("REF001"));
413 assert_eq!(data.syntax_kennung.as_deref(), Some("UNOC"));
414 assert_eq!(data.datum.as_deref(), Some("210101"));
415 assert_eq!(data.zeit.as_deref(), Some("1200"));
416 }
417
418 #[test]
419 fn test_extract_envelope_from_segments_json() {
420 let envelope = vec![OwnedSegment {
421 id: "UNB".to_string(),
422 elements: vec![
423 vec!["UNOC".to_string(), "3".to_string()],
424 vec!["9900123456789".to_string(), "500".to_string()],
425 vec!["9900987654321".to_string(), "500".to_string()],
426 vec!["210101".to_string(), "1200".to_string()],
427 vec!["REF001".to_string()],
428 ],
429 segment_number: 0,
430 }];
431
432 let nd = extract_nachrichtendaten(&envelope);
433 assert_eq!(nd["absenderCode"].as_str().unwrap(), "9900123456789");
434 assert_eq!(nd["empfaengerCode"].as_str().unwrap(), "9900987654321");
435 assert_eq!(nd["interchangeRef"].as_str().unwrap(), "REF001");
436 assert_eq!(nd["syntaxKennung"].as_str().unwrap(), "UNOC");
437 assert_eq!(nd["datum"].as_str().unwrap(), "210101");
438 assert_eq!(nd["zeit"].as_str().unwrap(), "1200");
439 }
440
441 #[test]
442 fn test_extract_unh_fields() {
443 let unh = OwnedSegment {
444 id: "UNH".to_string(),
445 elements: vec![
446 vec!["MSG001".to_string()],
447 vec![
448 "UTILMD".to_string(),
449 "D".to_string(),
450 "11A".to_string(),
451 "UN".to_string(),
452 "S2.1".to_string(),
453 ],
454 ],
455 segment_number: 0,
456 };
457
458 let (referenz, typ) = extract_unh_fields(&unh);
459 assert_eq!(referenz, "MSG001");
460 assert_eq!(typ, "UTILMD");
461 }
462
463 #[test]
464 fn test_rebuild_unb_from_interchangedaten_typed() {
465 let data = Interchangedaten {
466 syntax_kennung: Some("UNOC".to_string()),
467 absender_code: Some("9900123456789".to_string()),
468 empfaenger_code: Some("9900987654321".to_string()),
469 datum: Some("210101".to_string()),
470 zeit: Some("1200".to_string()),
471 interchange_ref: Some("REF001".to_string()),
472 };
473
474 let unb = rebuild_unb_from_interchangedaten(&data);
475 assert_eq!(unb.id, "UNB");
476 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
477 assert_eq!(unb.elements[1][0], "9900123456789");
478 assert_eq!(unb.elements[2][0], "9900987654321");
479 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
480 assert_eq!(unb.elements[4], vec!["REF001"]);
481 }
482
483 #[test]
484 fn test_rebuild_unb_from_nachrichtendaten() {
485 let nd = serde_json::json!({
486 "syntaxKennung": "UNOC",
487 "absenderCode": "9900123456789",
488 "empfaengerCode": "9900987654321",
489 "datum": "210101",
490 "zeit": "1200",
491 "interchangeRef": "REF001"
492 });
493
494 let unb = rebuild_unb(&nd);
495 assert_eq!(unb.id, "UNB");
496 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
497 assert_eq!(unb.elements[1][0], "9900123456789");
498 assert_eq!(unb.elements[2][0], "9900987654321");
499 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
500 assert_eq!(unb.elements[4], vec!["REF001"]);
501 }
502
503 #[test]
504 fn test_rebuild_unb_defaults() {
505 let nd = serde_json::json!({});
506 let unb = rebuild_unb(&nd);
507 assert_eq!(unb.id, "UNB");
508 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
509 }
510
511 #[test]
512 fn test_rebuild_unh() {
513 let unh = rebuild_unh("00001", "UTILMD", "11A", "S2.1");
514 assert_eq!(unh.id, "UNH");
515 assert_eq!(unh.elements[0], vec!["00001"]);
516 assert_eq!(unh.elements[1][0], "UTILMD");
517 assert_eq!(unh.elements[1][1], "D");
518 assert_eq!(unh.elements[1][2], "11A");
519 assert_eq!(unh.elements[1][3], "UN");
520 assert_eq!(unh.elements[1][4], "S2.1");
521 }
522
523 #[test]
524 fn test_rebuild_unh_uses_the_given_release_and_association() {
525 let unh = rebuild_unh("00001", "UTILMD", "11A", "G1.0a");
528 assert_eq!(unh.elements[1], vec!["UTILMD", "D", "11A", "UN", "G1.0a"]);
529
530 let unh = rebuild_unh(
531 "00001",
532 "MSCONS",
533 release_code_for_message_type("MSCONS"),
534 "2.4c",
535 );
536 assert_eq!(unh.elements[1], vec!["MSCONS", "D", "04B", "UN", "2.4c"]);
537 }
538
539 #[test]
540 fn test_rebuild_unt() {
541 let unt = rebuild_unt(25, "00001");
542 assert_eq!(unt.id, "UNT");
543 assert_eq!(unt.elements[0], vec!["25"]);
544 assert_eq!(unt.elements[1], vec!["00001"]);
545 }
546
547 #[test]
548 fn test_rebuild_unz() {
549 let unz = rebuild_unz(1, "REF001");
550 assert_eq!(unz.id, "UNZ");
551 assert_eq!(unz.elements[0], vec!["1"]);
552 assert_eq!(unz.elements[1], vec!["REF001"]);
553 }
554
555 #[test]
556 fn test_roundtrip_interchangedaten_rebuild() {
557 let original = OwnedSegment {
558 id: "UNB".to_string(),
559 elements: vec![
560 vec!["UNOC".to_string(), "3".to_string()],
561 vec!["9900123456789".to_string(), "500".to_string()],
562 vec!["9900987654321".to_string(), "500".to_string()],
563 vec!["210101".to_string(), "1200".to_string()],
564 vec!["REF001".to_string()],
565 ],
566 segment_number: 0,
567 };
568
569 let data = extract_interchangedaten(&[original]);
570 let rebuilt = rebuild_unb_from_interchangedaten(&data);
571 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
572 assert_eq!(rebuilt.elements[1][0], "9900123456789");
573 assert_eq!(rebuilt.elements[2][0], "9900987654321");
574 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
575 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
576 }
577
578 #[test]
579 fn test_roundtrip_nachrichtendaten_rebuild() {
580 let original = OwnedSegment {
581 id: "UNB".to_string(),
582 elements: vec![
583 vec!["UNOC".to_string(), "3".to_string()],
584 vec!["9900123456789".to_string(), "500".to_string()],
585 vec!["9900987654321".to_string(), "500".to_string()],
586 vec!["210101".to_string(), "1200".to_string()],
587 vec!["REF001".to_string()],
588 ],
589 segment_number: 0,
590 };
591
592 let nd = extract_nachrichtendaten(&[original]);
593 let rebuilt = rebuild_unb(&nd);
594 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
595 assert_eq!(rebuilt.elements[1][0], "9900123456789");
596 assert_eq!(rebuilt.elements[2][0], "9900987654321");
597 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
598 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
599 }
600
601 #[test]
602 fn test_rebuild_unb_normalizes_ccyymmdd_to_yymmdd() {
603 let data = Interchangedaten {
605 syntax_kennung: Some("UNOC".to_string()),
606 absender_code: Some("9900000000003".to_string()),
607 empfaenger_code: Some("9900000000001".to_string()),
608 datum: Some("20260409".to_string()), zeit: Some("0725".to_string()),
610 interchange_ref: Some("00004".to_string()),
611 };
612
613 let unb = rebuild_unb_from_interchangedaten(&data);
614 assert_eq!(unb.elements[3], vec!["260409", "0725"]); let nd = serde_json::json!({
618 "syntaxKennung": "UNOC",
619 "absenderCode": "9900000000003",
620 "empfaengerCode": "9900000000001",
621 "datum": "20260409",
622 "zeit": "0725",
623 "interchangeRef": "00004"
624 });
625 let unb_json = rebuild_unb(&nd);
626 assert_eq!(unb_json.elements[3], vec!["260409", "0725"]);
627 }
628
629 #[test]
630 fn test_rebuild_unb_preserves_yymmdd() {
631 let data = Interchangedaten {
633 datum: Some("260409".to_string()),
634 zeit: Some("0725".to_string()),
635 ..Default::default()
636 };
637 let unb = rebuild_unb_from_interchangedaten(&data);
638 assert_eq!(unb.elements[3], vec!["260409", "0725"]);
639 }
640
641 #[test]
642 fn test_into_dynamic_nachricht() {
643 let mapped = MappedMessage {
644 stammdaten: serde_json::json!({"marktteilnehmer": []}),
645 transaktionen: vec![MappedTransaktion {
646 stammdaten: serde_json::json!({"prozessdaten": {"id": "1"}}),
647 nesting_info: Default::default(),
648 dp_routing: Default::default(),
649 }],
650 nesting_info: Default::default(),
651 dp_routing: Default::default(),
652 inter_group_segments: Default::default(),
653 };
654
655 let nd = Nachrichtendaten {
656 unh_referenz: "00001".to_string(),
657 nachrichten_typ: "UTILMD".to_string(),
658 };
659
660 let nachricht = mapped.into_dynamic_nachricht(nd);
661 assert_eq!(nachricht.nachrichtendaten.unh_referenz, "00001");
662 assert_eq!(nachricht.transaktionen.len(), 1);
663 assert!(nachricht.transaktionen[0]["prozessdaten"].is_object());
664 }
665}