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(referenz: &str, nachrichten_typ: &str) -> OwnedSegment {
238 OwnedSegment {
239 id: "UNH".to_string(),
240 elements: vec![
241 vec![referenz.to_string()],
242 vec![
243 nachrichten_typ.to_string(),
244 "D".to_string(),
245 "11A".to_string(),
246 "UN".to_string(),
247 "S2.1".to_string(),
248 ],
249 ],
250 segment_number: 0,
251 }
252}
253
254pub fn rebuild_unt(segment_count: usize, referenz: &str) -> OwnedSegment {
259 OwnedSegment {
260 id: "UNT".to_string(),
261 elements: vec![vec![segment_count.to_string()], vec![referenz.to_string()]],
262 segment_number: 0,
263 }
264}
265
266pub fn rebuild_unz(message_count: usize, interchange_ref: &str) -> OwnedSegment {
270 OwnedSegment {
271 id: "UNZ".to_string(),
272 elements: vec![
273 vec![message_count.to_string()],
274 vec![interchange_ref.to_string()],
275 ],
276 segment_number: 0,
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_mapped_transaktion_serde_roundtrip() {
286 let tx = MappedTransaktion {
287 stammdaten: serde_json::json!({
288 "prozessdaten": {
289 "vorgangId": "TX001",
290 "transaktionsgrund": "E01"
291 },
292 "marktlokation": { "marktlokationsId": "DE000111222333" }
293 }),
294 nesting_info: Default::default(),
295 dp_routing: Default::default(),
296 };
297
298 let json = serde_json::to_string(&tx).unwrap();
299 let de: MappedTransaktion = serde_json::from_str(&json).unwrap();
300 assert_eq!(
301 de.stammdaten["prozessdaten"]["vorgangId"].as_str().unwrap(),
302 "TX001"
303 );
304 assert!(de.stammdaten["marktlokation"].is_object());
305 }
306
307 #[test]
308 fn test_dynamic_nachricht_serde_roundtrip() {
309 let msg: DynamicNachricht = Nachricht {
310 nachrichtendaten: Nachrichtendaten {
311 unh_referenz: "00001".to_string(),
312 nachrichten_typ: "UTILMD".to_string(),
313 },
314 stammdaten: serde_json::json!({
315 "marktteilnehmer": [
316 { "marktrolle": "MS", "rollencodenummer": "9900123" }
317 ]
318 }),
319 transaktionen: vec![serde_json::json!({})],
320 };
321
322 let json = serde_json::to_string(&msg).unwrap();
323 let de: DynamicNachricht = serde_json::from_str(&json).unwrap();
324 assert_eq!(de.nachrichtendaten.unh_referenz, "00001");
325 assert_eq!(de.nachrichtendaten.nachrichten_typ, "UTILMD");
326 assert_eq!(de.transaktionen.len(), 1);
327 }
328
329 #[test]
330 fn test_dynamic_interchange_serde_roundtrip() {
331 let interchange: DynamicInterchange = Interchange {
332 interchangedaten: Interchangedaten {
333 absender_code: Some("9900123456789".to_string()),
334 empfaenger_code: Some("9900987654321".to_string()),
335 ..Default::default()
336 },
337 nachrichten: vec![Nachricht {
338 nachrichtendaten: Nachrichtendaten {
339 unh_referenz: "00001".to_string(),
340 nachrichten_typ: "UTILMD".to_string(),
341 },
342 stammdaten: serde_json::json!({}),
343 transaktionen: vec![],
344 }],
345 };
346
347 let json = serde_json::to_string_pretty(&interchange).unwrap();
348 let de: DynamicInterchange = serde_json::from_str(&json).unwrap();
349 assert_eq!(de.nachrichten.len(), 1);
350 assert_eq!(de.nachrichten[0].nachrichtendaten.unh_referenz, "00001");
351 }
352
353 #[test]
354 fn test_extract_interchangedaten_from_segments() {
355 let envelope = vec![OwnedSegment {
356 id: "UNB".to_string(),
357 elements: vec![
358 vec!["UNOC".to_string(), "3".to_string()],
359 vec!["9900123456789".to_string(), "500".to_string()],
360 vec!["9900987654321".to_string(), "500".to_string()],
361 vec!["210101".to_string(), "1200".to_string()],
362 vec!["REF001".to_string()],
363 ],
364 segment_number: 0,
365 }];
366
367 let data = extract_interchangedaten(&envelope);
368 assert_eq!(data.absender_code.as_deref(), Some("9900123456789"));
369 assert_eq!(data.empfaenger_code.as_deref(), Some("9900987654321"));
370 assert_eq!(data.interchange_ref.as_deref(), Some("REF001"));
371 assert_eq!(data.syntax_kennung.as_deref(), Some("UNOC"));
372 assert_eq!(data.datum.as_deref(), Some("210101"));
373 assert_eq!(data.zeit.as_deref(), Some("1200"));
374 }
375
376 #[test]
377 fn test_extract_envelope_from_segments_json() {
378 let envelope = vec![OwnedSegment {
379 id: "UNB".to_string(),
380 elements: vec![
381 vec!["UNOC".to_string(), "3".to_string()],
382 vec!["9900123456789".to_string(), "500".to_string()],
383 vec!["9900987654321".to_string(), "500".to_string()],
384 vec!["210101".to_string(), "1200".to_string()],
385 vec!["REF001".to_string()],
386 ],
387 segment_number: 0,
388 }];
389
390 let nd = extract_nachrichtendaten(&envelope);
391 assert_eq!(nd["absenderCode"].as_str().unwrap(), "9900123456789");
392 assert_eq!(nd["empfaengerCode"].as_str().unwrap(), "9900987654321");
393 assert_eq!(nd["interchangeRef"].as_str().unwrap(), "REF001");
394 assert_eq!(nd["syntaxKennung"].as_str().unwrap(), "UNOC");
395 assert_eq!(nd["datum"].as_str().unwrap(), "210101");
396 assert_eq!(nd["zeit"].as_str().unwrap(), "1200");
397 }
398
399 #[test]
400 fn test_extract_unh_fields() {
401 let unh = OwnedSegment {
402 id: "UNH".to_string(),
403 elements: vec![
404 vec!["MSG001".to_string()],
405 vec![
406 "UTILMD".to_string(),
407 "D".to_string(),
408 "11A".to_string(),
409 "UN".to_string(),
410 "S2.1".to_string(),
411 ],
412 ],
413 segment_number: 0,
414 };
415
416 let (referenz, typ) = extract_unh_fields(&unh);
417 assert_eq!(referenz, "MSG001");
418 assert_eq!(typ, "UTILMD");
419 }
420
421 #[test]
422 fn test_rebuild_unb_from_interchangedaten_typed() {
423 let data = Interchangedaten {
424 syntax_kennung: Some("UNOC".to_string()),
425 absender_code: Some("9900123456789".to_string()),
426 empfaenger_code: Some("9900987654321".to_string()),
427 datum: Some("210101".to_string()),
428 zeit: Some("1200".to_string()),
429 interchange_ref: Some("REF001".to_string()),
430 };
431
432 let unb = rebuild_unb_from_interchangedaten(&data);
433 assert_eq!(unb.id, "UNB");
434 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
435 assert_eq!(unb.elements[1][0], "9900123456789");
436 assert_eq!(unb.elements[2][0], "9900987654321");
437 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
438 assert_eq!(unb.elements[4], vec!["REF001"]);
439 }
440
441 #[test]
442 fn test_rebuild_unb_from_nachrichtendaten() {
443 let nd = serde_json::json!({
444 "syntaxKennung": "UNOC",
445 "absenderCode": "9900123456789",
446 "empfaengerCode": "9900987654321",
447 "datum": "210101",
448 "zeit": "1200",
449 "interchangeRef": "REF001"
450 });
451
452 let unb = rebuild_unb(&nd);
453 assert_eq!(unb.id, "UNB");
454 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
455 assert_eq!(unb.elements[1][0], "9900123456789");
456 assert_eq!(unb.elements[2][0], "9900987654321");
457 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
458 assert_eq!(unb.elements[4], vec!["REF001"]);
459 }
460
461 #[test]
462 fn test_rebuild_unb_defaults() {
463 let nd = serde_json::json!({});
464 let unb = rebuild_unb(&nd);
465 assert_eq!(unb.id, "UNB");
466 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
467 }
468
469 #[test]
470 fn test_rebuild_unh() {
471 let unh = rebuild_unh("00001", "UTILMD");
472 assert_eq!(unh.id, "UNH");
473 assert_eq!(unh.elements[0], vec!["00001"]);
474 assert_eq!(unh.elements[1][0], "UTILMD");
475 assert_eq!(unh.elements[1][1], "D");
476 assert_eq!(unh.elements[1][2], "11A");
477 assert_eq!(unh.elements[1][3], "UN");
478 assert_eq!(unh.elements[1][4], "S2.1");
479 }
480
481 #[test]
482 fn test_rebuild_unt() {
483 let unt = rebuild_unt(25, "00001");
484 assert_eq!(unt.id, "UNT");
485 assert_eq!(unt.elements[0], vec!["25"]);
486 assert_eq!(unt.elements[1], vec!["00001"]);
487 }
488
489 #[test]
490 fn test_rebuild_unz() {
491 let unz = rebuild_unz(1, "REF001");
492 assert_eq!(unz.id, "UNZ");
493 assert_eq!(unz.elements[0], vec!["1"]);
494 assert_eq!(unz.elements[1], vec!["REF001"]);
495 }
496
497 #[test]
498 fn test_roundtrip_interchangedaten_rebuild() {
499 let original = OwnedSegment {
500 id: "UNB".to_string(),
501 elements: vec![
502 vec!["UNOC".to_string(), "3".to_string()],
503 vec!["9900123456789".to_string(), "500".to_string()],
504 vec!["9900987654321".to_string(), "500".to_string()],
505 vec!["210101".to_string(), "1200".to_string()],
506 vec!["REF001".to_string()],
507 ],
508 segment_number: 0,
509 };
510
511 let data = extract_interchangedaten(&[original]);
512 let rebuilt = rebuild_unb_from_interchangedaten(&data);
513 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
514 assert_eq!(rebuilt.elements[1][0], "9900123456789");
515 assert_eq!(rebuilt.elements[2][0], "9900987654321");
516 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
517 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
518 }
519
520 #[test]
521 fn test_roundtrip_nachrichtendaten_rebuild() {
522 let original = OwnedSegment {
523 id: "UNB".to_string(),
524 elements: vec![
525 vec!["UNOC".to_string(), "3".to_string()],
526 vec!["9900123456789".to_string(), "500".to_string()],
527 vec!["9900987654321".to_string(), "500".to_string()],
528 vec!["210101".to_string(), "1200".to_string()],
529 vec!["REF001".to_string()],
530 ],
531 segment_number: 0,
532 };
533
534 let nd = extract_nachrichtendaten(&[original]);
535 let rebuilt = rebuild_unb(&nd);
536 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
537 assert_eq!(rebuilt.elements[1][0], "9900123456789");
538 assert_eq!(rebuilt.elements[2][0], "9900987654321");
539 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
540 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
541 }
542
543 #[test]
544 fn test_rebuild_unb_normalizes_ccyymmdd_to_yymmdd() {
545 let data = Interchangedaten {
547 syntax_kennung: Some("UNOC".to_string()),
548 absender_code: Some("9900000000003".to_string()),
549 empfaenger_code: Some("9900000000001".to_string()),
550 datum: Some("20260409".to_string()), zeit: Some("0725".to_string()),
552 interchange_ref: Some("00004".to_string()),
553 };
554
555 let unb = rebuild_unb_from_interchangedaten(&data);
556 assert_eq!(unb.elements[3], vec!["260409", "0725"]); let nd = serde_json::json!({
560 "syntaxKennung": "UNOC",
561 "absenderCode": "9900000000003",
562 "empfaengerCode": "9900000000001",
563 "datum": "20260409",
564 "zeit": "0725",
565 "interchangeRef": "00004"
566 });
567 let unb_json = rebuild_unb(&nd);
568 assert_eq!(unb_json.elements[3], vec!["260409", "0725"]);
569 }
570
571 #[test]
572 fn test_rebuild_unb_preserves_yymmdd() {
573 let data = Interchangedaten {
575 datum: Some("260409".to_string()),
576 zeit: Some("0725".to_string()),
577 ..Default::default()
578 };
579 let unb = rebuild_unb_from_interchangedaten(&data);
580 assert_eq!(unb.elements[3], vec!["260409", "0725"]);
581 }
582
583 #[test]
584 fn test_into_dynamic_nachricht() {
585 let mapped = MappedMessage {
586 stammdaten: serde_json::json!({"marktteilnehmer": []}),
587 transaktionen: vec![MappedTransaktion {
588 stammdaten: serde_json::json!({"prozessdaten": {"id": "1"}}),
589 nesting_info: Default::default(),
590 dp_routing: Default::default(),
591 }],
592 nesting_info: Default::default(),
593 dp_routing: Default::default(),
594 inter_group_segments: Default::default(),
595 };
596
597 let nd = Nachrichtendaten {
598 unh_referenz: "00001".to_string(),
599 nachrichten_typ: "UTILMD".to_string(),
600 };
601
602 let nachricht = mapped.into_dynamic_nachricht(nd);
603 assert_eq!(nachricht.nachrichtendaten.unh_referenz, "00001");
604 assert_eq!(nachricht.transaktionen.len(), 1);
605 assert!(nachricht.transaktionen[0]["prozessdaten"].is_object());
606 }
607}