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 MT107Transaction {
9 #[serde(rename = "21")]
11 pub field_21: Field21NoOption,
12
13 #[serde(rename = "23E", skip_serializing_if = "Option::is_none")]
15 pub field_23e: Option<Field23E>,
16
17 #[serde(rename = "21C", skip_serializing_if = "Option::is_none")]
19 pub field_21c: Option<Field21C>,
20
21 #[serde(rename = "21D", skip_serializing_if = "Option::is_none")]
23 pub field_21d: Option<Field21D>,
24
25 #[serde(rename = "21E", skip_serializing_if = "Option::is_none")]
27 pub field_21e: Option<Field21E>,
28
29 #[serde(rename = "32B")]
31 pub field_32b: Field32B,
32
33 #[serde(flatten, skip_serializing_if = "Option::is_none")]
35 pub instructing_party_tx: Option<Field50InstructingParty>,
36
37 #[serde(flatten, skip_serializing_if = "Option::is_none")]
39 pub creditor_tx: Option<Field50Creditor>,
40
41 #[serde(flatten, skip_serializing_if = "Option::is_none")]
43 pub field_52: Option<Field52CreditorBank>,
44
45 #[serde(flatten, skip_serializing_if = "Option::is_none")]
47 pub field_57: Option<Field57DebtorBank>,
48
49 #[serde(flatten)]
51 pub field_59: Field59,
52
53 #[serde(rename = "70", skip_serializing_if = "Option::is_none")]
55 pub field_70: Option<Field70>,
56
57 #[serde(rename = "26T", skip_serializing_if = "Option::is_none")]
59 pub field_26t: Option<Field26T>,
60
61 #[serde(rename = "77B", skip_serializing_if = "Option::is_none")]
63 pub field_77b: Option<Field77B>,
64
65 #[serde(rename = "33B", skip_serializing_if = "Option::is_none")]
67 pub field_33b: Option<Field33B>,
68
69 #[serde(rename = "71A", skip_serializing_if = "Option::is_none")]
71 pub field_71a: Option<Field71A>,
72
73 #[serde(rename = "71F", skip_serializing_if = "Option::is_none")]
75 pub field_71f: Option<Field71F>,
76
77 #[serde(rename = "71G", skip_serializing_if = "Option::is_none")]
79 pub field_71g: Option<Field71G>,
80
81 #[serde(rename = "36", skip_serializing_if = "Option::is_none")]
83 pub field_36: Option<Field36>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94pub struct MT107 {
95 #[serde(rename = "20")]
97 pub field_20: Field20,
98
99 #[serde(rename = "23E", skip_serializing_if = "Option::is_none")]
101 pub field_23e: Option<Field23E>,
102
103 #[serde(rename = "21E", skip_serializing_if = "Option::is_none")]
105 pub field_21e: Option<Field21E>,
106
107 #[serde(rename = "30")]
109 pub field_30: Field30,
110
111 #[serde(rename = "51A", skip_serializing_if = "Option::is_none")]
113 pub field_51a: Option<Field51A>,
114
115 #[serde(flatten, skip_serializing_if = "Option::is_none")]
117 pub instructing_party: Option<Field50InstructingParty>,
118
119 #[serde(flatten, skip_serializing_if = "Option::is_none")]
121 pub creditor: Option<Field50Creditor>,
122
123 #[serde(flatten, skip_serializing_if = "Option::is_none")]
125 pub field_52: Option<Field52CreditorBank>,
126
127 #[serde(rename = "26T", skip_serializing_if = "Option::is_none")]
129 pub field_26t: Option<Field26T>,
130
131 #[serde(rename = "77B", skip_serializing_if = "Option::is_none")]
133 pub field_77b: Option<Field77B>,
134
135 #[serde(rename = "71A", skip_serializing_if = "Option::is_none")]
137 pub field_71a: Option<Field71A>,
138
139 #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
141 pub field_72: Option<Field72>,
142
143 #[serde(rename = "#")]
145 pub transactions: Vec<MT107Transaction>,
146
147 #[serde(rename = "32B")]
149 pub field_32b: Field32B,
150
151 #[serde(rename = "19", skip_serializing_if = "Option::is_none")]
153 pub field_19: Option<Field19>,
154
155 #[serde(rename = "71F", skip_serializing_if = "Option::is_none")]
157 pub field_71f: Option<Field71F>,
158
159 #[serde(rename = "71G", skip_serializing_if = "Option::is_none")]
161 pub field_71g: Option<Field71G>,
162
163 #[serde(flatten, skip_serializing_if = "Option::is_none")]
165 pub field_53: Option<Field53SenderCorrespondent>,
166}
167
168impl MT107 {
169 pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
171 let mut parser = crate::parser::MessageParser::new(block4, "107");
172
173 let field_20 = parser.parse_field::<Field20>("20")?;
175 let field_23e = parser.parse_optional_field::<Field23E>("23E")?;
176 let field_21e = parser.parse_optional_field::<Field21E>("21E")?;
177 let field_30 = parser.parse_field::<Field30>("30")?;
178 let field_51a = parser.parse_optional_field::<Field51A>("51A")?;
179
180 let (instructing_party, creditor) = Self::parse_field_50(&mut parser)?;
183
184 let field_52 = parser.parse_optional_variant_field::<Field52CreditorBank>("52")?;
185 let field_26t = parser.parse_optional_field::<Field26T>("26T")?;
186 let field_77b = parser.parse_optional_field::<Field77B>("77B")?;
187 let field_71a = parser.parse_optional_field::<Field71A>("71A")?;
188 let field_72 = parser.parse_optional_field::<Field72>("72")?;
189
190 let mut transactions = Vec::new();
192 parser = parser.with_duplicates(true);
193
194 while parser.detect_field("21") {
195 let txn_field_21 = parser.parse_field::<Field21NoOption>("21")?;
196 let txn_field_23e = parser.parse_optional_field::<Field23E>("23E")?;
197 let txn_field_21c = parser.parse_optional_field::<Field21C>("21C")?;
198 let txn_field_21d = parser.parse_optional_field::<Field21D>("21D")?;
199 let txn_field_21e = parser.parse_optional_field::<Field21E>("21E")?;
200 let txn_field_32b = parser.parse_field::<Field32B>("32B")?;
201
202 let (instructing_party_tx, creditor_tx) = Self::parse_field_50(&mut parser)?;
203
204 let txn_field_52 = parser.parse_optional_variant_field::<Field52CreditorBank>("52")?;
205 let txn_field_57 = parser.parse_optional_variant_field::<Field57DebtorBank>("57")?;
206 let txn_field_59 = parser.parse_variant_field::<Field59>("59")?;
207 let txn_field_70 = parser.parse_optional_field::<Field70>("70")?;
208 let txn_field_26t = parser.parse_optional_field::<Field26T>("26T")?;
209 let txn_field_77b = parser.parse_optional_field::<Field77B>("77B")?;
210 let txn_field_33b = parser.parse_optional_field::<Field33B>("33B")?;
211 let txn_field_71a = parser.parse_optional_field::<Field71A>("71A")?;
212 let txn_field_71f = parser.parse_optional_field::<Field71F>("71F")?;
213 let txn_field_71g = parser.parse_optional_field::<Field71G>("71G")?;
214 let txn_field_36 = parser.parse_optional_field::<Field36>("36")?;
215
216 transactions.push(MT107Transaction {
217 field_21: txn_field_21,
218 field_23e: txn_field_23e,
219 field_21c: txn_field_21c,
220 field_21d: txn_field_21d,
221 field_21e: txn_field_21e,
222 field_32b: txn_field_32b,
223 instructing_party_tx,
224 creditor_tx,
225 field_52: txn_field_52,
226 field_57: txn_field_57,
227 field_59: txn_field_59,
228 field_70: txn_field_70,
229 field_26t: txn_field_26t,
230 field_77b: txn_field_77b,
231 field_33b: txn_field_33b,
232 field_71a: txn_field_71a,
233 field_71f: txn_field_71f,
234 field_71g: txn_field_71g,
235 field_36: txn_field_36,
236 });
237 }
238
239 let settlement_field_32b = parser.parse_field::<Field32B>("32B")?;
242 let settlement_field_19 = parser.parse_optional_field::<Field19>("19")?;
243 let settlement_field_71f = parser.parse_optional_field::<Field71F>("71F")?;
244 let settlement_field_71g = parser.parse_optional_field::<Field71G>("71G")?;
245 let settlement_field_53 =
246 parser.parse_optional_variant_field::<Field53SenderCorrespondent>("53")?;
247
248 if !parser.is_complete() {
250 return Err(crate::errors::ParseError::InvalidFormat {
251 message: format!(
252 "Unparsed content remaining in message: {}",
253 parser.remaining()
254 ),
255 });
256 }
257
258 Ok(Self {
259 field_20,
260 field_23e,
261 field_21e,
262 field_30,
263 field_51a,
264 instructing_party,
265 creditor,
266 field_52,
267 field_26t,
268 field_77b,
269 field_71a,
270 field_72,
271 transactions,
272 field_32b: settlement_field_32b,
273 field_19: settlement_field_19,
274 field_71f: settlement_field_71f,
275 field_71g: settlement_field_71g,
276 field_53: settlement_field_53,
277 })
278 }
279
280 fn parse_field_50(
282 parser: &mut crate::parser::MessageParser,
283 ) -> Result<(Option<Field50InstructingParty>, Option<Field50Creditor>), crate::errors::ParseError>
284 {
285 let remaining = parser.remaining();
287 let trimmed = remaining.trim_start_matches(|c: char| c.is_whitespace());
288
289 if trimmed.starts_with(":50C:") {
291 let instructing_party =
292 parser.parse_optional_variant_field::<Field50InstructingParty>("50")?;
293 return Ok((instructing_party, None));
294 }
295 if trimmed.starts_with(":50L:") {
296 let instructing_party =
297 parser.parse_optional_variant_field::<Field50InstructingParty>("50")?;
298 return Ok((instructing_party, None));
299 }
300
301 if trimmed.starts_with(":50A:") || trimmed.starts_with(":50K:") {
303 let creditor = parser.parse_optional_variant_field::<Field50Creditor>("50")?;
304 return Ok((None, creditor));
305 }
306
307 Ok((None, None))
309 }
310
311 const MT107_VALID_23E_CODES: &'static [&'static str] = &[
317 "AUTH", "NAUT", "OTHR", "RTND", ];
322
323 fn has_23e_in_seq_a(&self) -> bool {
329 self.field_23e.is_some()
330 }
331
332 fn has_23e_in_all_seq_b(&self) -> bool {
334 !self.transactions.is_empty() && self.transactions.iter().all(|tx| tx.field_23e.is_some())
335 }
336
337 fn has_23e_in_any_seq_b(&self) -> bool {
339 self.transactions.iter().any(|tx| tx.field_23e.is_some())
340 }
341
342 fn has_creditor_in_seq_a(&self) -> bool {
344 self.creditor.is_some()
345 }
346
347 fn has_creditor_in_all_seq_b(&self) -> bool {
349 !self.transactions.is_empty() && self.transactions.iter().all(|tx| tx.creditor_tx.is_some())
350 }
351
352 fn has_creditor_in_any_seq_b(&self) -> bool {
354 self.transactions.iter().any(|tx| tx.creditor_tx.is_some())
355 }
356
357 fn has_instructing_party_in_seq_a(&self) -> bool {
359 self.instructing_party.is_some()
360 }
361
362 fn has_instructing_party_in_any_seq_b(&self) -> bool {
364 self.transactions
365 .iter()
366 .any(|tx| tx.instructing_party_tx.is_some())
367 }
368
369 fn has_21e_in_seq_a(&self) -> bool {
371 self.field_21e.is_some()
372 }
373
374 fn has_21e_in_any_seq_b(&self) -> bool {
376 self.transactions.iter().any(|tx| tx.field_21e.is_some())
377 }
378
379 fn has_26t_in_seq_a(&self) -> bool {
381 self.field_26t.is_some()
382 }
383
384 fn has_26t_in_any_seq_b(&self) -> bool {
386 self.transactions.iter().any(|tx| tx.field_26t.is_some())
387 }
388
389 fn has_77b_in_seq_a(&self) -> bool {
391 self.field_77b.is_some()
392 }
393
394 fn has_77b_in_any_seq_b(&self) -> bool {
396 self.transactions.iter().any(|tx| tx.field_77b.is_some())
397 }
398
399 fn has_71a_in_seq_a(&self) -> bool {
401 self.field_71a.is_some()
402 }
403
404 fn has_71a_in_any_seq_b(&self) -> bool {
406 self.transactions.iter().any(|tx| tx.field_71a.is_some())
407 }
408
409 fn has_52a_in_seq_a(&self) -> bool {
411 self.field_52.is_some()
412 }
413
414 fn has_52a_in_any_seq_b(&self) -> bool {
416 self.transactions.iter().any(|tx| tx.field_52.is_some())
417 }
418
419 fn has_71f_in_seq_b(&self) -> bool {
421 self.transactions.iter().any(|tx| tx.field_71f.is_some())
422 }
423
424 fn has_71f_in_seq_c(&self) -> bool {
426 self.field_71f.is_some()
427 }
428
429 fn has_71g_in_seq_b(&self) -> bool {
431 self.transactions.iter().any(|tx| tx.field_71g.is_some())
432 }
433
434 fn has_71g_in_seq_c(&self) -> bool {
436 self.field_71g.is_some()
437 }
438
439 fn validate_c1_23e_and_creditor_placement(&self) -> Vec<SwiftValidationError> {
447 let mut errors = Vec::new();
448
449 let has_23e_a = self.has_23e_in_seq_a();
451 let has_23e_all_b = self.has_23e_in_all_seq_b();
452 let has_23e_any_b = self.has_23e_in_any_seq_b();
453
454 if has_23e_a && has_23e_any_b {
455 errors.push(SwiftValidationError::content_error(
456 "D86",
457 "23E",
458 "",
459 "Field 23E must not be present in both Sequence A and Sequence B",
460 "Field 23E must be present either in Sequence A or in each occurrence of Sequence B, but not in both",
461 ));
462 } else if !has_23e_a && !has_23e_all_b {
463 if has_23e_any_b {
464 errors.push(SwiftValidationError::content_error(
465 "D86",
466 "23E",
467 "",
468 "Field 23E must be present in every Sequence B transaction when not in Sequence A",
469 "When field 23E is not in Sequence A, it must be present in each occurrence of Sequence B",
470 ));
471 } else {
472 errors.push(SwiftValidationError::content_error(
473 "D86",
474 "23E",
475 "",
476 "Field 23E must be present in either Sequence A or in every Sequence B transaction",
477 "Field 23E must be present either in Sequence A or in each occurrence of Sequence B",
478 ));
479 }
480 }
481
482 let has_creditor_a = self.has_creditor_in_seq_a();
484 let has_creditor_all_b = self.has_creditor_in_all_seq_b();
485 let has_creditor_any_b = self.has_creditor_in_any_seq_b();
486
487 if has_creditor_a && has_creditor_any_b {
488 errors.push(SwiftValidationError::content_error(
489 "D86",
490 "50a",
491 "",
492 "Field 50a (Creditor A/K) must not be present in both Sequence A and Sequence B",
493 "Field 50a (option A or K) must be present either in Sequence A or in each occurrence of Sequence B, but not in both",
494 ));
495 } else if !has_creditor_a && !has_creditor_all_b {
496 if has_creditor_any_b {
497 errors.push(SwiftValidationError::content_error(
498 "D86",
499 "50a",
500 "",
501 "Field 50a (Creditor A/K) must be present in every Sequence B transaction when not in Sequence A",
502 "When field 50a (option A or K) is not in Sequence A, it must be present in each occurrence of Sequence B",
503 ));
504 } else {
505 errors.push(SwiftValidationError::content_error(
506 "D86",
507 "50a",
508 "",
509 "Field 50a (Creditor A/K) must be present in either Sequence A or in every Sequence B transaction",
510 "Field 50a (option A or K) must be present either in Sequence A or in each occurrence of Sequence B",
511 ));
512 }
513 }
514
515 errors
516 }
517
518 fn validate_c2_seq_a_b_mutual_exclusivity(&self) -> Vec<SwiftValidationError> {
522 let mut errors = Vec::new();
523
524 if self.has_21e_in_seq_a() && self.has_21e_in_any_seq_b() {
526 errors.push(SwiftValidationError::content_error(
527 "D73",
528 "21E",
529 "",
530 "Field 21E must not be present in both Sequence A and Sequence B",
531 "When present in Sequence A, field 21E must not be present in any occurrence of Sequence B",
532 ));
533 }
534
535 if self.has_26t_in_seq_a() && self.has_26t_in_any_seq_b() {
537 errors.push(SwiftValidationError::content_error(
538 "D73",
539 "26T",
540 "",
541 "Field 26T must not be present in both Sequence A and Sequence B",
542 "When present in Sequence A, field 26T must not be present in any occurrence of Sequence B",
543 ));
544 }
545
546 if self.has_77b_in_seq_a() && self.has_77b_in_any_seq_b() {
548 errors.push(SwiftValidationError::content_error(
549 "D73",
550 "77B",
551 "",
552 "Field 77B must not be present in both Sequence A and Sequence B",
553 "When present in Sequence A, field 77B must not be present in any occurrence of Sequence B",
554 ));
555 }
556
557 if self.has_71a_in_seq_a() && self.has_71a_in_any_seq_b() {
559 errors.push(SwiftValidationError::content_error(
560 "D73",
561 "71A",
562 "",
563 "Field 71A must not be present in both Sequence A and Sequence B",
564 "When present in Sequence A, field 71A must not be present in any occurrence of Sequence B",
565 ));
566 }
567
568 if self.has_52a_in_seq_a() && self.has_52a_in_any_seq_b() {
570 errors.push(SwiftValidationError::content_error(
571 "D73",
572 "52a",
573 "",
574 "Field 52a must not be present in both Sequence A and Sequence B",
575 "When present in Sequence A, field 52a must not be present in any occurrence of Sequence B",
576 ));
577 }
578
579 if self.has_instructing_party_in_seq_a() && self.has_instructing_party_in_any_seq_b() {
581 errors.push(SwiftValidationError::content_error(
582 "D73",
583 "50a",
584 "",
585 "Field 50a (Instructing Party C/L) must not be present in both Sequence A and Sequence B",
586 "When present in Sequence A, field 50a (option C or L) must not be present in any occurrence of Sequence B",
587 ));
588 }
589
590 errors
591 }
592
593 fn validate_c3_registration_creditor_dependency(&self) -> Vec<SwiftValidationError> {
597 let mut errors = Vec::new();
598
599 if self.field_21e.is_some() && self.creditor.is_none() {
601 errors.push(SwiftValidationError::content_error(
602 "D77",
603 "50a",
604 "",
605 "Sequence A: Field 50a (Creditor A/K) is mandatory when field 21E is present",
606 "If field 21E is present in Sequence A, then field 50a (option A or K) must also be present in Sequence A",
607 ));
608 }
609
610 for (idx, transaction) in self.transactions.iter().enumerate() {
612 if transaction.field_21e.is_some() && transaction.creditor_tx.is_none() {
613 errors.push(SwiftValidationError::content_error(
614 "D77",
615 "50a",
616 "",
617 &format!(
618 "Transaction {}: Field 50a (Creditor A/K) is mandatory when field 21E is present",
619 idx + 1
620 ),
621 "If field 21E is present in Sequence B, then field 50a (option A or K) must also be present in the same occurrence",
622 ));
623 }
624 }
625
626 errors
627 }
628
629 fn validate_c4_rtnd_field_72_dependency(&self) -> Option<SwiftValidationError> {
633 if let Some(ref field_23e) = self.field_23e {
634 let is_rtnd = field_23e.instruction_code == "RTND";
635
636 if is_rtnd && self.field_72.is_none() {
637 return Some(SwiftValidationError::content_error(
638 "C82",
639 "72",
640 "",
641 "Field 72 is mandatory when field 23E contains code RTND",
642 "In Sequence A, if field 23E is present and contains RTND then field 72 must be present",
643 ));
644 }
645
646 if !is_rtnd && self.field_72.is_some() {
647 return Some(SwiftValidationError::content_error(
648 "C82",
649 "72",
650 "",
651 "Field 72 is not allowed when field 23E does not contain code RTND",
652 "Field 72 is only allowed when field 23E contains code RTND",
653 ));
654 }
655 } else {
656 if self.field_72.is_some() {
658 return Some(SwiftValidationError::content_error(
659 "C82",
660 "72",
661 "",
662 "Field 72 is not allowed when field 23E is not present in Sequence A",
663 "Field 72 is only allowed when field 23E is present in Sequence A with code RTND",
664 ));
665 }
666 }
667
668 None
669 }
670
671 fn validate_c5_charges_fields_consistency(&self) -> Vec<SwiftValidationError> {
675 let mut errors = Vec::new();
676
677 let has_71f_b = self.has_71f_in_seq_b();
679 let has_71f_c = self.has_71f_in_seq_c();
680
681 if has_71f_b && !has_71f_c {
682 errors.push(SwiftValidationError::content_error(
683 "D79",
684 "71F",
685 "",
686 "Field 71F is mandatory in Sequence C when present in Sequence B",
687 "If field 71F is present in one or more occurrence of Sequence B, then it must also be present in Sequence C",
688 ));
689 }
690
691 if has_71f_c && !has_71f_b {
692 errors.push(SwiftValidationError::content_error(
693 "D79",
694 "71F",
695 "",
696 "Field 71F is not allowed in Sequence C when not present in Sequence B",
697 "If field 71F is present in Sequence C, it must also be present in at least one occurrence of Sequence B",
698 ));
699 }
700
701 let has_71g_b = self.has_71g_in_seq_b();
703 let has_71g_c = self.has_71g_in_seq_c();
704
705 if has_71g_b && !has_71g_c {
706 errors.push(SwiftValidationError::content_error(
707 "D79",
708 "71G",
709 "",
710 "Field 71G is mandatory in Sequence C when present in Sequence B",
711 "If field 71G is present in one or more occurrence of Sequence B, then it must also be present in Sequence C",
712 ));
713 }
714
715 if has_71g_c && !has_71g_b {
716 errors.push(SwiftValidationError::content_error(
717 "D79",
718 "71G",
719 "",
720 "Field 71G is not allowed in Sequence C when not present in Sequence B",
721 "If field 71G is present in Sequence C, it must also be present in at least one occurrence of Sequence B",
722 ));
723 }
724
725 errors
726 }
727
728 fn validate_c6_field_33b_32b_comparison(&self) -> Vec<SwiftValidationError> {
732 let mut errors = Vec::new();
733
734 for (idx, transaction) in self.transactions.iter().enumerate() {
735 if let Some(ref field_33b) = transaction.field_33b {
736 let currency_32b = &transaction.field_32b.currency;
737 let amount_32b = transaction.field_32b.amount;
738 let currency_33b = &field_33b.currency;
739 let amount_33b = field_33b.amount;
740
741 if currency_32b == currency_33b && (amount_32b - amount_33b).abs() < 0.01 {
743 errors.push(SwiftValidationError::content_error(
744 "D21",
745 "33B",
746 &format!("{}{}", currency_33b, amount_33b),
747 &format!(
748 "Transaction {}: Field 33B must have different currency code or amount from field 32B. Both are {}{:.2}",
749 idx + 1, currency_32b, amount_32b
750 ),
751 "If field 33B is present, the currency code or the amount, or both, must be different between fields 33B and 32B",
752 ));
753 }
754 }
755 }
756
757 errors
758 }
759
760 fn validate_c7_exchange_rate_dependency(&self) -> Vec<SwiftValidationError> {
764 let mut errors = Vec::new();
765
766 for (idx, transaction) in self.transactions.iter().enumerate() {
767 if let Some(ref field_33b) = transaction.field_33b {
768 let currency_32b = &transaction.field_32b.currency;
769 let currency_33b = &field_33b.currency;
770
771 if currency_32b != currency_33b {
772 if transaction.field_36.is_none() {
774 errors.push(SwiftValidationError::content_error(
775 "D75",
776 "36",
777 "",
778 &format!(
779 "Transaction {}: Field 36 (Exchange Rate) is mandatory when field 33B has different currency ({}) from field 32B ({})",
780 idx + 1, currency_33b, currency_32b
781 ),
782 "If field 33B is present and currency codes in fields 32B and 33B are different, field 36 must be present",
783 ));
784 }
785 } else {
786 if transaction.field_36.is_some() {
788 errors.push(SwiftValidationError::content_error(
789 "D75",
790 "36",
791 "",
792 &format!(
793 "Transaction {}: Field 36 (Exchange Rate) is not allowed when field 33B has same currency as field 32B ({})",
794 idx + 1, currency_32b
795 ),
796 "If field 33B is present and currency codes in fields 32B and 33B are the same, field 36 must not be present",
797 ));
798 }
799 }
800 } else {
801 if transaction.field_36.is_some() {
803 errors.push(SwiftValidationError::content_error(
804 "D75",
805 "36",
806 "",
807 &format!(
808 "Transaction {}: Field 36 (Exchange Rate) is not allowed when field 33B is not present",
809 idx + 1
810 ),
811 "Field 36 is only allowed when field 33B is present",
812 ));
813 }
814 }
815 }
816
817 errors
818 }
819
820 fn validate_c8_sum_of_amounts(&self) -> Vec<SwiftValidationError> {
824 let mut errors = Vec::new();
825
826 if self.transactions.is_empty() {
827 return errors;
828 }
829
830 let sum_of_amounts: f64 = self.transactions.iter().map(|tx| tx.field_32b.amount).sum();
832
833 let has_charges = self.has_71f_in_seq_b() || self.has_71g_in_seq_b();
835
836 if has_charges {
837 if let Some(ref field_19) = self.field_19 {
839 let field_19_amount = field_19.amount;
840 if (field_19_amount - sum_of_amounts).abs() >= 0.01 {
841 errors.push(SwiftValidationError::content_error(
842 "C01",
843 "19",
844 &field_19_amount.to_string(),
845 &format!(
846 "Field 19 ({:.2}) must equal the sum of amounts in field 32B of Sequence B ({:.2})",
847 field_19_amount, sum_of_amounts
848 ),
849 "Field 19 must equal the sum of the amounts in all occurrences of field 32B in Sequence B",
850 ));
851 }
852 } else {
853 errors.push(SwiftValidationError::content_error(
854 "D80",
855 "19",
856 "",
857 "Field 19 is mandatory when charges are present in Sequence B",
858 "When charges are included (field 71F or 71G in Sequence B), the sum of amounts must be in field 19 of Sequence C",
859 ));
860 }
861 } else {
862 let settlement_amount = self.field_32b.amount;
864 if (settlement_amount - sum_of_amounts).abs() >= 0.01 {
865 errors.push(SwiftValidationError::content_error(
866 "D80",
867 "32B",
868 &settlement_amount.to_string(),
869 &format!(
870 "Sequence C field 32B amount ({:.2}) must equal the sum of amounts in Sequence B field 32B ({:.2}) when no charges are included",
871 settlement_amount, sum_of_amounts
872 ),
873 "When no charges are included, the sum of amounts in field 32B of Sequence B must be in field 32B of Sequence C",
874 ));
875 }
876
877 if self.field_19.is_some() {
878 errors.push(SwiftValidationError::content_error(
879 "D80",
880 "19",
881 "",
882 "Field 19 must not be present when no charges are included in Sequence B",
883 "Field 19 must not be present when the sum goes directly to field 32B of Sequence C",
884 ));
885 }
886 }
887
888 errors
889 }
890
891 fn validate_c9_currency_consistency(&self) -> Vec<SwiftValidationError> {
895 let mut errors = Vec::new();
896
897 if self.transactions.is_empty() {
898 return errors;
899 }
900
901 let settlement_currency = &self.field_32b.currency;
903 let ref_71f_currency = self.field_71f.as_ref().map(|f| &f.currency);
904 let ref_71g_currency = self.field_71g.as_ref().map(|f| &f.currency);
905
906 for (idx, transaction) in self.transactions.iter().enumerate() {
908 if &transaction.field_32b.currency != settlement_currency {
909 errors.push(SwiftValidationError::content_error(
910 "C02",
911 "32B",
912 &transaction.field_32b.currency,
913 &format!(
914 "Transaction {}: Currency code in field 32B ({}) must be the same as in Sequence C ({})",
915 idx + 1, transaction.field_32b.currency, settlement_currency
916 ),
917 "The currency code in field 32B must be the same for all occurrences in Sequences B and C",
918 ));
919 }
920
921 if let Some(ref tx_71f) = transaction.field_71f
923 && let Some(ref_currency) = ref_71f_currency
924 && &tx_71f.currency != ref_currency
925 {
926 errors.push(SwiftValidationError::content_error(
927 "C02",
928 "71F",
929 &tx_71f.currency,
930 &format!(
931 "Transaction {}: Currency code in field 71F ({}) must be the same as in Sequence C ({})",
932 idx + 1, tx_71f.currency, ref_currency
933 ),
934 "The currency code in field 71F must be the same for all occurrences in Sequences B and C",
935 ));
936 }
937
938 if let Some(ref tx_71g) = transaction.field_71g {
940 if &tx_71g.currency != settlement_currency {
941 errors.push(SwiftValidationError::content_error(
942 "C02",
943 "71G",
944 &tx_71g.currency,
945 &format!(
946 "Transaction {}: Currency code in field 71G ({}) must be the same as in Sequence C ({})",
947 idx + 1, tx_71g.currency, settlement_currency
948 ),
949 "The currency code in field 71G must be the same for all occurrences in Sequences B and C",
950 ));
951 }
952
953 if let Some(ref_currency) = ref_71g_currency
954 && &tx_71g.currency != ref_currency
955 {
956 errors.push(SwiftValidationError::content_error(
957 "C02",
958 "71G",
959 &tx_71g.currency,
960 &format!(
961 "Transaction {}: Currency code in field 71G ({}) must be the same as in Sequence C ({})",
962 idx + 1, tx_71g.currency, ref_currency
963 ),
964 "The currency code in field 71G must be the same for all occurrences in Sequences B and C",
965 ));
966 }
967 }
968 }
969
970 errors
971 }
972
973 fn validate_field_23e(&self) -> Vec<SwiftValidationError> {
976 let mut errors = Vec::new();
977
978 if let Some(ref field_23e) = self.field_23e {
980 let code = &field_23e.instruction_code;
981
982 if !Self::MT107_VALID_23E_CODES.contains(&code.as_str()) {
984 errors.push(SwiftValidationError::format_error(
985 "T47",
986 "23E",
987 code,
988 &format!("One of: {}", Self::MT107_VALID_23E_CODES.join(", ")),
989 &format!(
990 "Sequence A: Instruction code '{}' is not valid for MT107. Valid codes: {}",
991 code,
992 Self::MT107_VALID_23E_CODES.join(", ")
993 ),
994 ));
995 }
996
997 if field_23e.additional_info.is_some() && code != "OTHR" {
999 errors.push(SwiftValidationError::content_error(
1000 "D81",
1001 "23E",
1002 code,
1003 &format!(
1004 "Sequence A: Additional information is only allowed for code OTHR. Code '{}' does not allow additional information",
1005 code
1006 ),
1007 "Additional information in field 23E is only allowed for code OTHR",
1008 ));
1009 }
1010 }
1011
1012 for (idx, transaction) in self.transactions.iter().enumerate() {
1014 if let Some(ref field_23e) = transaction.field_23e {
1015 let code = &field_23e.instruction_code;
1016
1017 if !Self::MT107_VALID_23E_CODES.contains(&code.as_str()) {
1019 errors.push(SwiftValidationError::format_error(
1020 "T47",
1021 "23E",
1022 code,
1023 &format!("One of: {}", Self::MT107_VALID_23E_CODES.join(", ")),
1024 &format!(
1025 "Transaction {}: Instruction code '{}' is not valid for MT107. Valid codes: {}",
1026 idx + 1,
1027 code,
1028 Self::MT107_VALID_23E_CODES.join(", ")
1029 ),
1030 ));
1031 }
1032
1033 if field_23e.additional_info.is_some() && code != "OTHR" {
1035 errors.push(SwiftValidationError::content_error(
1036 "D81",
1037 "23E",
1038 code,
1039 &format!(
1040 "Transaction {}: Additional information is only allowed for code OTHR. Code '{}' does not allow additional information",
1041 idx + 1, code
1042 ),
1043 "Additional information in field 23E is only allowed for code OTHR",
1044 ));
1045 }
1046 }
1047 }
1048
1049 errors
1050 }
1051
1052 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
1055 let mut all_errors = Vec::new();
1056
1057 let c1_errors = self.validate_c1_23e_and_creditor_placement();
1059 all_errors.extend(c1_errors);
1060 if stop_on_first_error && !all_errors.is_empty() {
1061 return all_errors;
1062 }
1063
1064 let c2_errors = self.validate_c2_seq_a_b_mutual_exclusivity();
1066 all_errors.extend(c2_errors);
1067 if stop_on_first_error && !all_errors.is_empty() {
1068 return all_errors;
1069 }
1070
1071 let c3_errors = self.validate_c3_registration_creditor_dependency();
1073 all_errors.extend(c3_errors);
1074 if stop_on_first_error && !all_errors.is_empty() {
1075 return all_errors;
1076 }
1077
1078 if let Some(error) = self.validate_c4_rtnd_field_72_dependency() {
1080 all_errors.push(error);
1081 if stop_on_first_error {
1082 return all_errors;
1083 }
1084 }
1085
1086 let c5_errors = self.validate_c5_charges_fields_consistency();
1088 all_errors.extend(c5_errors);
1089 if stop_on_first_error && !all_errors.is_empty() {
1090 return all_errors;
1091 }
1092
1093 let c6_errors = self.validate_c6_field_33b_32b_comparison();
1095 all_errors.extend(c6_errors);
1096 if stop_on_first_error && !all_errors.is_empty() {
1097 return all_errors;
1098 }
1099
1100 let c7_errors = self.validate_c7_exchange_rate_dependency();
1102 all_errors.extend(c7_errors);
1103 if stop_on_first_error && !all_errors.is_empty() {
1104 return all_errors;
1105 }
1106
1107 let c8_errors = self.validate_c8_sum_of_amounts();
1109 all_errors.extend(c8_errors);
1110 if stop_on_first_error && !all_errors.is_empty() {
1111 return all_errors;
1112 }
1113
1114 let c9_errors = self.validate_c9_currency_consistency();
1116 all_errors.extend(c9_errors);
1117 if stop_on_first_error && !all_errors.is_empty() {
1118 return all_errors;
1119 }
1120
1121 let f23e_errors = self.validate_field_23e();
1123 all_errors.extend(f23e_errors);
1124
1125 all_errors
1126 }
1127
1128 pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
1130 let block4 = if input.starts_with("{") {
1132 crate::parser::SwiftParser::extract_block(input, 4)?.ok_or_else(|| {
1133 crate::errors::ParseError::InvalidFormat {
1134 message: "Block 4 not found".to_string(),
1135 }
1136 })?
1137 } else {
1138 input.to_string()
1140 };
1141
1142 Self::parse_from_block4(&block4)
1143 }
1144}
1145
1146impl crate::traits::SwiftMessageBody for MT107 {
1147 fn message_type() -> &'static str {
1148 "107"
1149 }
1150
1151 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
1152 MT107::parse_from_block4(block4)
1154 }
1155
1156 fn to_mt_string(&self) -> String {
1157 let mut result = String::new();
1158
1159 append_field(&mut result, &self.field_20);
1161 append_optional_field(&mut result, &self.field_23e);
1162 append_optional_field(&mut result, &self.field_21e);
1163 append_field(&mut result, &self.field_30);
1164 append_optional_field(&mut result, &self.field_51a);
1165 append_optional_field(&mut result, &self.instructing_party);
1166 append_optional_field(&mut result, &self.creditor);
1167 append_optional_field(&mut result, &self.field_52);
1168 append_optional_field(&mut result, &self.field_26t);
1169 append_optional_field(&mut result, &self.field_77b);
1170 append_optional_field(&mut result, &self.field_71a);
1171 append_optional_field(&mut result, &self.field_72);
1172
1173 for txn in &self.transactions {
1175 append_field(&mut result, &txn.field_21);
1176 append_optional_field(&mut result, &txn.field_23e);
1177 append_optional_field(&mut result, &txn.field_21c);
1178 append_optional_field(&mut result, &txn.field_21d);
1179 append_optional_field(&mut result, &txn.field_21e);
1180 append_field(&mut result, &txn.field_32b);
1181 append_optional_field(&mut result, &txn.instructing_party_tx);
1182 append_optional_field(&mut result, &txn.creditor_tx);
1183 append_optional_field(&mut result, &txn.field_52);
1184 append_optional_field(&mut result, &txn.field_57);
1185 append_field(&mut result, &txn.field_59);
1186 append_optional_field(&mut result, &txn.field_70);
1187 append_optional_field(&mut result, &txn.field_26t);
1188 append_optional_field(&mut result, &txn.field_77b);
1189 append_optional_field(&mut result, &txn.field_33b);
1190 append_optional_field(&mut result, &txn.field_71a);
1191 append_optional_field(&mut result, &txn.field_71f);
1192 append_optional_field(&mut result, &txn.field_71g);
1193 append_optional_field(&mut result, &txn.field_36);
1194 }
1195
1196 append_field(&mut result, &self.field_32b);
1198 append_optional_field(&mut result, &self.field_19);
1199 append_optional_field(&mut result, &self.field_71f);
1200 append_optional_field(&mut result, &self.field_71g);
1201 append_optional_field(&mut result, &self.field_53);
1202
1203 finalize_mt_string(result, false)
1204 }
1205
1206 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
1207 MT107::validate_network_rules(self, stop_on_first_error)
1209 }
1210}