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 result.push('-');
387 result
388 }
389
390 const MT104_VALID_23E_CODES_SEQ_A: &'static [&'static str] =
396 &["AUTH", "NAUT", "OTHR", "RFDD", "RTND"];
397
398 const MT104_VALID_23E_CODES_SEQ_B: &'static [&'static str] = &["AUTH", "NAUT", "OTHR"];
400
401 const CODE_WITH_ADDITIONAL_INFO: &'static str = "OTHR";
403
404 fn has_sequence_c(&self) -> bool {
410 self.field_32b.is_some()
411 }
412
413 fn has_rfdd_in_seq_a(&self) -> bool {
415 self.field_23e
416 .as_ref()
417 .is_some_and(|f| f.instruction_code == "RFDD")
418 }
419
420 fn has_rtnd_in_seq_a(&self) -> bool {
422 self.field_23e
423 .as_ref()
424 .is_some_and(|f| f.instruction_code == "RTND")
425 }
426
427 fn has_creditor_in_seq_a(&self) -> bool {
429 self.creditor.is_some()
430 }
431
432 fn has_creditor_in_all_seq_b(&self) -> bool {
434 !self.transactions.is_empty() && self.transactions.iter().all(|tx| tx.creditor_tx.is_some())
435 }
436
437 fn has_creditor_in_any_seq_b(&self) -> bool {
439 self.transactions.iter().any(|tx| tx.creditor_tx.is_some())
440 }
441
442 fn has_instructing_party_in_seq_a(&self) -> bool {
444 self.instructing_party.is_some()
445 }
446
447 fn has_instructing_party_in_any_seq_b(&self) -> bool {
449 self.transactions
450 .iter()
451 .any(|tx| tx.instructing_party_tx.is_some())
452 }
453
454 fn has_21e_in_seq_a(&self) -> bool {
456 self.field_21e.is_some()
457 }
458
459 fn has_21e_in_any_seq_b(&self) -> bool {
461 self.transactions.iter().any(|tx| tx.field_21e.is_some())
462 }
463
464 fn has_26t_in_seq_a(&self) -> bool {
466 self.field_26t.is_some()
467 }
468
469 fn has_26t_in_any_seq_b(&self) -> bool {
471 self.transactions.iter().any(|tx| tx.field_26t.is_some())
472 }
473
474 fn has_52a_in_seq_a(&self) -> bool {
476 self.field_52.is_some()
477 }
478
479 fn has_52a_in_any_seq_b(&self) -> bool {
481 self.transactions.iter().any(|tx| tx.field_52.is_some())
482 }
483
484 fn has_71a_in_seq_a(&self) -> bool {
486 self.field_71a.is_some()
487 }
488
489 fn has_71a_in_any_seq_b(&self) -> bool {
491 self.transactions.iter().any(|tx| tx.field_71a.is_some())
492 }
493
494 fn has_77b_in_seq_a(&self) -> bool {
496 self.field_77b.is_some()
497 }
498
499 fn has_77b_in_any_seq_b(&self) -> bool {
501 self.transactions.iter().any(|tx| tx.field_77b.is_some())
502 }
503
504 fn has_71f_in_any_seq_b(&self) -> bool {
506 self.transactions.iter().any(|tx| tx.field_71f.is_some())
507 }
508
509 fn has_71f_in_seq_c(&self) -> bool {
511 self.field_71f.is_some()
512 }
513
514 fn has_71g_in_any_seq_b(&self) -> bool {
516 self.transactions.iter().any(|tx| tx.field_71g.is_some())
517 }
518
519 fn has_71g_in_seq_c(&self) -> bool {
521 self.field_71g.is_some()
522 }
523
524 fn validate_c1_field_23e_dependencies(&self) -> Vec<SwiftValidationError> {
530 let mut errors = Vec::new();
531
532 if let Some(ref field_23e_a) = self.field_23e {
533 if field_23e_a.instruction_code == "RFDD" {
534 for (idx, transaction) in self.transactions.iter().enumerate() {
536 if transaction.field_23e.is_none() {
537 errors.push(SwiftValidationError::content_error(
538 "C75",
539 "23E",
540 "",
541 &format!(
542 "Transaction {}: Field 23E is mandatory in Sequence B when field 23E in Sequence A contains RFDD",
543 idx + 1
544 ),
545 "If field 23E is present in sequence A and contains RFDD then field 23E must be present in all occurrences of sequence B",
546 ));
547 }
548 }
549 } else {
550 for (idx, transaction) in self.transactions.iter().enumerate() {
552 if transaction.field_23e.is_some() {
553 errors.push(SwiftValidationError::content_error(
554 "C75",
555 "23E",
556 "",
557 &format!(
558 "Transaction {}: Field 23E must not be present in Sequence B when field 23E in Sequence A does not contain RFDD",
559 idx + 1
560 ),
561 "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",
562 ));
563 }
564 }
565 }
566 } else {
567 for (idx, transaction) in self.transactions.iter().enumerate() {
569 if transaction.field_23e.is_none() {
570 errors.push(SwiftValidationError::content_error(
571 "C75",
572 "23E",
573 "",
574 &format!(
575 "Transaction {}: Field 23E is mandatory in Sequence B when field 23E is not present in Sequence A",
576 idx + 1
577 ),
578 "If field 23E is not present in sequence A then field 23E must be present in all occurrences of sequence B",
579 ));
580 }
581 }
582 }
583
584 errors
585 }
586
587 fn validate_c2_creditor_field(&self) -> Option<SwiftValidationError> {
589 let in_seq_a = self.has_creditor_in_seq_a();
590 let in_all_seq_b = self.has_creditor_in_all_seq_b();
591 let in_any_seq_b = self.has_creditor_in_any_seq_b();
592
593 if in_seq_a && in_any_seq_b {
594 return Some(SwiftValidationError::content_error(
596 "C76",
597 "50a",
598 "",
599 "Field 50a (Creditor A/K) must not be present in both Sequence A and Sequence B",
600 "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",
601 ));
602 }
603
604 if !in_seq_a && !in_all_seq_b {
605 if in_any_seq_b {
606 return Some(SwiftValidationError::content_error(
608 "C76",
609 "50a",
610 "",
611 "Field 50a (Creditor A/K) must be present in every Sequence B transaction when not in Sequence A",
612 "Field 50a (option A or K), must be present in each occurrence of sequence B when not present in sequence A",
613 ));
614 } else {
615 return Some(SwiftValidationError::content_error(
617 "C76",
618 "50a",
619 "",
620 "Field 50a (Creditor A/K) must be present in either Sequence A or in every Sequence B transaction",
621 "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",
622 ));
623 }
624 }
625
626 None
627 }
628
629 fn validate_c3_mutual_exclusivity(&self) -> Vec<SwiftValidationError> {
631 let mut errors = Vec::new();
632
633 if self.has_21e_in_seq_a() && self.has_21e_in_any_seq_b() {
635 errors.push(SwiftValidationError::content_error(
636 "D73",
637 "21E",
638 "",
639 "Field 21E must not be present in both Sequence A and Sequence B",
640 "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",
641 ));
642 }
643
644 if self.has_26t_in_seq_a() && self.has_26t_in_any_seq_b() {
646 errors.push(SwiftValidationError::content_error(
647 "D73",
648 "26T",
649 "",
650 "Field 26T must not be present in both Sequence A and Sequence B",
651 "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",
652 ));
653 }
654
655 if self.has_52a_in_seq_a() && self.has_52a_in_any_seq_b() {
657 errors.push(SwiftValidationError::content_error(
658 "D73",
659 "52a",
660 "",
661 "Field 52a must not be present in both Sequence A and Sequence B",
662 "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",
663 ));
664 }
665
666 if self.has_71a_in_seq_a() && self.has_71a_in_any_seq_b() {
668 errors.push(SwiftValidationError::content_error(
669 "D73",
670 "71A",
671 "",
672 "Field 71A must not be present in both Sequence A and Sequence B",
673 "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",
674 ));
675 }
676
677 if self.has_77b_in_seq_a() && self.has_77b_in_any_seq_b() {
679 errors.push(SwiftValidationError::content_error(
680 "D73",
681 "77B",
682 "",
683 "Field 77B must not be present in both Sequence A and Sequence B",
684 "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",
685 ));
686 }
687
688 if self.has_instructing_party_in_seq_a() && self.has_instructing_party_in_any_seq_b() {
690 errors.push(SwiftValidationError::content_error(
691 "D73",
692 "50a",
693 "",
694 "Field 50a (Instructing Party C/L) must not be present in both Sequence A and Sequence B",
695 "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",
696 ));
697 }
698
699 errors
700 }
701
702 fn validate_c4_registration_reference(&self) -> Vec<SwiftValidationError> {
704 let mut errors = Vec::new();
705
706 if self.field_21e.is_some() && self.creditor.is_none() {
708 errors.push(SwiftValidationError::content_error(
709 "D77",
710 "50a",
711 "",
712 "Field 50a (Creditor A/K) is mandatory in Sequence A when field 21E is present",
713 "If field 21E is present in sequence A, field 50a (option A or K), must also be present in sequence A",
714 ));
715 }
716
717 for (idx, transaction) in self.transactions.iter().enumerate() {
719 if transaction.field_21e.is_some() && transaction.creditor_tx.is_none() {
720 errors.push(SwiftValidationError::content_error(
721 "D77",
722 "50a",
723 "",
724 &format!(
725 "Transaction {}: Field 50a (Creditor A/K) is mandatory when field 21E is present",
726 idx + 1
727 ),
728 "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",
729 ));
730 }
731 }
732
733 errors
734 }
735
736 fn validate_c5_field_72_rtnd(&self) -> Option<SwiftValidationError> {
738 let has_rtnd = self.has_rtnd_in_seq_a();
739 let has_field_72 = self.field_72.is_some();
740
741 if has_rtnd && !has_field_72 {
742 return Some(SwiftValidationError::content_error(
743 "C82",
744 "72",
745 "",
746 "Field 72 is mandatory when field 23E in Sequence A contains RTND",
747 "In sequence A, if field 23E is present and contains RTND then field 72 must be present",
748 ));
749 }
750
751 if !has_rtnd && has_field_72 {
752 return Some(SwiftValidationError::content_error(
753 "C82",
754 "72",
755 "",
756 "Field 72 is not allowed when field 23E in Sequence A does not contain RTND or is not present",
757 "In sequence A, if field 23E not present, or field 23E does not contain RTND - field 72 is not allowed",
758 ));
759 }
760
761 None
762 }
763
764 fn validate_c6_charges_dependencies(&self) -> Vec<SwiftValidationError> {
766 let mut errors = Vec::new();
767
768 let has_71f_b = self.has_71f_in_any_seq_b();
769 let has_71f_c = self.has_71f_in_seq_c();
770 let has_71g_b = self.has_71g_in_any_seq_b();
771 let has_71g_c = self.has_71g_in_seq_c();
772
773 if has_71f_b && !has_71f_c {
775 errors.push(SwiftValidationError::content_error(
776 "D79",
777 "71F",
778 "",
779 "Field 71F is mandatory in Sequence C when present in Sequence B",
780 "If field 71F is present in one or more occurrence of sequence B, then it must also be present in sequence C",
781 ));
782 }
783
784 if !has_71f_b && has_71f_c {
785 errors.push(SwiftValidationError::content_error(
786 "D79",
787 "71F",
788 "",
789 "Field 71F is not allowed in Sequence C when not present in Sequence B",
790 "If field 71F is not present in sequence B, then it must not be present in sequence C",
791 ));
792 }
793
794 if has_71g_b && !has_71g_c {
796 errors.push(SwiftValidationError::content_error(
797 "D79",
798 "71G",
799 "",
800 "Field 71G is mandatory in Sequence C when present in Sequence B",
801 "If field 71G is present in one or more occurrence of sequence B, then it must also be present in sequence C",
802 ));
803 }
804
805 if !has_71g_b && has_71g_c {
806 errors.push(SwiftValidationError::content_error(
807 "D79",
808 "71G",
809 "",
810 "Field 71G is not allowed in Sequence C when not present in Sequence B",
811 "If field 71G is not present in sequence B, then it must not be present in sequence C",
812 ));
813 }
814
815 errors
816 }
817
818 fn validate_c7_currency_amount_difference(&self) -> Vec<SwiftValidationError> {
820 let mut errors = Vec::new();
821
822 for (idx, transaction) in self.transactions.iter().enumerate() {
823 if let Some(ref field_33b) = transaction.field_33b {
824 let currency_32b = &transaction.field_32b.currency;
825 let currency_33b = &field_33b.currency;
826 let amount_32b = transaction.field_32b.amount;
827 let amount_33b = field_33b.amount;
828
829 if currency_32b == currency_33b && (amount_32b - amount_33b).abs() < 0.01 {
831 errors.push(SwiftValidationError::content_error(
832 "D21",
833 "33B",
834 &format!("{}{}", currency_33b, amount_33b),
835 &format!(
836 "Transaction {}: Currency code or amount, or both, must be different between fields 33B and 32B",
837 idx + 1
838 ),
839 "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",
840 ));
841 }
842 }
843 }
844
845 errors
846 }
847
848 fn validate_c8_exchange_rate(&self) -> Vec<SwiftValidationError> {
850 let mut errors = Vec::new();
851
852 for (idx, transaction) in self.transactions.iter().enumerate() {
853 if let Some(ref field_33b) = transaction.field_33b {
854 let currency_32b = &transaction.field_32b.currency;
855 let currency_33b = &field_33b.currency;
856
857 if currency_32b != currency_33b {
858 if transaction.field_36.is_none() {
860 errors.push(SwiftValidationError::content_error(
861 "D75",
862 "36",
863 "",
864 &format!(
865 "Transaction {}: Field 36 (Exchange Rate) is mandatory when currencies in fields 32B and 33B are different",
866 idx + 1
867 ),
868 "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",
869 ));
870 }
871 } else {
872 if transaction.field_36.is_some() {
874 errors.push(SwiftValidationError::content_error(
875 "D75",
876 "36",
877 "",
878 &format!(
879 "Transaction {}: Field 36 (Exchange Rate) is not allowed when currencies in fields 32B and 33B are the same",
880 idx + 1
881 ),
882 "If field 33B is present and the currency codes in fields 32B and 33B are the same, then field 36 must not be present",
883 ));
884 }
885 }
886 } else {
887 if transaction.field_36.is_some() {
889 errors.push(SwiftValidationError::content_error(
890 "D75",
891 "36",
892 "",
893 &format!(
894 "Transaction {}: Field 36 (Exchange Rate) is not allowed when field 33B is not present",
895 idx + 1
896 ),
897 "Field 36 must not be present when field 33B is not present",
898 ));
899 }
900 }
901 }
902
903 errors
904 }
905
906 fn validate_c9_field_19(&self) -> Option<SwiftValidationError> {
908 if !self.has_sequence_c() {
909 return None; }
911
912 let field_32b_c = self.field_32b.as_ref()?;
913 let settlement_amount = field_32b_c.amount;
914
915 let sum_of_amounts: f64 = self.transactions.iter().map(|tx| tx.field_32b.amount).sum();
917
918 let amounts_equal = (settlement_amount - sum_of_amounts).abs() < 0.01;
919
920 if amounts_equal && self.field_19.is_some() {
921 return Some(SwiftValidationError::content_error(
922 "D80",
923 "19",
924 "",
925 "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",
926 "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",
927 ));
928 }
929
930 if !amounts_equal && self.field_19.is_none() {
931 return Some(SwiftValidationError::content_error(
932 "D80",
933 "19",
934 "",
935 "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",
936 "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",
937 ));
938 }
939
940 None
941 }
942
943 fn validate_c10_field_19_amount(&self) -> Option<SwiftValidationError> {
945 if let Some(ref field_19) = self.field_19 {
946 let sum_of_amounts: f64 = self.transactions.iter().map(|tx| tx.field_32b.amount).sum();
948
949 if (field_19.amount - sum_of_amounts).abs() > 0.01 {
950 return Some(SwiftValidationError::content_error(
951 "C01",
952 "19",
953 &field_19.amount.to_string(),
954 &format!(
955 "Field 19 amount ({}) must equal the sum of amounts in all occurrences of field 32B in Sequence B ({})",
956 field_19.amount, sum_of_amounts
957 ),
958 "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",
959 ));
960 }
961 }
962
963 None
964 }
965
966 fn validate_c11_currency_consistency(&self) -> Vec<SwiftValidationError> {
968 let mut errors = Vec::new();
969
970 let mut currencies_32b: Vec<&String> = self
972 .transactions
973 .iter()
974 .map(|tx| &tx.field_32b.currency)
975 .collect();
976
977 if let Some(ref field_32b_c) = self.field_32b {
979 currencies_32b.push(&field_32b_c.currency);
980 }
981
982 if !currencies_32b.is_empty() {
984 let first_currency_32b = currencies_32b[0];
985 for currency in currencies_32b.iter().skip(1) {
986 if *currency != first_currency_32b {
987 errors.push(SwiftValidationError::content_error(
988 "C02",
989 "32B",
990 currency,
991 &format!(
992 "Currency code in field 32B ({}) must be the same for all occurrences in the message (expected: {})",
993 currency, first_currency_32b
994 ),
995 "The currency code in fields 32B must be the same for all occurrences of these fields in the message",
996 ));
997 break; }
999 }
1000 }
1001
1002 let mut currencies_71g: Vec<&String> = self
1004 .transactions
1005 .iter()
1006 .filter_map(|tx| tx.field_71g.as_ref().map(|f| &f.currency))
1007 .collect();
1008
1009 if let Some(ref field_71g_c) = self.field_71g {
1011 currencies_71g.push(&field_71g_c.currency);
1012 }
1013
1014 if !currencies_71g.is_empty() {
1016 let first_currency_71g = currencies_71g[0];
1017 for currency in currencies_71g.iter().skip(1) {
1018 if *currency != first_currency_71g {
1019 errors.push(SwiftValidationError::content_error(
1020 "C02",
1021 "71G",
1022 currency,
1023 &format!(
1024 "Currency code in field 71G ({}) must be the same for all occurrences in the message (expected: {})",
1025 currency, first_currency_71g
1026 ),
1027 "The currency code in field 71G in sequences B and C must be the same for all occurrences of these fields in the message",
1028 ));
1029 break; }
1031 }
1032 }
1033
1034 let mut currencies_71f: Vec<&String> = self
1036 .transactions
1037 .iter()
1038 .filter_map(|tx| tx.field_71f.as_ref().map(|f| &f.currency))
1039 .collect();
1040
1041 if let Some(ref field_71f_c) = self.field_71f {
1043 currencies_71f.push(&field_71f_c.currency);
1044 }
1045
1046 if !currencies_71f.is_empty() {
1048 let first_currency_71f = currencies_71f[0];
1049 for currency in currencies_71f.iter().skip(1) {
1050 if *currency != first_currency_71f {
1051 errors.push(SwiftValidationError::content_error(
1052 "C02",
1053 "71F",
1054 currency,
1055 &format!(
1056 "Currency code in field 71F ({}) must be the same for all occurrences in the message (expected: {})",
1057 currency, first_currency_71f
1058 ),
1059 "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",
1060 ));
1061 break; }
1063 }
1064 }
1065
1066 errors
1067 }
1068
1069 fn validate_c12_rfdd_comprehensive(&self) -> Vec<SwiftValidationError> {
1071 let mut errors = Vec::new();
1072 let has_rfdd = self.has_rfdd_in_seq_a();
1073
1074 if has_rfdd {
1075 for (idx, transaction) in self.transactions.iter().enumerate() {
1078 if transaction.field_21e.is_some() {
1079 errors.push(SwiftValidationError::content_error(
1080 "C96",
1081 "21E",
1082 "",
1083 &format!(
1084 "Transaction {}: Field 21E is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1085 idx + 1
1086 ),
1087 "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",
1088 ));
1089 }
1090
1091 if transaction.creditor_tx.is_some() {
1092 errors.push(SwiftValidationError::content_error(
1093 "C96",
1094 "50a",
1095 "",
1096 &format!(
1097 "Transaction {}: Field 50a (Creditor A/K) is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1098 idx + 1
1099 ),
1100 "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",
1101 ));
1102 }
1103
1104 if transaction.field_52.is_some() {
1105 errors.push(SwiftValidationError::content_error(
1106 "C96",
1107 "52a",
1108 "",
1109 &format!(
1110 "Transaction {}: Field 52a is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1111 idx + 1
1112 ),
1113 "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",
1114 ));
1115 }
1116
1117 if transaction.field_71f.is_some() {
1118 errors.push(SwiftValidationError::content_error(
1119 "C96",
1120 "71F",
1121 "",
1122 &format!(
1123 "Transaction {}: Field 71F is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1124 idx + 1
1125 ),
1126 "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",
1127 ));
1128 }
1129
1130 if transaction.field_71g.is_some() {
1131 errors.push(SwiftValidationError::content_error(
1132 "C96",
1133 "71G",
1134 "",
1135 &format!(
1136 "Transaction {}: Field 71G is not allowed in Sequence B when field 23E in Sequence A contains RFDD",
1137 idx + 1
1138 ),
1139 "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",
1140 ));
1141 }
1142 }
1143
1144 if self.has_sequence_c() {
1146 errors.push(SwiftValidationError::content_error(
1147 "C96",
1148 "32B",
1149 "",
1150 "Sequence C is not allowed when field 23E in Sequence A contains RFDD",
1151 "In sequence A, if field 23E is present and contains RFDD, then sequence C must not be present",
1152 ));
1153 }
1154 } else {
1155 if self.field_21r.is_some() {
1158 errors.push(SwiftValidationError::content_error(
1159 "C96",
1160 "21R",
1161 "",
1162 "Field 21R is not allowed in Sequence A when field 23E does not contain RFDD or is not present",
1163 "In sequence A field 23E does not contain RFDD or field 23E is not present, in sequence A field 21R must not be present",
1164 ));
1165 }
1166
1167 if !self.has_sequence_c() {
1169 errors.push(SwiftValidationError::content_error(
1170 "C96",
1171 "32B",
1172 "",
1173 "Sequence C is mandatory when field 23E in Sequence A does not contain RFDD or is not present",
1174 "In sequence A field 23E does not contain RFDD or field 23E is not present, sequence C must be present",
1175 ));
1176 }
1177 }
1178
1179 errors
1180 }
1181
1182 fn validate_field_23e_seq_a(&self) -> Vec<SwiftValidationError> {
1184 let mut errors = Vec::new();
1185
1186 if let Some(ref field_23e) = self.field_23e {
1187 let code = &field_23e.instruction_code;
1188
1189 if !Self::MT104_VALID_23E_CODES_SEQ_A.contains(&code.as_str()) {
1191 errors.push(SwiftValidationError::format_error(
1192 "T47",
1193 "23E",
1194 code,
1195 &format!("One of: {}", Self::MT104_VALID_23E_CODES_SEQ_A.join(", ")),
1196 &format!(
1197 "Sequence A: Instruction code '{}' is not valid for MT104. Valid codes: {}",
1198 code,
1199 Self::MT104_VALID_23E_CODES_SEQ_A.join(", ")
1200 ),
1201 ));
1202 }
1203
1204 if field_23e.additional_info.is_some() && code != Self::CODE_WITH_ADDITIONAL_INFO {
1206 errors.push(SwiftValidationError::content_error(
1207 "D81",
1208 "23E",
1209 code,
1210 &format!(
1211 "Sequence A: Additional information is only allowed for code OTHR. Code '{}' does not allow additional information",
1212 code
1213 ),
1214 "The narrative second subfield can only be used in combination with OTHR",
1215 ));
1216 }
1217 }
1218
1219 errors
1220 }
1221
1222 fn validate_field_23e_seq_b(&self) -> Vec<SwiftValidationError> {
1224 let mut errors = Vec::new();
1225
1226 for (idx, transaction) in self.transactions.iter().enumerate() {
1227 if let Some(ref field_23e) = transaction.field_23e {
1228 let code = &field_23e.instruction_code;
1229
1230 if !Self::MT104_VALID_23E_CODES_SEQ_B.contains(&code.as_str()) {
1232 errors.push(SwiftValidationError::format_error(
1233 "T47",
1234 "23E",
1235 code,
1236 &format!("One of: {}", Self::MT104_VALID_23E_CODES_SEQ_B.join(", ")),
1237 &format!(
1238 "Transaction {}: Instruction code '{}' is not valid for MT104 Sequence B. Valid codes: {}",
1239 idx + 1,
1240 code,
1241 Self::MT104_VALID_23E_CODES_SEQ_B.join(", ")
1242 ),
1243 ));
1244 }
1245
1246 if field_23e.additional_info.is_some() && code != Self::CODE_WITH_ADDITIONAL_INFO {
1248 errors.push(SwiftValidationError::content_error(
1249 "D81",
1250 "23E",
1251 code,
1252 &format!(
1253 "Transaction {}: Additional information is only allowed for code OTHR. Code '{}' does not allow additional information",
1254 idx + 1,
1255 code
1256 ),
1257 "The narrative second subfield can only be used in combination with OTHR",
1258 ));
1259 }
1260 }
1261 }
1262
1263 errors
1264 }
1265
1266 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
1269 let mut all_errors = Vec::new();
1270
1271 let c1_errors = self.validate_c1_field_23e_dependencies();
1273 all_errors.extend(c1_errors);
1274 if stop_on_first_error && !all_errors.is_empty() {
1275 return all_errors;
1276 }
1277
1278 if let Some(error) = self.validate_c2_creditor_field() {
1280 all_errors.push(error);
1281 if stop_on_first_error {
1282 return all_errors;
1283 }
1284 }
1285
1286 let c3_errors = self.validate_c3_mutual_exclusivity();
1288 all_errors.extend(c3_errors);
1289 if stop_on_first_error && !all_errors.is_empty() {
1290 return all_errors;
1291 }
1292
1293 let c4_errors = self.validate_c4_registration_reference();
1295 all_errors.extend(c4_errors);
1296 if stop_on_first_error && !all_errors.is_empty() {
1297 return all_errors;
1298 }
1299
1300 if let Some(error) = self.validate_c5_field_72_rtnd() {
1302 all_errors.push(error);
1303 if stop_on_first_error {
1304 return all_errors;
1305 }
1306 }
1307
1308 let c6_errors = self.validate_c6_charges_dependencies();
1310 all_errors.extend(c6_errors);
1311 if stop_on_first_error && !all_errors.is_empty() {
1312 return all_errors;
1313 }
1314
1315 let c7_errors = self.validate_c7_currency_amount_difference();
1317 all_errors.extend(c7_errors);
1318 if stop_on_first_error && !all_errors.is_empty() {
1319 return all_errors;
1320 }
1321
1322 let c8_errors = self.validate_c8_exchange_rate();
1324 all_errors.extend(c8_errors);
1325 if stop_on_first_error && !all_errors.is_empty() {
1326 return all_errors;
1327 }
1328
1329 if let Some(error) = self.validate_c9_field_19() {
1331 all_errors.push(error);
1332 if stop_on_first_error {
1333 return all_errors;
1334 }
1335 }
1336
1337 if let Some(error) = self.validate_c10_field_19_amount() {
1339 all_errors.push(error);
1340 if stop_on_first_error {
1341 return all_errors;
1342 }
1343 }
1344
1345 let c11_errors = self.validate_c11_currency_consistency();
1347 all_errors.extend(c11_errors);
1348 if stop_on_first_error && !all_errors.is_empty() {
1349 return all_errors;
1350 }
1351
1352 let c12_errors = self.validate_c12_rfdd_comprehensive();
1354 all_errors.extend(c12_errors);
1355 if stop_on_first_error && !all_errors.is_empty() {
1356 return all_errors;
1357 }
1358
1359 let f23e_a_errors = self.validate_field_23e_seq_a();
1361 all_errors.extend(f23e_a_errors);
1362 if stop_on_first_error && !all_errors.is_empty() {
1363 return all_errors;
1364 }
1365
1366 let f23e_b_errors = self.validate_field_23e_seq_b();
1368 all_errors.extend(f23e_b_errors);
1369
1370 all_errors
1371 }
1372}
1373
1374impl crate::traits::SwiftMessageBody for MT104 {
1375 fn message_type() -> &'static str {
1376 "104"
1377 }
1378
1379 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
1380 MT104::parse_from_block4(block4)
1382 }
1383
1384 fn to_mt_string(&self) -> String {
1385 MT104::to_mt_string(self)
1387 }
1388
1389 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
1390 MT104::validate_network_rules(self, stop_on_first_error)
1392 }
1393}