1use crate::errors::SwiftValidationError;
2use crate::fields::*;
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct MT104Transaction {
9 #[serde(rename = "21")]
11 pub field_21: Field21NoOption,
12
13 #[serde(rename = "23E")]
15 pub field_23e: Option<Field23E>,
16
17 #[serde(rename = "21C")]
19 pub field_21c: Option<Field21C>,
20
21 #[serde(rename = "21D")]
23 pub field_21d: Option<Field21D>,
24
25 #[serde(rename = "21E")]
27 pub field_21e: Option<Field21E>,
28
29 #[serde(rename = "32B")]
31 pub field_32b: Field32B,
32
33 #[serde(flatten)]
35 pub instructing_party_tx: Option<Field50InstructingParty>,
36
37 #[serde(flatten)]
39 pub creditor_tx: Option<Field50Creditor>,
40
41 #[serde(flatten)]
43 pub field_52: Option<Field52CreditorBank>,
44
45 #[serde(flatten)]
47 pub field_57: Option<Field57DebtorBank>,
48
49 #[serde(flatten)]
51 pub field_59: Field59Debtor,
52
53 #[serde(rename = "70")]
55 pub field_70: Option<Field70>,
56
57 #[serde(rename = "26T")]
59 pub field_26t: Option<Field26T>,
60
61 #[serde(rename = "77B")]
63 pub field_77b: Option<Field77B>,
64
65 #[serde(rename = "33B")]
67 pub field_33b: Option<Field33B>,
68
69 #[serde(rename = "71A")]
71 pub field_71a: Option<Field71A>,
72
73 #[serde(rename = "71F")]
75 pub field_71f: Option<Field71F>,
76
77 #[serde(rename = "71G")]
79 pub field_71g: Option<Field71G>,
80
81 #[serde(rename = "36")]
83 pub field_36: Option<Field36>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94pub struct MT104 {
95 #[serde(rename = "20")]
97 pub field_20: Field20,
98
99 #[serde(rename = "21R")]
101 pub field_21r: Option<Field21R>,
102
103 #[serde(rename = "23E")]
105 pub field_23e: Option<Field23E>,
106
107 #[serde(rename = "21E")]
109 pub field_21e: Option<Field21E>,
110
111 #[serde(rename = "30")]
113 pub field_30: Field30,
114
115 #[serde(rename = "51A")]
117 pub field_51a: Option<Field51A>,
118
119 #[serde(flatten)]
121 pub instructing_party: Option<Field50InstructingParty>,
122
123 #[serde(flatten)]
125 pub creditor: Option<Field50Creditor>,
126
127 #[serde(flatten)]
129 pub field_52: Option<Field52CreditorBank>,
130
131 #[serde(rename = "26T")]
133 pub field_26t: Option<Field26T>,
134
135 #[serde(rename = "77B")]
137 pub field_77b: Option<Field77B>,
138
139 #[serde(rename = "71A")]
141 pub field_71a: Option<Field71A>,
142
143 #[serde(rename = "72")]
145 pub field_72: Option<Field72>,
146
147 #[serde(rename = "#")]
149 pub transactions: Vec<MT104Transaction>,
150
151 #[serde(rename = "32B")]
153 pub field_32b: Option<Field32B>,
154
155 #[serde(rename = "19")]
157 pub field_19: Option<Field19>,
158
159 #[serde(rename = "71F")]
161 pub field_71f: Option<Field71F>,
162
163 #[serde(rename = "71G")]
165 pub field_71g: Option<Field71G>,
166
167 #[serde(flatten)]
169 pub field_53: Option<Field53SenderCorrespondent>,
170}
171
172impl MT104 {
173 pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
175 let mut parser = crate::parser::MessageParser::new(block4, "104");
176
177 let field_20 = parser.parse_field::<Field20>("20")?;
179 let field_21r = parser.parse_optional_field::<Field21R>("21R")?;
180 let field_23e = parser.parse_optional_field::<Field23E>("23E")?;
181 let field_21e = parser.parse_optional_field::<Field21E>("21E")?;
182 let field_30 = parser.parse_field::<Field30>("30")?;
183 let field_51a = parser.parse_optional_field::<Field51A>("51A")?;
184
185 let mut instructing_party = None;
187 let mut creditor = None;
188
189 if let Some(variant) = parser.peek_field_variant("50") {
191 match variant.as_str() {
192 "C" | "L" => {
193 instructing_party =
195 parser.parse_optional_variant_field::<Field50InstructingParty>("50")?;
196 }
197 "A" | "K" => {
198 creditor = parser.parse_optional_variant_field::<Field50Creditor>("50")?;
200 }
201 _ => {
202 if let Ok(ip) =
204 parser.parse_optional_variant_field::<Field50InstructingParty>("50")
205 {
206 instructing_party = ip;
207 } else {
208 creditor = parser.parse_optional_variant_field::<Field50Creditor>("50")?;
209 }
210 }
211 }
212 }
213
214 let field_52 = parser.parse_optional_variant_field::<Field52CreditorBank>("52")?;
215 let field_26t = parser.parse_optional_field::<Field26T>("26T")?;
216 let field_77b = parser.parse_optional_field::<Field77B>("77B")?;
217 let field_71a = parser.parse_optional_field::<Field71A>("71A")?;
218 let field_72 = parser.parse_optional_field::<Field72>("72")?;
219
220 let mut transactions = Vec::new();
222 parser = parser.with_duplicates(true);
223
224 while parser.detect_field("21") {
225 let field_21 = parser.parse_field::<Field21NoOption>("21")?;
226 let field_23e_tx = parser.parse_optional_field::<Field23E>("23E")?;
227 let field_21c = parser.parse_optional_field::<Field21C>("21C")?;
228 let field_21d = parser.parse_optional_field::<Field21D>("21D")?;
229 let field_21e_tx = parser.parse_optional_field::<Field21E>("21E")?;
230 let field_32b = parser.parse_field::<Field32B>("32B")?;
231
232 let mut instructing_party_tx = None;
234 let mut creditor_tx = None;
235
236 if let Some(variant) = parser.peek_field_variant("50") {
238 match variant.as_str() {
239 "C" | "L" => {
240 instructing_party_tx =
242 parser.parse_optional_variant_field::<Field50InstructingParty>("50")?;
243 }
244 "A" | "K" => {
245 creditor_tx =
247 parser.parse_optional_variant_field::<Field50Creditor>("50")?;
248 }
249 _ => {
250 if let Ok(ip) =
252 parser.parse_optional_variant_field::<Field50InstructingParty>("50")
253 {
254 instructing_party_tx = ip;
255 } else {
256 creditor_tx =
257 parser.parse_optional_variant_field::<Field50Creditor>("50")?;
258 }
259 }
260 }
261 }
262
263 let field_52_tx = parser.parse_optional_variant_field::<Field52CreditorBank>("52")?;
264 let field_57 = parser.parse_optional_variant_field::<Field57DebtorBank>("57")?;
265 let field_59 = parser.parse_variant_field::<Field59Debtor>("59")?;
266 let field_70 = parser.parse_optional_field::<Field70>("70")?;
267 let field_26t_tx = parser.parse_optional_field::<Field26T>("26T")?;
268 let field_77b_tx = parser.parse_optional_field::<Field77B>("77B")?;
269 let field_33b = parser.parse_optional_field::<Field33B>("33B")?;
270 let field_71a_tx = parser.parse_optional_field::<Field71A>("71A")?;
271 let field_71f = parser.parse_optional_field::<Field71F>("71F")?;
272 let field_71g = parser.parse_optional_field::<Field71G>("71G")?;
273 let field_36 = parser.parse_optional_field::<Field36>("36")?;
274
275 transactions.push(MT104Transaction {
276 field_21,
277 field_23e: field_23e_tx,
278 field_21c,
279 field_21d,
280 field_21e: field_21e_tx,
281 field_32b,
282 instructing_party_tx,
283 creditor_tx,
284 field_52: field_52_tx,
285 field_57,
286 field_59,
287 field_70,
288 field_26t: field_26t_tx,
289 field_77b: field_77b_tx,
290 field_33b,
291 field_71a: field_71a_tx,
292 field_71f,
293 field_71g,
294 field_36,
295 });
296 }
297
298 let field_32b = parser.parse_optional_field::<Field32B>("32B")?;
300 let field_19 = parser.parse_optional_field::<Field19>("19")?;
301 let field_71f = parser.parse_optional_field::<Field71F>("71F")?;
302 let field_71g = parser.parse_optional_field::<Field71G>("71G")?;
303 let field_53 = parser.parse_optional_variant_field::<Field53SenderCorrespondent>("53")?;
304
305 verify_parser_complete(&parser)?;
307
308 Ok(Self {
309 field_20,
310 field_21r,
311 field_23e,
312 field_21e,
313 field_30,
314 field_51a,
315 instructing_party,
316 creditor,
317 field_52,
318 field_26t,
319 field_77b,
320 field_71a,
321 field_72,
322 transactions,
323 field_32b,
324 field_19,
325 field_71f,
326 field_71g,
327 field_53,
328 })
329 }
330
331 pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
333 let block4 = extract_block4(input)?;
334 Self::parse_from_block4(&block4)
335 }
336
337 pub fn to_mt_string(&self) -> String {
339 let mut result = String::new();
340
341 append_field(&mut result, &self.field_20);
343 append_optional_field(&mut result, &self.field_21r);
344 append_optional_field(&mut result, &self.field_23e);
345 append_optional_field(&mut result, &self.field_21e);
346 append_field(&mut result, &self.field_30);
347 append_optional_field(&mut result, &self.field_51a);
348 append_optional_field(&mut result, &self.instructing_party);
349 append_optional_field(&mut result, &self.creditor);
350 append_optional_field(&mut result, &self.field_52);
351 append_optional_field(&mut result, &self.field_26t);
352 append_optional_field(&mut result, &self.field_77b);
353 append_optional_field(&mut result, &self.field_71a);
354 append_optional_field(&mut result, &self.field_72);
355
356 for transaction in &self.transactions {
358 append_field(&mut result, &transaction.field_21);
359 append_optional_field(&mut result, &transaction.field_23e);
360 append_optional_field(&mut result, &transaction.field_21c);
361 append_optional_field(&mut result, &transaction.field_21d);
362 append_optional_field(&mut result, &transaction.field_21e);
363 append_field(&mut result, &transaction.field_32b);
364 append_optional_field(&mut result, &transaction.instructing_party_tx);
365 append_optional_field(&mut result, &transaction.creditor_tx);
366 append_optional_field(&mut result, &transaction.field_52);
367 append_optional_field(&mut result, &transaction.field_57);
368 append_field(&mut result, &transaction.field_59);
369 append_optional_field(&mut result, &transaction.field_70);
370 append_optional_field(&mut result, &transaction.field_26t);
371 append_optional_field(&mut result, &transaction.field_77b);
372 append_optional_field(&mut result, &transaction.field_33b);
373 append_optional_field(&mut result, &transaction.field_71a);
374 append_optional_field(&mut result, &transaction.field_71f);
375 append_optional_field(&mut result, &transaction.field_71g);
376 append_optional_field(&mut result, &transaction.field_36);
377 }
378
379 append_optional_field(&mut result, &self.field_32b);
381 append_optional_field(&mut result, &self.field_19);
382 append_optional_field(&mut result, &self.field_71f);
383 append_optional_field(&mut result, &self.field_71g);
384 append_optional_field(&mut result, &self.field_53);
385
386 finalize_mt_string(result, false)
387 }
388
389 const MT104_VALID_23E_CODES_SEQ_A: &'static [&'static str] =
395 &["AUTH", "NAUT", "OTHR", "RFDD", "RTND"];
396
397 const MT104_VALID_23E_CODES_SEQ_B: &'static [&'static str] = &["AUTH", "NAUT", "OTHR"];
399
400 const CODE_WITH_ADDITIONAL_INFO: &'static str = "OTHR";
402
403 fn has_sequence_c(&self) -> bool {
409 self.field_32b.is_some()
410 }
411
412 fn has_rfdd_in_seq_a(&self) -> bool {
414 self.field_23e
415 .as_ref()
416 .is_some_and(|f| f.instruction_code == "RFDD")
417 }
418
419 fn has_rtnd_in_seq_a(&self) -> bool {
421 self.field_23e
422 .as_ref()
423 .is_some_and(|f| f.instruction_code == "RTND")
424 }
425
426 fn has_creditor_in_seq_a(&self) -> bool {
428 self.creditor.is_some()
429 }
430
431 fn has_creditor_in_all_seq_b(&self) -> bool {
433 !self.transactions.is_empty() && self.transactions.iter().all(|tx| tx.creditor_tx.is_some())
434 }
435
436 fn has_creditor_in_any_seq_b(&self) -> bool {
438 self.transactions.iter().any(|tx| tx.creditor_tx.is_some())
439 }
440
441 fn has_instructing_party_in_seq_a(&self) -> bool {
443 self.instructing_party.is_some()
444 }
445
446 fn has_instructing_party_in_any_seq_b(&self) -> bool {
448 self.transactions
449 .iter()
450 .any(|tx| tx.instructing_party_tx.is_some())
451 }
452
453 fn has_21e_in_seq_a(&self) -> bool {
455 self.field_21e.is_some()
456 }
457
458 fn has_21e_in_any_seq_b(&self) -> bool {
460 self.transactions.iter().any(|tx| tx.field_21e.is_some())
461 }
462
463 fn has_26t_in_seq_a(&self) -> bool {
465 self.field_26t.is_some()
466 }
467
468 fn has_26t_in_any_seq_b(&self) -> bool {
470 self.transactions.iter().any(|tx| tx.field_26t.is_some())
471 }
472
473 fn has_52a_in_seq_a(&self) -> bool {
475 self.field_52.is_some()
476 }
477
478 fn has_52a_in_any_seq_b(&self) -> bool {
480 self.transactions.iter().any(|tx| tx.field_52.is_some())
481 }
482
483 fn has_71a_in_seq_a(&self) -> bool {
485 self.field_71a.is_some()
486 }
487
488 fn has_71a_in_any_seq_b(&self) -> bool {
490 self.transactions.iter().any(|tx| tx.field_71a.is_some())
491 }
492
493 fn has_77b_in_seq_a(&self) -> bool {
495 self.field_77b.is_some()
496 }
497
498 fn has_77b_in_any_seq_b(&self) -> bool {
500 self.transactions.iter().any(|tx| tx.field_77b.is_some())
501 }
502
503 fn has_71f_in_any_seq_b(&self) -> bool {
505 self.transactions.iter().any(|tx| tx.field_71f.is_some())
506 }
507
508 fn has_71f_in_seq_c(&self) -> bool {
510 self.field_71f.is_some()
511 }
512
513 fn has_71g_in_any_seq_b(&self) -> bool {
515 self.transactions.iter().any(|tx| tx.field_71g.is_some())
516 }
517
518 fn has_71g_in_seq_c(&self) -> bool {
520 self.field_71g.is_some()
521 }
522
523 fn validate_c1_field_23e_dependencies(&self) -> Vec<SwiftValidationError> {
529 let mut errors = Vec::new();
530
531 if let Some(ref field_23e_a) = self.field_23e {
532 if field_23e_a.instruction_code == "RFDD" {
533 for (idx, transaction) in self.transactions.iter().enumerate() {
535 if transaction.field_23e.is_none() {
536 errors.push(SwiftValidationError::content_error(
537 "C75",
538 "23E",
539 "",
540 &format!(
541 "Transaction {}: Field 23E is mandatory in Sequence B when field 23E in Sequence A contains RFDD",
542 idx + 1
543 ),
544 "If field 23E is present in sequence A and contains RFDD then field 23E must be present in all occurrences of sequence B",
545 ));
546 }
547 }
548 } else {
549 for (idx, transaction) in self.transactions.iter().enumerate() {
551 if transaction.field_23e.is_some() {
552 errors.push(SwiftValidationError::content_error(
553 "C75",
554 "23E",
555 "",
556 &format!(
557 "Transaction {}: Field 23E must not be present in Sequence B when field 23E in Sequence A does not contain RFDD",
558 idx + 1
559 ),
560 "If field 23E is present in sequence A and does not contain RFDD then field 23E must not be present in any occurrence of sequence B",
561 ));
562 }
563 }
564 }
565 } else {
566 for (idx, transaction) in self.transactions.iter().enumerate() {
568 if transaction.field_23e.is_none() {
569 errors.push(SwiftValidationError::content_error(
570 "C75",
571 "23E",
572 "",
573 &format!(
574 "Transaction {}: Field 23E is mandatory in Sequence B when field 23E is not present in Sequence A",
575 idx + 1
576 ),
577 "If field 23E is not present in sequence A then field 23E must be present in all occurrences of sequence B",
578 ));
579 }
580 }
581 }
582
583 errors
584 }
585
586 fn validate_c2_creditor_field(&self) -> Option<SwiftValidationError> {
588 let in_seq_a = self.has_creditor_in_seq_a();
589 let in_all_seq_b = self.has_creditor_in_all_seq_b();
590 let in_any_seq_b = self.has_creditor_in_any_seq_b();
591
592 if in_seq_a && in_any_seq_b {
593 return Some(SwiftValidationError::content_error(
595 "C76",
596 "50a",
597 "",
598 "Field 50a (Creditor A/K) must not be present in both Sequence A and Sequence B",
599 "Field 50a (option A or K), must be present in either sequence A or in each occurrence of sequence B, but must never be present in both sequences",
600 ));
601 }
602
603 if !in_seq_a && !in_all_seq_b {
604 if in_any_seq_b {
605 return Some(SwiftValidationError::content_error(
607 "C76",
608 "50a",
609 "",
610 "Field 50a (Creditor A/K) must be present in every Sequence B transaction when not in Sequence A",
611 "Field 50a (option A or K), must be present in each occurrence of sequence B when not present in sequence A",
612 ));
613 } else {
614 return Some(SwiftValidationError::content_error(
616 "C76",
617 "50a",
618 "",
619 "Field 50a (Creditor A/K) must be present in either Sequence A or in every Sequence B transaction",
620 "Field 50a (option A or K), must be present in either sequence A or in each occurrence of sequence B, but must never be absent from both sequences",
621 ));
622 }
623 }
624
625 None
626 }
627
628 fn validate_c3_mutual_exclusivity(&self) -> Vec<SwiftValidationError> {
630 let mut errors = Vec::new();
631
632 if self.has_21e_in_seq_a() && self.has_21e_in_any_seq_b() {
634 errors.push(SwiftValidationError::content_error(
635 "D73",
636 "21E",
637 "",
638 "Field 21E must not be present in both Sequence A and Sequence B",
639 "When present in sequence A, field 21E must not be present in any occurrence of sequence B. When present in one or more occurrences of sequence B, field 21E must not be present in sequence A",
640 ));
641 }
642
643 if self.has_26t_in_seq_a() && self.has_26t_in_any_seq_b() {
645 errors.push(SwiftValidationError::content_error(
646 "D73",
647 "26T",
648 "",
649 "Field 26T must not be present in both Sequence A and Sequence B",
650 "When present in sequence A, field 26T must not be present in any occurrence of sequence B. When present in one or more occurrences of sequence B, field 26T must not be present in sequence A",
651 ));
652 }
653
654 if self.has_52a_in_seq_a() && self.has_52a_in_any_seq_b() {
656 errors.push(SwiftValidationError::content_error(
657 "D73",
658 "52a",
659 "",
660 "Field 52a must not be present in both Sequence A and Sequence B",
661 "When present in sequence A, field 52a must not be present in any occurrence of sequence B. When present in one or more occurrences of sequence B, field 52a must not be present in sequence A",
662 ));
663 }
664
665 if self.has_71a_in_seq_a() && self.has_71a_in_any_seq_b() {
667 errors.push(SwiftValidationError::content_error(
668 "D73",
669 "71A",
670 "",
671 "Field 71A must not be present in both Sequence A and Sequence B",
672 "When present in sequence A, field 71A must not be present in any occurrence of sequence B. When present in one or more occurrences of sequence B, field 71A must not be present in sequence A",
673 ));
674 }
675
676 if self.has_77b_in_seq_a() && self.has_77b_in_any_seq_b() {
678 errors.push(SwiftValidationError::content_error(
679 "D73",
680 "77B",
681 "",
682 "Field 77B must not be present in both Sequence A and Sequence B",
683 "When present in sequence A, field 77B must not be present in any occurrence of sequence B. When present in one or more occurrences of sequence B, field 77B must not be present in sequence A",
684 ));
685 }
686
687 if self.has_instructing_party_in_seq_a() && self.has_instructing_party_in_any_seq_b() {
689 errors.push(SwiftValidationError::content_error(
690 "D73",
691 "50a",
692 "",
693 "Field 50a (Instructing Party C/L) must not be present in both Sequence A and Sequence B",
694 "When present in sequence A, field 50a (option C or L) must not be present in any occurrence of sequence B. When present in one or more occurrences of sequence B, field 50a (option C or L) must not be present in sequence A",
695 ));
696 }
697
698 errors
699 }
700
701 fn validate_c4_registration_reference(&self) -> Vec<SwiftValidationError> {
703 let mut errors = Vec::new();
704
705 if self.field_21e.is_some() && self.creditor.is_none() {
707 errors.push(SwiftValidationError::content_error(
708 "D77",
709 "50a",
710 "",
711 "Field 50a (Creditor A/K) is mandatory in Sequence A when field 21E is present",
712 "If field 21E is present in sequence A, field 50a (option A or K), must also be present in sequence A",
713 ));
714 }
715
716 for (idx, transaction) in self.transactions.iter().enumerate() {
718 if transaction.field_21e.is_some() && transaction.creditor_tx.is_none() {
719 errors.push(SwiftValidationError::content_error(
720 "D77",
721 "50a",
722 "",
723 &format!(
724 "Transaction {}: Field 50a (Creditor A/K) is mandatory when field 21E is present",
725 idx + 1
726 ),
727 "In each occurrence of sequence B, if field 21E is present, then field 50a (option A or K), must also be present in the same occurrence",
728 ));
729 }
730 }
731
732 errors
733 }
734
735 fn validate_c5_field_72_rtnd(&self) -> Option<SwiftValidationError> {
737 let has_rtnd = self.has_rtnd_in_seq_a();
738 let has_field_72 = self.field_72.is_some();
739
740 if has_rtnd && !has_field_72 {
741 return Some(SwiftValidationError::content_error(
742 "C82",
743 "72",
744 "",
745 "Field 72 is mandatory when field 23E in Sequence A contains RTND",
746 "In sequence A, if field 23E is present and contains RTND then field 72 must be present",
747 ));
748 }
749
750 if !has_rtnd && has_field_72 {
751 return Some(SwiftValidationError::content_error(
752 "C82",
753 "72",
754 "",
755 "Field 72 is not allowed when field 23E in Sequence A does not contain RTND or is not present",
756 "In sequence A, if field 23E not present, or field 23E does not contain RTND - field 72 is not allowed",
757 ));
758 }
759
760 None
761 }
762
763 fn validate_c6_charges_dependencies(&self) -> Vec<SwiftValidationError> {
765 let mut errors = Vec::new();
766
767 let has_71f_b = self.has_71f_in_any_seq_b();
768 let has_71f_c = self.has_71f_in_seq_c();
769 let has_71g_b = self.has_71g_in_any_seq_b();
770 let has_71g_c = self.has_71g_in_seq_c();
771
772 if has_71f_b && !has_71f_c {
774 errors.push(SwiftValidationError::content_error(
775 "D79",
776 "71F",
777 "",
778 "Field 71F is mandatory in Sequence C when present in Sequence B",
779 "If field 71F is present in one or more occurrence of sequence B, then it must also be present in sequence C",
780 ));
781 }
782
783 if !has_71f_b && has_71f_c {
784 errors.push(SwiftValidationError::content_error(
785 "D79",
786 "71F",
787 "",
788 "Field 71F is not allowed in Sequence C when not present in Sequence B",
789 "If field 71F is not present in sequence B, then it must not be present in sequence C",
790 ));
791 }
792
793 if has_71g_b && !has_71g_c {
795 errors.push(SwiftValidationError::content_error(
796 "D79",
797 "71G",
798 "",
799 "Field 71G is mandatory in Sequence C when present in Sequence B",
800 "If field 71G is present in one or more occurrence of sequence B, then it must also be present in sequence C",
801 ));
802 }
803
804 if !has_71g_b && has_71g_c {
805 errors.push(SwiftValidationError::content_error(
806 "D79",
807 "71G",
808 "",
809 "Field 71G is not allowed in Sequence C when not present in Sequence B",
810 "If field 71G is not present in sequence B, then it must not be present in sequence C",
811 ));
812 }
813
814 errors
815 }
816
817 fn validate_c7_currency_amount_difference(&self) -> Vec<SwiftValidationError> {
819 let mut errors = Vec::new();
820
821 for (idx, transaction) in self.transactions.iter().enumerate() {
822 if let Some(ref field_33b) = transaction.field_33b {
823 let currency_32b = &transaction.field_32b.currency;
824 let currency_33b = &field_33b.currency;
825 let amount_32b = transaction.field_32b.amount;
826 let amount_33b = field_33b.amount;
827
828 if currency_32b == currency_33b && (amount_32b - amount_33b).abs() < 0.01 {
830 errors.push(SwiftValidationError::content_error(
831 "D21",
832 "33B",
833 &format!("{}{}", currency_33b, amount_33b),
834 &format!(
835 "Transaction {}: Currency code or amount, or both, must be different between fields 33B and 32B",
836 idx + 1
837 ),
838 "In each occurrence of sequence B, if field 33B is present then the currency code or the amount, or both, must be different between fields 33B and 32B",
839 ));
840 }
841 }
842 }
843
844 errors
845 }
846
847 fn validate_c8_exchange_rate(&self) -> Vec<SwiftValidationError> {
849 let mut errors = Vec::new();
850
851 for (idx, transaction) in self.transactions.iter().enumerate() {
852 if let Some(ref field_33b) = transaction.field_33b {
853 let currency_32b = &transaction.field_32b.currency;
854 let currency_33b = &field_33b.currency;
855
856 if currency_32b != currency_33b {
857 if transaction.field_36.is_none() {
859 errors.push(SwiftValidationError::content_error(
860 "D75",
861 "36",
862 "",
863 &format!(
864 "Transaction {}: Field 36 (Exchange Rate) is mandatory when currencies in fields 32B and 33B are different",
865 idx + 1
866 ),
867 "In any occurrence of sequence B, if field 33B is present and the currency codes in fields 32B and 33B are different, then field 36 must be present",
868 ));
869 }
870 } else {
871 if transaction.field_36.is_some() {
873 errors.push(SwiftValidationError::content_error(
874 "D75",
875 "36",
876 "",
877 &format!(
878 "Transaction {}: Field 36 (Exchange Rate) is not allowed when currencies in fields 32B and 33B are the same",
879 idx + 1
880 ),
881 "If field 33B is present and the currency codes in fields 32B and 33B are the same, then field 36 must not be present",
882 ));
883 }
884 }
885 } else {
886 if transaction.field_36.is_some() {
888 errors.push(SwiftValidationError::content_error(
889 "D75",
890 "36",
891 "",
892 &format!(
893 "Transaction {}: Field 36 (Exchange Rate) is not allowed when field 33B is not present",
894 idx + 1
895 ),
896 "Field 36 must not be present when field 33B is not present",
897 ));
898 }
899 }
900 }
901
902 errors
903 }
904
905 fn validate_c9_field_19(&self) -> Option<SwiftValidationError> {
907 if !self.has_sequence_c() {
908 return None; }
910
911 let field_32b_c = self.field_32b.as_ref()?;
912 let settlement_amount = field_32b_c.amount;
913
914 let sum_of_amounts: f64 = self.transactions.iter().map(|tx| tx.field_32b.amount).sum();
916
917 let amounts_equal = (settlement_amount - sum_of_amounts).abs() < 0.01;
918
919 if amounts_equal && self.field_19.is_some() {
920 return Some(SwiftValidationError::content_error(
921 "D80",
922 "19",
923 "",
924 "Field 19 must not be present when amount in field 32B of Sequence C equals the sum of amounts in field 32B of Sequence B",
925 "If sequence C is present and if the amount in field 32B of sequence C is equal to the sum of the amounts of the fields 32B of sequence B, then field 19 must not be present",
926 ));
927 }
928
929 if !amounts_equal && self.field_19.is_none() {
930 return Some(SwiftValidationError::content_error(
931 "D80",
932 "19",
933 "",
934 "Field 19 must be present when amount in field 32B of Sequence C is not equal to the sum of amounts in field 32B of Sequence B",
935 "If the amount in field 32B of sequence C is not equal to the sum of the amounts of the fields 32B of sequence B, then field 19 must be present",
936 ));
937 }
938
939 None
940 }
941
942 fn validate_c10_field_19_amount(&self) -> Option<SwiftValidationError> {
944 if let Some(ref field_19) = self.field_19 {
945 let sum_of_amounts: f64 = self.transactions.iter().map(|tx| tx.field_32b.amount).sum();
947
948 if (field_19.amount - sum_of_amounts).abs() > 0.01 {
949 return Some(SwiftValidationError::content_error(
950 "C01",
951 "19",
952 &field_19.amount.to_string(),
953 &format!(
954 "Field 19 amount ({}) must equal the sum of amounts in all occurrences of field 32B in Sequence B ({})",
955 field_19.amount, sum_of_amounts
956 ),
957 "If field 19 is present in sequence C then it must be equal to the sum of the amounts in all occurrences of field 32B in sequence B",
958 ));
959 }
960 }
961
962 None
963 }
964
965 fn validate_c11_currency_consistency(&self) -> Vec<SwiftValidationError> {
967 let mut errors = Vec::new();
968
969 let mut currencies_32b: Vec<&String> = self
971 .transactions
972 .iter()
973 .map(|tx| &tx.field_32b.currency)
974 .collect();
975
976 if let Some(ref field_32b_c) = self.field_32b {
978 currencies_32b.push(&field_32b_c.currency);
979 }
980
981 if !currencies_32b.is_empty() {
983 let first_currency_32b = currencies_32b[0];
984 for currency in currencies_32b.iter().skip(1) {
985 if *currency != first_currency_32b {
986 errors.push(SwiftValidationError::content_error(
987 "C02",
988 "32B",
989 currency,
990 &format!(
991 "Currency code in field 32B ({}) must be the same for all occurrences in the message (expected: {})",
992 currency, first_currency_32b
993 ),
994 "The currency code in fields 32B must be the same for all occurrences of these fields in the message",
995 ));
996 break; }
998 }
999 }
1000
1001 let mut currencies_71g: Vec<&String> = self
1003 .transactions
1004 .iter()
1005 .filter_map(|tx| tx.field_71g.as_ref().map(|f| &f.currency))
1006 .collect();
1007
1008 if let Some(ref field_71g_c) = self.field_71g {
1010 currencies_71g.push(&field_71g_c.currency);
1011 }
1012
1013 if !currencies_71g.is_empty() {
1015 let first_currency_71g = currencies_71g[0];
1016 for currency in currencies_71g.iter().skip(1) {
1017 if *currency != first_currency_71g {
1018 errors.push(SwiftValidationError::content_error(
1019 "C02",
1020 "71G",
1021 currency,
1022 &format!(
1023 "Currency code in field 71G ({}) must be the same for all occurrences in the message (expected: {})",
1024 currency, first_currency_71g
1025 ),
1026 "The currency code in field 71G in sequences B and C must be the same for all occurrences of these fields in the message",
1027 ));
1028 break; }
1030 }
1031 }
1032
1033 let mut currencies_71f: Vec<&String> = self
1035 .transactions
1036 .iter()
1037 .filter_map(|tx| tx.field_71f.as_ref().map(|f| &f.currency))
1038 .collect();
1039
1040 if let Some(ref field_71f_c) = self.field_71f {
1042 currencies_71f.push(&field_71f_c.currency);
1043 }
1044
1045 if !currencies_71f.is_empty() {
1047 let first_currency_71f = currencies_71f[0];
1048 for currency in currencies_71f.iter().skip(1) {
1049 if *currency != first_currency_71f {
1050 errors.push(SwiftValidationError::content_error(
1051 "C02",
1052 "71F",
1053 currency,
1054 &format!(
1055 "Currency code in field 71F ({}) must be the same for all occurrences in the message (expected: {})",
1056 currency, first_currency_71f
1057 ),
1058 "The currency code in the charges fields 71F (in sequences B and C) must be the same for all occurrences of these fields in the message",
1059 ));
1060 break; }
1062 }
1063 }
1064
1065 errors
1066 }
1067
1068 fn validate_c12_rfdd_comprehensive(&self) -> Vec<SwiftValidationError> {
1070 let mut errors = Vec::new();
1071 let has_rfdd = self.has_rfdd_in_seq_a();
1072
1073 if has_rfdd {
1074 for (idx, transaction) in self.transactions.iter().enumerate() {
1077 if transaction.field_21e.is_some() {
1078 errors.push(SwiftValidationError::content_error(
1079 "C96",
1080 "21E",
1081 "",
1082 &format!(
1083 "Transaction {}: Field 21E is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1084 idx + 1
1085 ),
1086 "In sequence A, if field 23E is present and contains RFDD, then in sequence B the fields 21E, 50a (option A or K), 52a, 71F, 71G must not be present",
1087 ));
1088 }
1089
1090 if transaction.creditor_tx.is_some() {
1091 errors.push(SwiftValidationError::content_error(
1092 "C96",
1093 "50a",
1094 "",
1095 &format!(
1096 "Transaction {}: Field 50a (Creditor A/K) is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1097 idx + 1
1098 ),
1099 "In sequence A, if field 23E is present and contains RFDD, then in sequence B the fields 21E, 50a (option A or K), 52a, 71F, 71G must not be present",
1100 ));
1101 }
1102
1103 if transaction.field_52.is_some() {
1104 errors.push(SwiftValidationError::content_error(
1105 "C96",
1106 "52a",
1107 "",
1108 &format!(
1109 "Transaction {}: Field 52a is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1110 idx + 1
1111 ),
1112 "In sequence A, if field 23E is present and contains RFDD, then in sequence B the fields 21E, 50a (option A or K), 52a, 71F, 71G must not be present",
1113 ));
1114 }
1115
1116 if transaction.field_71f.is_some() {
1117 errors.push(SwiftValidationError::content_error(
1118 "C96",
1119 "71F",
1120 "",
1121 &format!(
1122 "Transaction {}: Field 71F is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1123 idx + 1
1124 ),
1125 "In sequence A, if field 23E is present and contains RFDD, then in sequence B the fields 21E, 50a (option A or K), 52a, 71F, 71G must not be present",
1126 ));
1127 }
1128
1129 if transaction.field_71g.is_some() {
1130 errors.push(SwiftValidationError::content_error(
1131 "C96",
1132 "71G",
1133 "",
1134 &format!(
1135 "Transaction {}: Field 71G is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1136 idx + 1
1137 ),
1138 "In sequence A, if field 23E is present and contains RFDD, then in sequence B the fields 21E, 50a (option A or K), 52a, 71F, 71G must not be present",
1139 ));
1140 }
1141 }
1142
1143 if self.has_sequence_c() {
1145 errors.push(SwiftValidationError::content_error(
1146 "C96",
1147 "32B",
1148 "",
1149 "Sequence C is not allowed when field 23E in Sequence A contains RFDD",
1150 "In sequence A, if field 23E is present and contains RFDD, then sequence C must not be present",
1151 ));
1152 }
1153 } else {
1154 if self.field_21r.is_some() {
1157 errors.push(SwiftValidationError::content_error(
1158 "C96",
1159 "21R",
1160 "",
1161 "Field 21R is not allowed in Sequence A when field 23E does not contain RFDD or is not present",
1162 "In sequence A field 23E does not contain RFDD or field 23E is not present, in sequence A field 21R must not be present",
1163 ));
1164 }
1165
1166 if !self.has_sequence_c() {
1168 errors.push(SwiftValidationError::content_error(
1169 "C96",
1170 "32B",
1171 "",
1172 "Sequence C is mandatory when field 23E in Sequence A does not contain RFDD or is not present",
1173 "In sequence A field 23E does not contain RFDD or field 23E is not present, sequence C must be present",
1174 ));
1175 }
1176 }
1177
1178 errors
1179 }
1180
1181 fn validate_field_23e_seq_a(&self) -> Vec<SwiftValidationError> {
1183 let mut errors = Vec::new();
1184
1185 if let Some(ref field_23e) = self.field_23e {
1186 let code = &field_23e.instruction_code;
1187
1188 if !Self::MT104_VALID_23E_CODES_SEQ_A.contains(&code.as_str()) {
1190 errors.push(SwiftValidationError::format_error(
1191 "T47",
1192 "23E",
1193 code,
1194 &format!("One of: {}", Self::MT104_VALID_23E_CODES_SEQ_A.join(", ")),
1195 &format!(
1196 "Sequence A: Instruction code '{}' is not valid for MT104. Valid codes: {}",
1197 code,
1198 Self::MT104_VALID_23E_CODES_SEQ_A.join(", ")
1199 ),
1200 ));
1201 }
1202
1203 if field_23e.additional_info.is_some() && code != Self::CODE_WITH_ADDITIONAL_INFO {
1205 errors.push(SwiftValidationError::content_error(
1206 "D81",
1207 "23E",
1208 code,
1209 &format!(
1210 "Sequence A: Additional information is only allowed for code OTHR. Code '{}' does not allow additional information",
1211 code
1212 ),
1213 "The narrative second subfield can only be used in combination with OTHR",
1214 ));
1215 }
1216 }
1217
1218 errors
1219 }
1220
1221 fn validate_field_23e_seq_b(&self) -> Vec<SwiftValidationError> {
1223 let mut errors = Vec::new();
1224
1225 for (idx, transaction) in self.transactions.iter().enumerate() {
1226 if let Some(ref field_23e) = transaction.field_23e {
1227 let code = &field_23e.instruction_code;
1228
1229 if !Self::MT104_VALID_23E_CODES_SEQ_B.contains(&code.as_str()) {
1231 errors.push(SwiftValidationError::format_error(
1232 "T47",
1233 "23E",
1234 code,
1235 &format!("One of: {}", Self::MT104_VALID_23E_CODES_SEQ_B.join(", ")),
1236 &format!(
1237 "Transaction {}: Instruction code '{}' is not valid for MT104 Sequence B. Valid codes: {}",
1238 idx + 1,
1239 code,
1240 Self::MT104_VALID_23E_CODES_SEQ_B.join(", ")
1241 ),
1242 ));
1243 }
1244
1245 if field_23e.additional_info.is_some() && code != Self::CODE_WITH_ADDITIONAL_INFO {
1247 errors.push(SwiftValidationError::content_error(
1248 "D81",
1249 "23E",
1250 code,
1251 &format!(
1252 "Transaction {}: Additional information is only allowed for code OTHR. Code '{}' does not allow additional information",
1253 idx + 1,
1254 code
1255 ),
1256 "The narrative second subfield can only be used in combination with OTHR",
1257 ));
1258 }
1259 }
1260 }
1261
1262 errors
1263 }
1264
1265 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
1268 let mut all_errors = Vec::new();
1269
1270 let c1_errors = self.validate_c1_field_23e_dependencies();
1272 all_errors.extend(c1_errors);
1273 if stop_on_first_error && !all_errors.is_empty() {
1274 return all_errors;
1275 }
1276
1277 if let Some(error) = self.validate_c2_creditor_field() {
1279 all_errors.push(error);
1280 if stop_on_first_error {
1281 return all_errors;
1282 }
1283 }
1284
1285 let c3_errors = self.validate_c3_mutual_exclusivity();
1287 all_errors.extend(c3_errors);
1288 if stop_on_first_error && !all_errors.is_empty() {
1289 return all_errors;
1290 }
1291
1292 let c4_errors = self.validate_c4_registration_reference();
1294 all_errors.extend(c4_errors);
1295 if stop_on_first_error && !all_errors.is_empty() {
1296 return all_errors;
1297 }
1298
1299 if let Some(error) = self.validate_c5_field_72_rtnd() {
1301 all_errors.push(error);
1302 if stop_on_first_error {
1303 return all_errors;
1304 }
1305 }
1306
1307 let c6_errors = self.validate_c6_charges_dependencies();
1309 all_errors.extend(c6_errors);
1310 if stop_on_first_error && !all_errors.is_empty() {
1311 return all_errors;
1312 }
1313
1314 let c7_errors = self.validate_c7_currency_amount_difference();
1316 all_errors.extend(c7_errors);
1317 if stop_on_first_error && !all_errors.is_empty() {
1318 return all_errors;
1319 }
1320
1321 let c8_errors = self.validate_c8_exchange_rate();
1323 all_errors.extend(c8_errors);
1324 if stop_on_first_error && !all_errors.is_empty() {
1325 return all_errors;
1326 }
1327
1328 if let Some(error) = self.validate_c9_field_19() {
1330 all_errors.push(error);
1331 if stop_on_first_error {
1332 return all_errors;
1333 }
1334 }
1335
1336 if let Some(error) = self.validate_c10_field_19_amount() {
1338 all_errors.push(error);
1339 if stop_on_first_error {
1340 return all_errors;
1341 }
1342 }
1343
1344 let c11_errors = self.validate_c11_currency_consistency();
1346 all_errors.extend(c11_errors);
1347 if stop_on_first_error && !all_errors.is_empty() {
1348 return all_errors;
1349 }
1350
1351 let c12_errors = self.validate_c12_rfdd_comprehensive();
1353 all_errors.extend(c12_errors);
1354 if stop_on_first_error && !all_errors.is_empty() {
1355 return all_errors;
1356 }
1357
1358 let f23e_a_errors = self.validate_field_23e_seq_a();
1360 all_errors.extend(f23e_a_errors);
1361 if stop_on_first_error && !all_errors.is_empty() {
1362 return all_errors;
1363 }
1364
1365 let f23e_b_errors = self.validate_field_23e_seq_b();
1367 all_errors.extend(f23e_b_errors);
1368
1369 all_errors
1370 }
1371}
1372
1373impl crate::traits::SwiftMessageBody for MT104 {
1374 fn message_type() -> &'static str {
1375 "104"
1376 }
1377
1378 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
1379 MT104::parse_from_block4(block4)
1381 }
1382
1383 fn to_mt_string(&self) -> String {
1384 MT104::to_mt_string(self)
1386 }
1387
1388 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
1389 MT104::validate_network_rules(self, stop_on_first_error)
1391 }
1392}