1use mig_types::segment::OwnedSegment;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13pub use bo4e_edifact_types::{
15 DynamicInterchange, DynamicNachricht, Interchange, Interchangedaten, Nachricht,
16 Nachrichtendaten,
17};
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct MappedTransaktion {
26 pub stammdaten: serde_json::Value,
29
30 #[serde(skip)]
37 pub nesting_info: HashMap<String, Vec<usize>>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct MappedMessage {
47 pub stammdaten: serde_json::Value,
49
50 pub transaktionen: Vec<MappedTransaktion>,
52
53 #[serde(skip)]
59 pub nesting_info: HashMap<String, Vec<usize>>,
60}
61
62impl MappedMessage {
63 pub fn into_dynamic_nachricht(self, nachrichtendaten: Nachrichtendaten) -> DynamicNachricht {
68 Nachricht {
69 nachrichtendaten,
70 stammdaten: self.stammdaten,
71 transaktionen: self
72 .transaktionen
73 .into_iter()
74 .map(|t| t.stammdaten)
75 .collect(),
76 }
77 }
78}
79
80pub fn extract_unh_fields(unh: &OwnedSegment) -> (String, String) {
82 let referenz = unh.get_element(0).to_string();
83 let typ = unh.get_component(1, 0).to_string();
84 (referenz, typ)
85}
86
87pub fn extract_interchangedaten(envelope: &[OwnedSegment]) -> Interchangedaten {
89 let mut result = Interchangedaten::default();
90
91 for seg in envelope {
92 if seg.is("UNB") {
93 let val = |s: &str| {
94 if s.is_empty() {
95 None
96 } else {
97 Some(s.to_string())
98 }
99 };
100 result.syntax_kennung = val(seg.get_component(0, 0));
101 result.absender_code = val(seg.get_component(1, 0));
102 result.empfaenger_code = val(seg.get_component(2, 0));
103 result.datum = val(seg.get_component(3, 0));
104 result.zeit = val(seg.get_component(3, 1));
105 result.interchange_ref = val(seg.get_element(4));
106 }
107 }
108
109 result
110}
111
112pub fn extract_nachrichtendaten(envelope: &[OwnedSegment]) -> serde_json::Value {
116 let data = extract_interchangedaten(envelope);
117 serde_json::to_value(&data).unwrap_or_default()
118}
119
120fn normalize_unb_datum(datum: &str) -> &str {
125 if datum.len() == 8 && datum.as_bytes().iter().all(|b| b.is_ascii_digit()) {
126 &datum[2..]
127 } else {
128 datum
129 }
130}
131
132pub fn rebuild_unb_from_interchangedaten(data: &Interchangedaten) -> OwnedSegment {
138 let syntax = data.syntax_kennung.as_deref().unwrap_or("UNOC");
139 let sender = data.absender_code.as_deref().unwrap_or("");
140 let receiver = data.empfaenger_code.as_deref().unwrap_or("");
141 let datum = normalize_unb_datum(data.datum.as_deref().unwrap_or(""));
142 let zeit = data.zeit.as_deref().unwrap_or("");
143 let interchange_ref = data.interchange_ref.as_deref().unwrap_or("00000");
144
145 OwnedSegment {
146 id: "UNB".to_string(),
147 elements: vec![
148 vec![syntax.to_string(), "3".to_string()],
149 vec![sender.to_string(), "500".to_string()],
150 vec![receiver.to_string(), "500".to_string()],
151 vec![datum.to_string(), zeit.to_string()],
152 vec![interchange_ref.to_string()],
153 ],
154 segment_number: 0,
155 }
156}
157
158pub fn rebuild_unb(nachrichtendaten: &serde_json::Value) -> OwnedSegment {
164 let syntax = nachrichtendaten
165 .get("syntaxKennung")
166 .and_then(|v| v.as_str())
167 .unwrap_or("UNOC");
168 let sender = nachrichtendaten
169 .get("absenderCode")
170 .and_then(|v| v.as_str())
171 .unwrap_or("");
172 let receiver = nachrichtendaten
173 .get("empfaengerCode")
174 .and_then(|v| v.as_str())
175 .unwrap_or("");
176 let datum_raw = nachrichtendaten
177 .get("datum")
178 .and_then(|v| v.as_str())
179 .unwrap_or("");
180 let datum = normalize_unb_datum(datum_raw);
181 let zeit = nachrichtendaten
182 .get("zeit")
183 .and_then(|v| v.as_str())
184 .unwrap_or("");
185 let interchange_ref = nachrichtendaten
186 .get("interchangeRef")
187 .and_then(|v| v.as_str())
188 .unwrap_or("00000");
189
190 OwnedSegment {
191 id: "UNB".to_string(),
192 elements: vec![
193 vec![syntax.to_string(), "3".to_string()],
194 vec![sender.to_string(), "500".to_string()],
195 vec![receiver.to_string(), "500".to_string()],
196 vec![datum.to_string(), zeit.to_string()],
197 vec![interchange_ref.to_string()],
198 ],
199 segment_number: 0,
200 }
201}
202
203pub fn rebuild_unh(referenz: &str, nachrichten_typ: &str) -> OwnedSegment {
207 OwnedSegment {
208 id: "UNH".to_string(),
209 elements: vec![
210 vec![referenz.to_string()],
211 vec![
212 nachrichten_typ.to_string(),
213 "D".to_string(),
214 "11A".to_string(),
215 "UN".to_string(),
216 "S2.1".to_string(),
217 ],
218 ],
219 segment_number: 0,
220 }
221}
222
223pub fn rebuild_unt(segment_count: usize, referenz: &str) -> OwnedSegment {
228 OwnedSegment {
229 id: "UNT".to_string(),
230 elements: vec![vec![segment_count.to_string()], vec![referenz.to_string()]],
231 segment_number: 0,
232 }
233}
234
235pub fn rebuild_unz(message_count: usize, interchange_ref: &str) -> OwnedSegment {
239 OwnedSegment {
240 id: "UNZ".to_string(),
241 elements: vec![
242 vec![message_count.to_string()],
243 vec![interchange_ref.to_string()],
244 ],
245 segment_number: 0,
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn test_mapped_transaktion_serde_roundtrip() {
255 let tx = MappedTransaktion {
256 stammdaten: serde_json::json!({
257 "prozessdaten": {
258 "vorgangId": "TX001",
259 "transaktionsgrund": "E01"
260 },
261 "marktlokation": { "marktlokationsId": "DE000111222333" }
262 }),
263 nesting_info: Default::default(),
264 };
265
266 let json = serde_json::to_string(&tx).unwrap();
267 let de: MappedTransaktion = serde_json::from_str(&json).unwrap();
268 assert_eq!(
269 de.stammdaten["prozessdaten"]["vorgangId"].as_str().unwrap(),
270 "TX001"
271 );
272 assert!(de.stammdaten["marktlokation"].is_object());
273 }
274
275 #[test]
276 fn test_dynamic_nachricht_serde_roundtrip() {
277 let msg: DynamicNachricht = Nachricht {
278 nachrichtendaten: Nachrichtendaten {
279 unh_referenz: "00001".to_string(),
280 nachrichten_typ: "UTILMD".to_string(),
281 },
282 stammdaten: serde_json::json!({
283 "marktteilnehmer": [
284 { "marktrolle": "MS", "rollencodenummer": "9900123" }
285 ]
286 }),
287 transaktionen: vec![serde_json::json!({})],
288 };
289
290 let json = serde_json::to_string(&msg).unwrap();
291 let de: DynamicNachricht = serde_json::from_str(&json).unwrap();
292 assert_eq!(de.nachrichtendaten.unh_referenz, "00001");
293 assert_eq!(de.nachrichtendaten.nachrichten_typ, "UTILMD");
294 assert_eq!(de.transaktionen.len(), 1);
295 }
296
297 #[test]
298 fn test_dynamic_interchange_serde_roundtrip() {
299 let interchange: DynamicInterchange = Interchange {
300 interchangedaten: Interchangedaten {
301 absender_code: Some("9900123456789".to_string()),
302 empfaenger_code: Some("9900987654321".to_string()),
303 ..Default::default()
304 },
305 nachrichten: vec![Nachricht {
306 nachrichtendaten: Nachrichtendaten {
307 unh_referenz: "00001".to_string(),
308 nachrichten_typ: "UTILMD".to_string(),
309 },
310 stammdaten: serde_json::json!({}),
311 transaktionen: vec![],
312 }],
313 };
314
315 let json = serde_json::to_string_pretty(&interchange).unwrap();
316 let de: DynamicInterchange = serde_json::from_str(&json).unwrap();
317 assert_eq!(de.nachrichten.len(), 1);
318 assert_eq!(de.nachrichten[0].nachrichtendaten.unh_referenz, "00001");
319 }
320
321 #[test]
322 fn test_extract_interchangedaten_from_segments() {
323 let envelope = vec![OwnedSegment {
324 id: "UNB".to_string(),
325 elements: vec![
326 vec!["UNOC".to_string(), "3".to_string()],
327 vec!["9900123456789".to_string(), "500".to_string()],
328 vec!["9900987654321".to_string(), "500".to_string()],
329 vec!["210101".to_string(), "1200".to_string()],
330 vec!["REF001".to_string()],
331 ],
332 segment_number: 0,
333 }];
334
335 let data = extract_interchangedaten(&envelope);
336 assert_eq!(data.absender_code.as_deref(), Some("9900123456789"));
337 assert_eq!(data.empfaenger_code.as_deref(), Some("9900987654321"));
338 assert_eq!(data.interchange_ref.as_deref(), Some("REF001"));
339 assert_eq!(data.syntax_kennung.as_deref(), Some("UNOC"));
340 assert_eq!(data.datum.as_deref(), Some("210101"));
341 assert_eq!(data.zeit.as_deref(), Some("1200"));
342 }
343
344 #[test]
345 fn test_extract_envelope_from_segments_json() {
346 let envelope = vec![OwnedSegment {
347 id: "UNB".to_string(),
348 elements: vec![
349 vec!["UNOC".to_string(), "3".to_string()],
350 vec!["9900123456789".to_string(), "500".to_string()],
351 vec!["9900987654321".to_string(), "500".to_string()],
352 vec!["210101".to_string(), "1200".to_string()],
353 vec!["REF001".to_string()],
354 ],
355 segment_number: 0,
356 }];
357
358 let nd = extract_nachrichtendaten(&envelope);
359 assert_eq!(nd["absenderCode"].as_str().unwrap(), "9900123456789");
360 assert_eq!(nd["empfaengerCode"].as_str().unwrap(), "9900987654321");
361 assert_eq!(nd["interchangeRef"].as_str().unwrap(), "REF001");
362 assert_eq!(nd["syntaxKennung"].as_str().unwrap(), "UNOC");
363 assert_eq!(nd["datum"].as_str().unwrap(), "210101");
364 assert_eq!(nd["zeit"].as_str().unwrap(), "1200");
365 }
366
367 #[test]
368 fn test_extract_unh_fields() {
369 let unh = OwnedSegment {
370 id: "UNH".to_string(),
371 elements: vec![
372 vec!["MSG001".to_string()],
373 vec![
374 "UTILMD".to_string(),
375 "D".to_string(),
376 "11A".to_string(),
377 "UN".to_string(),
378 "S2.1".to_string(),
379 ],
380 ],
381 segment_number: 0,
382 };
383
384 let (referenz, typ) = extract_unh_fields(&unh);
385 assert_eq!(referenz, "MSG001");
386 assert_eq!(typ, "UTILMD");
387 }
388
389 #[test]
390 fn test_rebuild_unb_from_interchangedaten_typed() {
391 let data = Interchangedaten {
392 syntax_kennung: Some("UNOC".to_string()),
393 absender_code: Some("9900123456789".to_string()),
394 empfaenger_code: Some("9900987654321".to_string()),
395 datum: Some("210101".to_string()),
396 zeit: Some("1200".to_string()),
397 interchange_ref: Some("REF001".to_string()),
398 };
399
400 let unb = rebuild_unb_from_interchangedaten(&data);
401 assert_eq!(unb.id, "UNB");
402 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
403 assert_eq!(unb.elements[1][0], "9900123456789");
404 assert_eq!(unb.elements[2][0], "9900987654321");
405 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
406 assert_eq!(unb.elements[4], vec!["REF001"]);
407 }
408
409 #[test]
410 fn test_rebuild_unb_from_nachrichtendaten() {
411 let nd = serde_json::json!({
412 "syntaxKennung": "UNOC",
413 "absenderCode": "9900123456789",
414 "empfaengerCode": "9900987654321",
415 "datum": "210101",
416 "zeit": "1200",
417 "interchangeRef": "REF001"
418 });
419
420 let unb = rebuild_unb(&nd);
421 assert_eq!(unb.id, "UNB");
422 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
423 assert_eq!(unb.elements[1][0], "9900123456789");
424 assert_eq!(unb.elements[2][0], "9900987654321");
425 assert_eq!(unb.elements[3], vec!["210101", "1200"]);
426 assert_eq!(unb.elements[4], vec!["REF001"]);
427 }
428
429 #[test]
430 fn test_rebuild_unb_defaults() {
431 let nd = serde_json::json!({});
432 let unb = rebuild_unb(&nd);
433 assert_eq!(unb.id, "UNB");
434 assert_eq!(unb.elements[0], vec!["UNOC", "3"]);
435 }
436
437 #[test]
438 fn test_rebuild_unh() {
439 let unh = rebuild_unh("00001", "UTILMD");
440 assert_eq!(unh.id, "UNH");
441 assert_eq!(unh.elements[0], vec!["00001"]);
442 assert_eq!(unh.elements[1][0], "UTILMD");
443 assert_eq!(unh.elements[1][1], "D");
444 assert_eq!(unh.elements[1][2], "11A");
445 assert_eq!(unh.elements[1][3], "UN");
446 assert_eq!(unh.elements[1][4], "S2.1");
447 }
448
449 #[test]
450 fn test_rebuild_unt() {
451 let unt = rebuild_unt(25, "00001");
452 assert_eq!(unt.id, "UNT");
453 assert_eq!(unt.elements[0], vec!["25"]);
454 assert_eq!(unt.elements[1], vec!["00001"]);
455 }
456
457 #[test]
458 fn test_rebuild_unz() {
459 let unz = rebuild_unz(1, "REF001");
460 assert_eq!(unz.id, "UNZ");
461 assert_eq!(unz.elements[0], vec!["1"]);
462 assert_eq!(unz.elements[1], vec!["REF001"]);
463 }
464
465 #[test]
466 fn test_roundtrip_interchangedaten_rebuild() {
467 let original = OwnedSegment {
468 id: "UNB".to_string(),
469 elements: vec![
470 vec!["UNOC".to_string(), "3".to_string()],
471 vec!["9900123456789".to_string(), "500".to_string()],
472 vec!["9900987654321".to_string(), "500".to_string()],
473 vec!["210101".to_string(), "1200".to_string()],
474 vec!["REF001".to_string()],
475 ],
476 segment_number: 0,
477 };
478
479 let data = extract_interchangedaten(&[original]);
480 let rebuilt = rebuild_unb_from_interchangedaten(&data);
481 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
482 assert_eq!(rebuilt.elements[1][0], "9900123456789");
483 assert_eq!(rebuilt.elements[2][0], "9900987654321");
484 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
485 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
486 }
487
488 #[test]
489 fn test_roundtrip_nachrichtendaten_rebuild() {
490 let original = OwnedSegment {
491 id: "UNB".to_string(),
492 elements: vec![
493 vec!["UNOC".to_string(), "3".to_string()],
494 vec!["9900123456789".to_string(), "500".to_string()],
495 vec!["9900987654321".to_string(), "500".to_string()],
496 vec!["210101".to_string(), "1200".to_string()],
497 vec!["REF001".to_string()],
498 ],
499 segment_number: 0,
500 };
501
502 let nd = extract_nachrichtendaten(&[original]);
503 let rebuilt = rebuild_unb(&nd);
504 assert_eq!(rebuilt.elements[0], vec!["UNOC", "3"]);
505 assert_eq!(rebuilt.elements[1][0], "9900123456789");
506 assert_eq!(rebuilt.elements[2][0], "9900987654321");
507 assert_eq!(rebuilt.elements[3], vec!["210101", "1200"]);
508 assert_eq!(rebuilt.elements[4], vec!["REF001"]);
509 }
510
511 #[test]
512 fn test_rebuild_unb_normalizes_ccyymmdd_to_yymmdd() {
513 let data = Interchangedaten {
515 syntax_kennung: Some("UNOC".to_string()),
516 absender_code: Some("9900000000003".to_string()),
517 empfaenger_code: Some("9900000000001".to_string()),
518 datum: Some("20260409".to_string()), zeit: Some("0725".to_string()),
520 interchange_ref: Some("00004".to_string()),
521 };
522
523 let unb = rebuild_unb_from_interchangedaten(&data);
524 assert_eq!(unb.elements[3], vec!["260409", "0725"]); let nd = serde_json::json!({
528 "syntaxKennung": "UNOC",
529 "absenderCode": "9900000000003",
530 "empfaengerCode": "9900000000001",
531 "datum": "20260409",
532 "zeit": "0725",
533 "interchangeRef": "00004"
534 });
535 let unb_json = rebuild_unb(&nd);
536 assert_eq!(unb_json.elements[3], vec!["260409", "0725"]);
537 }
538
539 #[test]
540 fn test_rebuild_unb_preserves_yymmdd() {
541 let data = Interchangedaten {
543 datum: Some("260409".to_string()),
544 zeit: Some("0725".to_string()),
545 ..Default::default()
546 };
547 let unb = rebuild_unb_from_interchangedaten(&data);
548 assert_eq!(unb.elements[3], vec!["260409", "0725"]);
549 }
550
551 #[test]
552 fn test_into_dynamic_nachricht() {
553 let mapped = MappedMessage {
554 stammdaten: serde_json::json!({"marktteilnehmer": []}),
555 transaktionen: vec![MappedTransaktion {
556 stammdaten: serde_json::json!({"prozessdaten": {"id": "1"}}),
557 nesting_info: Default::default(),
558 }],
559 nesting_info: Default::default(),
560 };
561
562 let nd = Nachrichtendaten {
563 unh_referenz: "00001".to_string(),
564 nachrichten_typ: "UTILMD".to_string(),
565 };
566
567 let nachricht = mapped.into_dynamic_nachricht(nd);
568 assert_eq!(nachricht.nachrichtendaten.unh_referenz, "00001");
569 assert_eq!(nachricht.transaktionen.len(), 1);
570 assert!(nachricht.transaktionen[0]["prozessdaten"].is_object());
571 }
572}