Skip to main content

mig_bo4e/
model.rs

1//! Output model types for the MIG-driven mapping pipeline.
2//!
3//! Public types `Interchange`, `Nachricht`, `DynamicInterchange`, `DynamicNachricht`,
4//! `Interchangedaten`, `Nachrichtendaten` are re-exported from `bo4e-edifact-types`.
5//!
6//! Internal engine types `MappedMessage` and `MappedTransaktion` carry forward-mapping
7//! results including `nesting_info` metadata that is not part of the public API.
8
9use mig_assembly::assembler::AssembledSegment;
10use mig_types::segment::OwnedSegment;
11use serde::{Deserialize, Serialize};
12use std::collections::{BTreeMap, HashMap};
13
14// Re-export public model types from bo4e-edifact-types
15pub use bo4e_edifact_types::{
16    DynamicInterchange, DynamicNachricht, Interchange, Interchangedaten, Nachricht,
17    Nachrichtendaten,
18};
19
20/// Internal engine type for a forward-mapped transaction.
21///
22/// Contains all BO4E entities (including prozessdaten) in `stammdaten`,
23/// plus nesting distribution info used by the reverse mapper.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct MappedTransaktion {
27    /// All BO4E entities mapped from this transaction's segment groups.
28    /// Keys are entity names in camelCase (e.g., "prozessdaten", "marktlokation", "messlokation").
29    pub stammdaten: serde_json::Value,
30
31    /// Nesting distribution info for transaction-level entities.
32    ///
33    /// Maps entity key (camelCase) -> parent rep index for each child element.
34    /// Used by the reverse mapper to distribute children among parent group reps
35    /// within a transaction (e.g., SG36->SG40 in PRICAT).
36    /// Derived from the tree structure during forward mapping; never serialized.
37    #[serde(skip)]
38    pub nesting_info: HashMap<String, Vec<usize>>,
39
40    /// NAD+DP routing metadata captured during forward mapping.
41    ///
42    /// Maps the destination entity key ("marktlokation" / "messlokation") to a
43    /// vec of metadata blobs (one per routed DP entry, in array order). Each
44    /// blob carries the original `marktteilnehmer[]` index, marktrolle code,
45    /// LOC qualifier, and any unmapped extras — everything the reverse mapper
46    /// needs to rebuild the NAD+DP segment without leaking a `_dpSource`
47    /// marker into the public BO4E JSON. Empty when the transaction has no
48    /// NAD+DP segment.
49    #[serde(skip)]
50    pub dp_routing: HashMap<String, Vec<serde_json::Map<String, serde_json::Value>>>,
51}
52
53/// Intermediate result from mapping a single message's assembled tree.
54///
55/// Contains message-level stammdaten and per-transaction results.
56/// Used by `MappingEngine::map_interchange()` before wrapping into `Nachricht`.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct MappedMessage {
60    /// Message-level BO4E entities (e.g., Marktteilnehmer from SG2).
61    pub stammdaten: serde_json::Value,
62
63    /// Per-transaction results (one per SG4 instance).
64    pub transaktionen: Vec<MappedTransaktion>,
65
66    /// Nesting distribution info for message-level entities.
67    ///
68    /// Maps entity key (camelCase) -> parent rep index for each child element.
69    /// Used by the reverse mapper to distribute children among parent group reps.
70    /// Derived from the tree structure during forward mapping; never serialized.
71    #[serde(skip)]
72    pub nesting_info: HashMap<String, Vec<usize>>,
73
74    /// NAD+DP routing metadata captured during forward mapping at message
75    /// scope. See [`MappedTransaktion::dp_routing`] for the per-transaction
76    /// counterpart used when NAD+DP lives under a transaction-level group.
77    #[serde(skip)]
78    pub dp_routing: HashMap<String, Vec<serde_json::Map<String, serde_json::Value>>>,
79
80    /// Inter-group segments captured by the assembler at message scope.
81    ///
82    /// Contains both schema-recognized root segments emitted between groups
83    /// (e.g. UNS+S in MSCONS / ORDERS) and PID-foreign segments preserved by
84    /// `skip_unknown_segments` mode (e.g. IMD in QUOTES 15005). Threaded
85    /// through MappedMessage so that `map_interchange_reverse` can hand
86    /// them back to the disassembler for byte-identical roundtrip — without
87    /// this, BO4E forward + reverse drops anything not represented in a
88    /// TOML mapping definition.
89    #[serde(skip)]
90    pub inter_group_segments: BTreeMap<usize, Vec<AssembledSegment>>,
91}
92
93impl MappedMessage {
94    /// Convert this internal engine result into a public `DynamicNachricht`.
95    ///
96    /// Each `MappedTransaktion.stammdaten` becomes a transaction entry in the
97    /// `DynamicNachricht.transaktionen` Vec.
98    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
111/// Extract message reference and message type from a UNH segment.
112pub 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
118/// Extract typed interchange-level metadata from envelope segments (UNB).
119pub 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
143/// Extract interchange-level metadata from envelope segments (UNB) as JSON.
144///
145/// Kept for backward compatibility. Prefer `extract_interchangedaten()` for typed access.
146pub 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
151/// Normalize a date string to UNB S004 YYMMDD format (6 digits).
152///
153/// UNB with UNOC:3 syntax uses YYMMDD (6 digits), not CCYYMMDD (8 digits).
154/// If an 8-digit CCYYMMDD date is provided, the century prefix is stripped.
155fn 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
163/// Rebuild a UNB (interchange header) segment from typed `Interchangedaten`.
164///
165/// This is the inverse of `extract_interchangedaten()`.
166/// Fields not present get sensible defaults (UNOC:3, "500" qualifier).
167/// Dates in CCYYMMDD (8-digit) format are automatically normalized to YYMMDD (6-digit).
168pub 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
189/// Rebuild a UNB (interchange header) segment from nachrichtendaten JSON.
190///
191/// This is the inverse of `extract_nachrichtendaten()`.
192/// Fields not present in the JSON get sensible defaults (UNOC:3, "500" qualifier).
193/// Dates in CCYYMMDD (8-digit) format are automatically normalized to YYMMDD (6-digit).
194pub 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
234/// Rebuild a UNH (message header) segment from reference number and message type.
235///
236/// Produces: `UNH+referenz+typ:D:11A:UN:S2.1`
237pub 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
254/// Rebuild a UNT (message trailer) segment.
255///
256/// Produces: `UNT+count+referenz`
257/// `segment_count` includes UNH and UNT themselves.
258pub 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
266/// Rebuild a UNZ (interchange trailer) segment.
267///
268/// Produces: `UNZ+count+ref`
269pub 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        // UNB S004 datum must be YYMMDD (6 digits), not CCYYMMDD (8 digits)
546        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()), // 8-digit CCYYMMDD input
551            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"]); // normalized to 6-digit YYMMDD
557
558        // Same via JSON path
559        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        // Already 6-digit YYMMDD — should pass through unchanged
574        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}