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 {
268 match msg_type {
269 "APERAK" => "07B",
270 "COMDIS" => "17A",
271 "CONTRL" => "04B",
272 "IFTSTA" => "18A",
273 "INSRPT" => "18A",
274 "INVOIC" => "06A",
275 "MSCONS" => "04B",
276 "ORDCHG" => "09B",
277 "ORDERS" => "09B",
278 "ORDRSP" => "10A",
279 "PARTIN" => "20B",
280 "PRICAT" => "20B",
281 "QUOTES" => "10A",
282 "REMADV" => "05A",
283 "REQOTE" => "10A",
284 "UTILMD" => "11A",
285 "UTILTS" => "18A",
286 _ => "04B", }
288}
289
290pub fn rebuild_unt(segment_count: usize, referenz: &str) -> OwnedSegment {
295 OwnedSegment {
296 id: "UNT".to_string(),
297 elements: vec![vec![segment_count.to_string()], vec![referenz.to_string()]],
298 segment_number: 0,
299 }
300}
301
302pub fn rebuild_unz(message_count: usize, interchange_ref: &str) -> OwnedSegment {
306 OwnedSegment {
307 id: "UNZ".to_string(),
308 elements: vec![
309 vec![message_count.to_string()],
310 vec![interchange_ref.to_string()],
311 ],
312 segment_number: 0,
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn test_mapped_transaktion_serde_roundtrip() {
322 let tx = MappedTransaktion {
323 stammdaten: serde_json::json!({
324 "prozessdaten": {
325 "vorgangId": "TX001",
326 "transaktionsgrund": "E01"
327 },
328 "marktlokation": { "marktlokationsId": "DE000111222333" }
329 }),
330 nesting_info: Default::default(),
331 dp_routing: Default::default(),
332 };
333
334 let json = serde_json::to_string(&tx).unwrap();
335 let de: MappedTransaktion = serde_json::from_str(&json).unwrap();
336 assert_eq!(
337 de.stammdaten["prozessdaten"]["vorgangId"].as_str().unwrap(),
338 "TX001"
339 );
340 assert!(de.stammdaten["marktlokation"].is_object());
341 }
342
343 #[test]
344 fn test_dynamic_nachricht_serde_roundtrip() {
345 let msg: DynamicNachricht = Nachricht {
346 nachrichtendaten: Nachrichtendaten {
347 unh_referenz: "00001".to_string(),
348 nachrichten_typ: "UTILMD".to_string(),
349 },
350 stammdaten: serde_json::json!({
351 "marktteilnehmer": [
352 { "marktrolle": "MS", "rollencodenummer": "9900123" }
353 ]
354 }),
355 transaktionen: vec![serde_json::json!({})],
356 };
357
358 let json = serde_json::to_string(&msg).unwrap();
359 let de: DynamicNachricht = serde_json::from_str(&json).unwrap();
360 assert_eq!(de.nachrichtendaten.unh_referenz, "00001");
361 assert_eq!(de.nachrichtendaten.nachrichten_typ, "UTILMD");
362 assert_eq!(de.transaktionen.len(), 1);
363 }
364
365 #[test]
366 fn test_dynamic_interchange_serde_roundtrip() {
367 let interchange: DynamicInterchange = Interchange {
368 interchangedaten: Interchangedaten {
369 absender_code: Some("9900123456789".to_string()),
370 empfaenger_code: Some("9900987654321".to_string()),
371 ..Default::default()
372 },
373 nachrichten: vec![Nachricht {
374 nachrichtendaten: Nachrichtendaten {
375 unh_referenz: "00001".to_string(),
376 nachrichten_typ: "UTILMD".to_string(),
377 },
378 stammdaten: serde_json::json!({}),
379 transaktionen: vec![],
380 }],
381 };
382
383 let json = serde_json::to_string_pretty(&interchange).unwrap();
384 let de: DynamicInterchange = serde_json::from_str(&json).unwrap();
385 assert_eq!(de.nachrichten.len(), 1);
386 assert_eq!(de.nachrichten[0].nachrichtendaten.unh_referenz, "00001");
387 }
388
389 #[test]
390 fn test_extract_interchangedaten_from_segments() {
391 let envelope = vec![OwnedSegment {
392 id: "UNB".to_string(),
393 elements: vec![
394 vec!["UNOC".to_string(), "3".to_string()],
395 vec!["9900123456789".to_string(), "500".to_string()],
396 vec!["9900987654321".to_string(), "500".to_string()],
397 vec!["210101".to_string(), "1200".to_string()],
398 vec!["REF001".to_string()],
399 ],
400 segment_number: 0,
401 }];
402
403 let data = extract_interchangedaten(&envelope);
404 assert_eq!(data.absender_code.as_deref(), Some("9900123456789"));
405 assert_eq!(data.empfaenger_code.as_deref(), Some("9900987654321"));
406 assert_eq!(data.interchange_ref.as_deref(), Some("REF001"));
407 assert_eq!(data.syntax_kennung.as_deref(), Some("UNOC"));
408 assert_eq!(data.datum.as_deref(), Some("210101"));
409 assert_eq!(data.zeit.as_deref(), Some("1200"));
410 }
411
412 #[test]
413 fn test_extract_envelope_from_segments_json() {
414 let envelope = vec![OwnedSegment {
415 id: "UNB".to_string(),
416 elements: vec![
417 vec!["UNOC".to_string(), "3".to_string()],
418 vec!["9900123456789".to_string(), "500".to_string()],
419 vec!["9900987654321".to_string(), "500".to_string()],
420 vec!["210101".to_string(), "1200".to_string()],
421 vec!["REF001".to_string()],
422 ],
423 segment_number: 0,
424 }];
425
426 let nd = extract_nachrichtendaten(&envelope);
427 assert_eq!(nd["absenderCode"].as_str().unwrap(), "9900123456789");
428 assert_eq!(nd["empfaengerCode"].as_str().unwrap(), "9900987654321");
429 assert_eq!(nd["interchangeRef"].as_str().unwrap(), "REF001");
430 assert_eq!(nd["syntaxKennung"].as_str().unwrap(), "UNOC");
431 assert_eq!(nd["datum"].as_str().unwrap(), "210101");
432 assert_eq!(nd["zeit"].as_str().unwrap(), "1200");
433 }
434
435 #[test]
436 fn test_extract_unh_fields() {
437 let unh = OwnedSegment {
438 id: "UNH".to_string(),
439 elements: vec![
440 vec!["MSG001".to_string()],
441 vec![
442 "UTILMD".to_string(),
443 "D".to_string(),
444 "11A".to_string(),
445 "UN".to_string(),
446 "S2.1".to_string(),
447 ],
448 ],
449 segment_number: 0,
450 };
451
452 let (referenz, typ) = extract_unh_fields(&unh);
453 assert_eq!(referenz, "MSG001");
454 assert_eq!(typ, "UTILMD");
455 }
456
457 #[test]
458 fn test_rebuild_unb_from_interchangedaten_typed() {
459 let data = Interchangedaten {
460 syntax_kennung: Some("UNOC".to_string()),
461 absender_code: Some("9900123456789".to_string()),
462 empfaenger_code: Some("9900987654321".to_string()),
463 datum: Some("210101".to_string()),
464 zeit: Some("1200".to_string()),
465 interchange_ref: Some("REF001".to_string()),
466 };
467
468 let unb = rebuild_unb_from_interchangedaten(&data);
469 assert_eq!(unb.id, "UNB");
470 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
471 assert_eq!(unb.elements[1][0], "9900123456789");
472 assert_eq!(unb.elements[2][0], "9900987654321");
473 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
474 assert_eq!(unb.elements[4], vec!["REF001"]);
475 }
476
477 #[test]
478 fn test_rebuild_unb_from_nachrichtendaten() {
479 let nd = serde_json::json!({
480 "syntaxKennung": "UNOC",
481 "absenderCode": "9900123456789",
482 "empfaengerCode": "9900987654321",
483 "datum": "210101",
484 "zeit": "1200",
485 "interchangeRef": "REF001"
486 });
487
488 let unb = rebuild_unb(&nd);
489 assert_eq!(unb.id, "UNB");
490 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
491 assert_eq!(unb.elements[1][0], "9900123456789");
492 assert_eq!(unb.elements[2][0], "9900987654321");
493 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
494 assert_eq!(unb.elements[4], vec!["REF001"]);
495 }
496
497 #[test]
498 fn test_rebuild_unb_defaults() {
499 let nd = serde_json::json!({});
500 let unb = rebuild_unb(&nd);
501 assert_eq!(unb.id, "UNB");
502 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
503 }
504
505 #[test]
506 fn test_rebuild_unh() {
507 let unh = rebuild_unh("00001", "UTILMD", "11A", "S2.1");
508 assert_eq!(unh.id, "UNH");
509 assert_eq!(unh.elements[0], vec!["00001"]);
510 assert_eq!(unh.elements[1][0], "UTILMD");
511 assert_eq!(unh.elements[1][1], "D");
512 assert_eq!(unh.elements[1][2], "11A");
513 assert_eq!(unh.elements[1][3], "UN");
514 assert_eq!(unh.elements[1][4], "S2.1");
515 }
516
517 #[test]
518 fn test_rebuild_unh_uses_the_given_release_and_association() {
519 let unh = rebuild_unh("00001", "UTILMD", "11A", "G1.0a");
522 assert_eq!(unh.elements[1], vec!["UTILMD", "D", "11A", "UN", "G1.0a"]);
523
524 let unh = rebuild_unh(
525 "00001",
526 "MSCONS",
527 release_code_for_message_type("MSCONS"),
528 "2.4c",
529 );
530 assert_eq!(unh.elements[1], vec!["MSCONS", "D", "04B", "UN", "2.4c"]);
531 }
532
533 #[test]
534 fn test_rebuild_unt() {
535 let unt = rebuild_unt(25, "00001");
536 assert_eq!(unt.id, "UNT");
537 assert_eq!(unt.elements[0], vec!["25"]);
538 assert_eq!(unt.elements[1], vec!["00001"]);
539 }
540
541 #[test]
542 fn test_rebuild_unz() {
543 let unz = rebuild_unz(1, "REF001");
544 assert_eq!(unz.id, "UNZ");
545 assert_eq!(unz.elements[0], vec!["1"]);
546 assert_eq!(unz.elements[1], vec!["REF001"]);
547 }
548
549 #[test]
550 fn test_roundtrip_interchangedaten_rebuild() {
551 let original = OwnedSegment {
552 id: "UNB".to_string(),
553 elements: vec![
554 vec!["UNOC".to_string(), "3".to_string()],
555 vec!["9900123456789".to_string(), "500".to_string()],
556 vec!["9900987654321".to_string(), "500".to_string()],
557 vec!["210101".to_string(), "1200".to_string()],
558 vec!["REF001".to_string()],
559 ],
560 segment_number: 0,
561 };
562
563 let data = extract_interchangedaten(&[original]);
564 let rebuilt = rebuild_unb_from_interchangedaten(&data);
565 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
566 assert_eq!(rebuilt.elements[1][0], "9900123456789");
567 assert_eq!(rebuilt.elements[2][0], "9900987654321");
568 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
569 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
570 }
571
572 #[test]
573 fn test_roundtrip_nachrichtendaten_rebuild() {
574 let original = OwnedSegment {
575 id: "UNB".to_string(),
576 elements: vec![
577 vec!["UNOC".to_string(), "3".to_string()],
578 vec!["9900123456789".to_string(), "500".to_string()],
579 vec!["9900987654321".to_string(), "500".to_string()],
580 vec!["210101".to_string(), "1200".to_string()],
581 vec!["REF001".to_string()],
582 ],
583 segment_number: 0,
584 };
585
586 let nd = extract_nachrichtendaten(&[original]);
587 let rebuilt = rebuild_unb(&nd);
588 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
589 assert_eq!(rebuilt.elements[1][0], "9900123456789");
590 assert_eq!(rebuilt.elements[2][0], "9900987654321");
591 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
592 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
593 }
594
595 #[test]
596 fn test_rebuild_unb_normalizes_ccyymmdd_to_yymmdd() {
597 let data = Interchangedaten {
599 syntax_kennung: Some("UNOC".to_string()),
600 absender_code: Some("9900000000003".to_string()),
601 empfaenger_code: Some("9900000000001".to_string()),
602 datum: Some("20260409".to_string()), zeit: Some("0725".to_string()),
604 interchange_ref: Some("00004".to_string()),
605 };
606
607 let unb = rebuild_unb_from_interchangedaten(&data);
608 assert_eq!(unb.elements[3], vec!["260409", "0725"]); let nd = serde_json::json!({
612 "syntaxKennung": "UNOC",
613 "absenderCode": "9900000000003",
614 "empfaengerCode": "9900000000001",
615 "datum": "20260409",
616 "zeit": "0725",
617 "interchangeRef": "00004"
618 });
619 let unb_json = rebuild_unb(&nd);
620 assert_eq!(unb_json.elements[3], vec!["260409", "0725"]);
621 }
622
623 #[test]
624 fn test_rebuild_unb_preserves_yymmdd() {
625 let data = Interchangedaten {
627 datum: Some("260409".to_string()),
628 zeit: Some("0725".to_string()),
629 ..Default::default()
630 };
631 let unb = rebuild_unb_from_interchangedaten(&data);
632 assert_eq!(unb.elements[3], vec!["260409", "0725"]);
633 }
634
635 #[test]
636 fn test_into_dynamic_nachricht() {
637 let mapped = MappedMessage {
638 stammdaten: serde_json::json!({"marktteilnehmer": []}),
639 transaktionen: vec![MappedTransaktion {
640 stammdaten: serde_json::json!({"prozessdaten": {"id": "1"}}),
641 nesting_info: Default::default(),
642 dp_routing: Default::default(),
643 }],
644 nesting_info: Default::default(),
645 dp_routing: Default::default(),
646 inter_group_segments: Default::default(),
647 };
648
649 let nd = Nachrichtendaten {
650 unh_referenz: "00001".to_string(),
651 nachrichten_typ: "UTILMD".to_string(),
652 };
653
654 let nachricht = mapped.into_dynamic_nachricht(nd);
655 assert_eq!(nachricht.nachrichtendaten.unh_referenz, "00001");
656 assert_eq!(nachricht.transaktionen.len(), 1);
657 assert!(nachricht.transaktionen[0]["prozessdaten"].is_object());
658 }
659}