swift_mt_message/messages/
mt107.rs

1use crate::errors::SwiftValidationError;
2use crate::fields::*;
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6/// Sequence B - Transaction details
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct MT107Transaction {
9    /// Transaction reference (Field 21)
10    #[serde(rename = "21")]
11    pub field_21: Field21NoOption,
12
13    /// Instruction code (Field 23E)
14    #[serde(rename = "23E", skip_serializing_if = "Option::is_none")]
15    pub field_23e: Option<Field23E>,
16
17    /// Mandate reference (Field 21C)
18    #[serde(rename = "21C", skip_serializing_if = "Option::is_none")]
19    pub field_21c: Option<Field21C>,
20
21    /// Direct debit reference (Field 21D)
22    #[serde(rename = "21D", skip_serializing_if = "Option::is_none")]
23    pub field_21d: Option<Field21D>,
24
25    /// Registration reference (Field 21E)
26    #[serde(rename = "21E", skip_serializing_if = "Option::is_none")]
27    pub field_21e: Option<Field21E>,
28
29    /// Transaction amount (Field 32B)
30    #[serde(rename = "32B")]
31    pub field_32b: Field32B,
32
33    /// Instructing party (Field 50C/L)
34    #[serde(flatten, skip_serializing_if = "Option::is_none")]
35    pub instructing_party_tx: Option<Field50InstructingParty>,
36
37    /// Creditor (Field 50A/K)
38    #[serde(flatten, skip_serializing_if = "Option::is_none")]
39    pub creditor_tx: Option<Field50Creditor>,
40
41    /// Creditor's bank (Field 52)
42    #[serde(flatten, skip_serializing_if = "Option::is_none")]
43    pub field_52: Option<Field52CreditorBank>,
44
45    /// Debtor's bank (Field 57)
46    #[serde(flatten, skip_serializing_if = "Option::is_none")]
47    pub field_57: Option<Field57DebtorBank>,
48
49    /// Debtor (Field 59)
50    #[serde(flatten)]
51    pub field_59: Field59,
52
53    /// Remittance information (Field 70)
54    #[serde(rename = "70", skip_serializing_if = "Option::is_none")]
55    pub field_70: Option<Field70>,
56
57    /// Transaction type code (Field 26T)
58    #[serde(rename = "26T", skip_serializing_if = "Option::is_none")]
59    pub field_26t: Option<Field26T>,
60
61    /// Regulatory reporting (Field 77B)
62    #[serde(rename = "77B", skip_serializing_if = "Option::is_none")]
63    pub field_77b: Option<Field77B>,
64
65    /// Original ordered amount (Field 33B)
66    #[serde(rename = "33B", skip_serializing_if = "Option::is_none")]
67    pub field_33b: Option<Field33B>,
68
69    /// Details of charges (Field 71A)
70    #[serde(rename = "71A", skip_serializing_if = "Option::is_none")]
71    pub field_71a: Option<Field71A>,
72
73    /// Sender's charges (Field 71F)
74    #[serde(rename = "71F", skip_serializing_if = "Option::is_none")]
75    pub field_71f: Option<Field71F>,
76
77    /// Receiver's charges (Field 71G)
78    #[serde(rename = "71G", skip_serializing_if = "Option::is_none")]
79    pub field_71g: Option<Field71G>,
80
81    /// Exchange rate (Field 36)
82    #[serde(rename = "36", skip_serializing_if = "Option::is_none")]
83    pub field_36: Option<Field36>,
84}
85
86/// **MT107: General Direct Debit Message**
87///
88/// General direct debit instruction with flexible structure and settlement details.
89/// Similar to MT104 but with additional flexibility for complex scenarios.
90///
91/// **Usage:** General direct debits, flexible collections
92/// **Category:** Category 1 (Customer Payments)
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94pub struct MT107 {
95    /// Sender's reference (Field 20)
96    #[serde(rename = "20")]
97    pub field_20: Field20,
98
99    /// Instruction code (Field 23E)
100    #[serde(rename = "23E", skip_serializing_if = "Option::is_none")]
101    pub field_23e: Option<Field23E>,
102
103    /// Registration reference (Field 21E)
104    #[serde(rename = "21E", skip_serializing_if = "Option::is_none")]
105    pub field_21e: Option<Field21E>,
106
107    /// Requested execution date (Field 30)
108    #[serde(rename = "30")]
109    pub field_30: Field30,
110
111    /// Sending institution (Field 51A)
112    #[serde(rename = "51A", skip_serializing_if = "Option::is_none")]
113    pub field_51a: Option<Field51A>,
114
115    /// Instructing party (Field 50C/L)
116    #[serde(flatten, skip_serializing_if = "Option::is_none")]
117    pub instructing_party: Option<Field50InstructingParty>,
118
119    /// Creditor (Field 50A/K)
120    #[serde(flatten, skip_serializing_if = "Option::is_none")]
121    pub creditor: Option<Field50Creditor>,
122
123    /// Creditor's bank (Field 52)
124    #[serde(flatten, skip_serializing_if = "Option::is_none")]
125    pub field_52: Option<Field52CreditorBank>,
126
127    /// Transaction type code (Field 26T)
128    #[serde(rename = "26T", skip_serializing_if = "Option::is_none")]
129    pub field_26t: Option<Field26T>,
130
131    /// Regulatory reporting (Field 77B)
132    #[serde(rename = "77B", skip_serializing_if = "Option::is_none")]
133    pub field_77b: Option<Field77B>,
134
135    /// Details of charges (Field 71A)
136    #[serde(rename = "71A", skip_serializing_if = "Option::is_none")]
137    pub field_71a: Option<Field71A>,
138
139    /// Sender to receiver information (Field 72)
140    #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
141    pub field_72: Option<Field72>,
142
143    /// Transaction details (Sequence B)
144    #[serde(rename = "#")]
145    pub transactions: Vec<MT107Transaction>,
146
147    /// Settlement amount (Field 32B, Sequence C)
148    #[serde(rename = "32B")]
149    pub field_32b: Field32B,
150
151    /// Sum of amounts (Field 19)
152    #[serde(rename = "19", skip_serializing_if = "Option::is_none")]
153    pub field_19: Option<Field19>,
154
155    /// Sum of sender's charges (Field 71F)
156    #[serde(rename = "71F", skip_serializing_if = "Option::is_none")]
157    pub field_71f: Option<Field71F>,
158
159    /// Sum of receiver's charges (Field 71G)
160    #[serde(rename = "71G", skip_serializing_if = "Option::is_none")]
161    pub field_71g: Option<Field71G>,
162
163    /// Sender's correspondent (Field 53)
164    #[serde(flatten, skip_serializing_if = "Option::is_none")]
165    pub field_53: Option<Field53SenderCorrespondent>,
166}
167
168impl MT107 {
169    /// Parse message from Block 4 content
170    pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
171        let mut parser = crate::parser::MessageParser::new(block4, "107");
172
173        // Parse Sequence A - General Information
174        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        // Try to parse field 50 - could be instructing party (C/L) or creditor (A/K)
181        // We'll need to detect the variant and determine which type
182        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        // Parse Sequence B - Transaction Details (repeating)
191        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        // Parse Sequence C - Settlement Details
240        // Note: duplicates remain enabled to allow parsing field 32B again
241        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        // Verify all content is consumed
249        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    /// Helper to parse field 50 which can be either instructing party (C/L) or creditor (A/K)
281    fn parse_field_50(
282        parser: &mut crate::parser::MessageParser,
283    ) -> Result<(Option<Field50InstructingParty>, Option<Field50Creditor>), crate::errors::ParseError>
284    {
285        // Detect which variant of field 50 is present
286        let remaining = parser.remaining();
287        let trimmed = remaining.trim_start_matches(|c: char| c.is_whitespace());
288
289        // Check for instructing party variants (C, L)
290        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        // Check for creditor variants (A, K)
302        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        // No field 50 present
308        Ok((None, None))
309    }
310
311    // ========================================================================
312    // NETWORK VALIDATION RULES (SR 2025 MT107)
313    // ========================================================================
314
315    /// Field 23E valid instruction codes for MT107
316    const MT107_VALID_23E_CODES: &'static [&'static str] = &[
317        "AUTH", // Pre-authorised direct debit
318        "NAUT", // Non pre-authorised
319        "OTHR", // Other
320        "RTND", // Returned
321    ];
322
323    // ========================================================================
324    // HELPER METHODS
325    // ========================================================================
326
327    /// Check if field 23E is present in Sequence A
328    fn has_23e_in_seq_a(&self) -> bool {
329        self.field_23e.is_some()
330    }
331
332    /// Check if field 23E is present in all Sequence B transactions
333    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    /// Check if field 23E is present in any Sequence B transaction
338    fn has_23e_in_any_seq_b(&self) -> bool {
339        self.transactions.iter().any(|tx| tx.field_23e.is_some())
340    }
341
342    /// Check if creditor (A/K) is present in Sequence A
343    fn has_creditor_in_seq_a(&self) -> bool {
344        self.creditor.is_some()
345    }
346
347    /// Check if creditor (A/K) is present in all Sequence B transactions
348    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    /// Check if creditor (A/K) is present in any Sequence B transaction
353    fn has_creditor_in_any_seq_b(&self) -> bool {
354        self.transactions.iter().any(|tx| tx.creditor_tx.is_some())
355    }
356
357    /// Check if instructing party (C/L) is present in Sequence A
358    fn has_instructing_party_in_seq_a(&self) -> bool {
359        self.instructing_party.is_some()
360    }
361
362    /// Check if instructing party (C/L) is present in any Sequence B transaction
363    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    /// Check if field 21E is present in Sequence A
370    fn has_21e_in_seq_a(&self) -> bool {
371        self.field_21e.is_some()
372    }
373
374    /// Check if field 21E is present in any Sequence B transaction
375    fn has_21e_in_any_seq_b(&self) -> bool {
376        self.transactions.iter().any(|tx| tx.field_21e.is_some())
377    }
378
379    /// Check if field 26T is present in Sequence A
380    fn has_26t_in_seq_a(&self) -> bool {
381        self.field_26t.is_some()
382    }
383
384    /// Check if field 26T is present in any Sequence B transaction
385    fn has_26t_in_any_seq_b(&self) -> bool {
386        self.transactions.iter().any(|tx| tx.field_26t.is_some())
387    }
388
389    /// Check if field 77B is present in Sequence A
390    fn has_77b_in_seq_a(&self) -> bool {
391        self.field_77b.is_some()
392    }
393
394    /// Check if field 77B is present in any Sequence B transaction
395    fn has_77b_in_any_seq_b(&self) -> bool {
396        self.transactions.iter().any(|tx| tx.field_77b.is_some())
397    }
398
399    /// Check if field 71A is present in Sequence A
400    fn has_71a_in_seq_a(&self) -> bool {
401        self.field_71a.is_some()
402    }
403
404    /// Check if field 71A is present in any Sequence B transaction
405    fn has_71a_in_any_seq_b(&self) -> bool {
406        self.transactions.iter().any(|tx| tx.field_71a.is_some())
407    }
408
409    /// Check if field 52a (creditor's bank) is present in Sequence A
410    fn has_52a_in_seq_a(&self) -> bool {
411        self.field_52.is_some()
412    }
413
414    /// Check if field 52a (creditor's bank) is present in any Sequence B transaction
415    fn has_52a_in_any_seq_b(&self) -> bool {
416        self.transactions.iter().any(|tx| tx.field_52.is_some())
417    }
418
419    /// Check if field 71F is present in any Sequence B transaction
420    fn has_71f_in_seq_b(&self) -> bool {
421        self.transactions.iter().any(|tx| tx.field_71f.is_some())
422    }
423
424    /// Check if field 71F is present in Sequence C
425    fn has_71f_in_seq_c(&self) -> bool {
426        self.field_71f.is_some()
427    }
428
429    /// Check if field 71G is present in any Sequence B transaction
430    fn has_71g_in_seq_b(&self) -> bool {
431        self.transactions.iter().any(|tx| tx.field_71g.is_some())
432    }
433
434    /// Check if field 71G is present in Sequence C
435    fn has_71g_in_seq_c(&self) -> bool {
436        self.field_71g.is_some()
437    }
438
439    // ========================================================================
440    // VALIDATION RULES (C1-C9)
441    // ========================================================================
442
443    /// C1: Field 23E and Field 50a (option A or K) Mutual Exclusivity (Error code: D86)
444    /// Field 23E and field 50a (option A or K) must be present either in sequence A
445    /// or in each occurrence of sequence B, but not in both
446    fn validate_c1_23e_and_creditor_placement(&self) -> Vec<SwiftValidationError> {
447        let mut errors = Vec::new();
448
449        // Validate Field 23E placement
450        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        // Validate Field 50a (A/K - Creditor) placement
483        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    /// C2: Fields in Sequence A vs Sequence B Mutual Exclusivity (Error code: D73)
519    /// When present in sequence A, fields 21E, 26T, 77B, 71A, 52a and 50a (option C or L)
520    /// must not be present in any occurrence of sequence B, and vice versa
521    fn validate_c2_seq_a_b_mutual_exclusivity(&self) -> Vec<SwiftValidationError> {
522        let mut errors = Vec::new();
523
524        // Field 21E
525        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        // Field 26T
536        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        // Field 77B
547        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        // Field 71A
558        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        // Field 52a
569        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        // Field 50a (C/L - Instructing Party)
580        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    /// C3: Registration Reference and Creditor Dependency (Error code: D77)
594    /// If field 21E is present, then field 50a (option A or K) must also be present
595    /// in the same sequence
596    fn validate_c3_registration_creditor_dependency(&self) -> Vec<SwiftValidationError> {
597        let mut errors = Vec::new();
598
599        // Check Sequence A
600        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        // Check each transaction in Sequence B
611        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    /// C4: Field 23E RTND and Field 72 Dependency (Error code: C82)
630    /// In sequence A, if field 23E contains RTND then field 72 must be present,
631    /// otherwise field 72 is not allowed
632    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            // Field 23E not present in Sequence A
657            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    /// C5: Charges Fields in Sequence B and Sequence C (Error code: D79)
672    /// If fields 71F and 71G are present in Sequence B, they must also be present in Sequence C,
673    /// and vice versa
674    fn validate_c5_charges_fields_consistency(&self) -> Vec<SwiftValidationError> {
675        let mut errors = Vec::new();
676
677        // Field 71F
678        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        // Field 71G
702        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    /// C6: Field 33B and 32B Comparison (Error code: D21)
729    /// If field 33B is present, the currency code or amount, or both, must be different
730    /// between fields 33B and 32B
731    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                // Both currency and amount must not be the same
742                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    /// C7: Field 33B, 32B and Exchange Rate (Error code: D75)
761    /// If field 33B is present and currency codes are different, field 36 must be present.
762    /// Otherwise, field 36 must not be present
763    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                    // Different currencies - field 36 is mandatory
773                    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                    // Same currencies - field 36 must not be present
787                    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                // Field 33B not present - field 36 must not be present
802                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    /// C8: Sum of Amounts and Settlement Amount (Error code: D80, C01)
821    /// The sum of amounts in field 32B of Sequence B must be in field 32B of Sequence C
822    /// (when no charges) or in field 19 of Sequence C
823    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        // Calculate sum of all transaction amounts
831        let sum_of_amounts: f64 = self.transactions.iter().map(|tx| tx.field_32b.amount).sum();
832
833        // Check if charges are present
834        let has_charges = self.has_71f_in_seq_b() || self.has_71g_in_seq_b();
835
836        if has_charges {
837            // Field 19 should be present and equal to sum
838            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            // No charges - field 32B of Sequence C should equal sum, field 19 must not be present
863            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    /// C9: Currency Consistency Across Message (Error code: C02)
892    /// Currency codes in fields 32B and 71G must be the same across all sequences.
893    /// Currency codes in field 71F must be the same across all sequences.
894    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        // Get reference currencies from Sequence C
902        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        // Check 32B currency consistency in Sequence B
907        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            // Check 71F currency consistency
922            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            // Check 71G currency consistency
939            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    /// Validate Field 23E instruction codes (Error codes: T47, D81)
974    /// Validates instruction code values and additional information restrictions
975    fn validate_field_23e(&self) -> Vec<SwiftValidationError> {
976        let mut errors = Vec::new();
977
978        // Check Sequence A field 23E
979        if let Some(ref field_23e) = self.field_23e {
980            let code = &field_23e.instruction_code;
981
982            // T47: Validate instruction code is in allowed list
983            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            // D81: Additional information only allowed for code OTHR
998            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        // Check Sequence B field 23E
1013        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                // T47: Validate instruction code is in allowed list
1018                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                // D81: Additional information only allowed for code OTHR
1034                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    /// Main validation method - validates all network rules
1053    /// Returns array of validation errors, respects stop_on_first_error flag
1054    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
1055        let mut all_errors = Vec::new();
1056
1057        // C1: Field 23E and Creditor Placement
1058        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        // C2: Sequence A/B Mutual Exclusivity
1065        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        // C3: Registration Reference and Creditor Dependency
1072        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        // C4: Field 23E RTND and Field 72 Dependency
1079        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        // C5: Charges Fields Consistency
1087        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        // C6: Field 33B and 32B Comparison
1094        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        // C7: Exchange Rate Dependency
1101        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        // C8: Sum of Amounts
1108        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        // C9: Currency Consistency
1115        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        // Field 23E Validation
1122        let f23e_errors = self.validate_field_23e();
1123        all_errors.extend(f23e_errors);
1124
1125        all_errors
1126    }
1127
1128    /// Parse from SWIFT MT text format
1129    pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
1130        // If input starts with block headers, extract Block 4
1131        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            // Assume input is already block 4 content
1139            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        // Call the existing public method implementation
1153        MT107::parse_from_block4(block4)
1154    }
1155
1156    fn to_mt_string(&self) -> String {
1157        let mut result = String::new();
1158
1159        // Sequence A - General Information
1160        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        // Sequence B - Transaction Details
1174        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        // Sequence C - Settlement Details
1197        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        // Call the existing public method implementation
1208        MT107::validate_network_rules(self, stop_on_first_error)
1209    }
1210}