swift_mt_message/messages/
mt202.rs

1use crate::{SwiftMessage, fields::*, swift_serde};
2use serde::{Deserialize, Serialize};
3
4/// # MT202: General Financial Institution Transfer
5///
6/// ## Overview
7/// MT202 enables financial institutions to transfer funds between themselves for their own
8/// account or for the account of their customers. It serves as the backbone for correspondent
9/// banking relationships, facilitating both institutional transfers and cover payments for
10/// customer transactions. The MT202 supports various transfer scenarios including nostro/vostro
11/// account movements, liquidity management, and settlement processing.
12///
13/// ## Message Type Specification
14/// **Message Type**: `202`  
15/// **Category**: Financial Institution Transfers (Category 2)  
16/// **Usage**: General Financial Institution Transfer  
17/// **Processing**: Real-time gross settlement (RTGS) and net settlement  
18/// **Network**: SWIFT FIN (Financial network)  
19///
20/// ### Message Variants
21/// ```text
22/// MT202        - Standard financial institution transfer
23/// MT202.COV    - Cover message for customer credit transfers
24/// ```
25///
26/// ## Message Structure
27/// The MT202 message consists of mandatory and optional fields organized in specific sequences:
28///
29/// ### Mandatory Fields (Core Requirements)
30/// - **Field 20**: Transaction Reference Number (sender's unique reference)
31/// - **Field 21**: Related Reference (link to previous message or transaction)
32/// - **Field 32A**: Value Date/Currency/Amount (settlement details)
33/// - **Field 58A**: Beneficiary Institution (final receiving institution)
34///
35/// ### Optional Fields (Enhanced Processing)
36/// ```text
37/// Field 13C   - Time Indication (processing timing)
38/// Field 52A   - Ordering Institution (sender's bank)
39/// Field 53A   - Sender's Correspondent (intermediate bank)
40/// Field 54A   - Receiver's Correspondent (intermediate bank)
41/// Field 56A   - Intermediary Institution (routing bank)
42/// Field 57A   - Account With Institution (beneficiary's correspondent)
43/// Field 72    - Sender to Receiver Information (processing instructions)
44/// ```
45///
46/// ### MT202.COV Specific Fields (Cover Message)
47/// When used as a cover message for customer transfers:
48/// ```text
49/// Field 50A   - Ordering Customer (ultimate originator)
50/// Field 59A   - Beneficiary Customer (ultimate recipient)
51/// Field 70    - Remittance Information (payment details)
52/// Field 33B   - Currency/Instructed Amount (original amount)
53/// ```
54///
55/// ## Business Applications
56///
57/// ### Primary Use Cases
58/// - **Nostro/Vostro movements**: Account movements between correspondent banks
59/// - **Liquidity management**: Funding and cash management between institutions
60/// - **Settlement processing**: Final settlement of payment obligations
61/// - **Cover payments**: Cover for underlying customer credit transfers
62/// - **Reimbursement**: Institution-to-institution reimbursements
63/// - **Treasury operations**: Bank treasury funding and investment settlements
64///
65/// ### Industry Sectors
66/// - **Correspondent Banking**: Managing correspondent account relationships
67/// - **Central Banking**: Central bank operations and monetary policy implementation
68/// - **Commercial Banking**: Inter-bank funding and settlement
69/// - **Investment Banking**: Securities settlement and margin funding
70/// - **Corporate Banking**: Large corporate cash management
71/// - **Trade Finance**: Trade settlement and financing
72///
73/// ## Routing and Settlement Patterns
74///
75/// ### Direct Settlement
76/// ```text
77/// Ordering Institution → Beneficiary Institution
78/// (Field 52A → Field 58A)
79/// ```
80///
81/// ### Correspondent Banking Chain
82/// ```text
83/// Ordering Institution → Sender's Correspondent → Receiver's Correspondent → Beneficiary Institution
84/// (Field 52A → Field 53A → Field 54A → Field 58A)
85/// ```
86///
87/// ### Complex Multi-Institution Routing
88/// ```text
89/// Ordering Institution → Sender's Correspondent → Intermediary →
90/// Account With Institution → Beneficiary Institution
91/// (Field 52A → Field 53A → Field 56A → Field 57A → Field 58A)
92/// ```
93///
94/// ## Field Relationships and Dependencies
95///
96/// ### Time Indication (Field 13C)
97/// - **Multiple occurrences**: Field 13C can appear multiple times for different timing requirements
98/// - **CLS timing**: Cut-off times for Continuous Linked Settlement
99/// - **TARGET timing**: European Central Bank TARGET system timing
100/// - **Local timing**: Domestic settlement system timing requirements
101///
102/// ### Institution Chain Validation
103/// - All institutions must have valid correspondent relationships
104/// - BIC codes must be active and reachable via SWIFT network
105/// - Account numbers must be valid for the specified institutions
106/// - Routing must comply with sanctions and regulatory requirements
107///
108/// ### Cover Message Requirements (MT202.COV)
109/// - Field 50A (Ordering Customer) becomes mandatory for cover messages
110/// - Field 59A (Beneficiary Customer) identifies ultimate beneficiary
111/// - Field 70 (Remittance Information) provides payment purpose details
112/// - Field 33B used for currency conversion scenarios
113///
114/// ## Validation Rules and Compliance
115///
116/// ### Network Validated Rules (SWIFT Standards)
117/// - **T20**: Related reference format validation
118/// - **T21**: Transaction reference uniqueness
119/// - **T32**: Date and amount format validation
120/// - **T58**: Beneficiary institution BIC validation
121/// - **C20**: Reference number consistency
122/// - **C21**: Related reference requirement
123/// - **C58**: Institution identification completeness
124///
125/// ### Business Rule Validations
126/// - Transaction and related references should be unique per sender per day
127/// - Value date should be valid business day for settlement currency
128/// - Institution chain should form valid correspondent relationships
129/// - Time indications should be reasonable for processing requirements
130/// - Cover message fields should be consistent with underlying transaction
131///
132/// ### Regulatory Compliance
133/// - **Sanctions Screening**: All institutions subject to sanctions checks
134/// - **Regulatory Reporting**: Large value transfer reporting requirements
135/// - **AML/KYC**: Know Your Customer requirements for cover messages
136/// - **Correspondent Banking**: Due diligence and compliance monitoring
137/// - **Capital Requirements**: Basel III liquidity and capital impact
138///
139/// ## Error Handling and Processing
140///
141/// ### Field 72 Processing Instructions
142/// ```text
143/// /INT/Internal transfer between own accounts
144/// /COV/Cover payment for customer transfer  
145/// /REIMBURSEMENT/Reimbursement payment
146/// /SETTLEMENT/Settlement of obligations
147/// ```
148///
149/// ### Common Processing Scenarios
150/// - **Same-day settlement**: Immediate settlement requirement
151/// - **Future-dated**: Settlement on specific future date
152/// - **Recurring**: Standing instruction processing
153/// - **Amendment**: Modification of previous instruction
154/// - **Cancellation**: Reversal of previous transfer
155#[swift_serde]
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, SwiftMessage)]
157#[swift_message(mt = "202")]
158pub struct MT202 {
159    /// **Transaction Reference Number** - Field 20
160    ///
161    /// Unique sender's reference identifying this specific financial institution transfer.
162    /// Used throughout the transfer lifecycle for tracking, reconciliation, and audit.
163    /// Must be unique within the sender's system per business day.
164    ///
165    /// **Format**: Up to 16 alphanumeric characters  
166    /// **Usage**: Mandatory in all MT202 variants  
167    /// **Business Rule**: Should follow sender's reference numbering scheme
168    #[field("20")]
169    pub field_20: Field20,
170
171    /// **Related Reference** - Field 21
172    ///
173    /// Reference to a related message or transaction that this MT202 is associated with.
174    /// Critical for linking cover payments to underlying customer transfers and for
175    /// maintaining audit trails across related transactions.
176    ///
177    /// **Format**: Up to 16 alphanumeric characters  
178    /// **Usage**: Mandatory in all MT202 messages  
179    /// **Relationship**: Links to previous messages or transactions
180    #[field("21")]
181    pub field_21: Field21,
182
183    /// **Value Date/Currency/Amount** - Field 32A
184    ///
185    /// Core settlement details specifying when, in what currency, and for what amount
186    /// the institutional transfer should be processed. The value date determines when
187    /// the settlement occurs between the institutions.
188    ///
189    /// **Components**: Date (YYMMDD) + Currency (3 chars) + Amount (decimal)  
190    /// **Business Rule**: Value date should be valid business day for currency  
191    /// **Settlement**: Determines actual settlement timing between institutions
192    #[field("32A")]
193    pub field_32a: Field32A,
194
195    /// **Beneficiary Institution** - Field 58A
196    ///
197    /// Identifies the financial institution that will receive the funds being transferred.
198    /// This is the final destination institution in the transfer chain and must be
199    /// clearly identified for successful settlement.
200    ///
201    /// **Format**: [/account]BIC code  
202    /// **Usage**: Mandatory in all MT202 messages  
203    /// **Settlement**: Final destination for fund settlement
204    #[field("58A")]
205    pub field_58a: Field58A,
206
207    /// **Time Indication** - Field 13C (Optional, Repetitive)
208    ///
209    /// Provides specific timing instructions for transfer processing, including
210    /// cut-off times for various settlement systems and coordination requirements.
211    /// Can appear multiple times for different timing requirements.
212    ///
213    /// **Usage**: Optional, used for time-sensitive transfers  
214    /// **Multiple**: Can appear multiple times for different systems  
215    /// **Format**: Time + UTC offsets for coordination  
216    /// **Business Value**: Enables precise timing control across settlement systems
217    #[field("13C")]
218    pub field_13c: Option<Vec<Field13C>>,
219
220    /// **Ordering Institution** - Field 52A (Optional)
221    ///
222    /// Identifies the financial institution that is ordering the transfer.
223    /// This is typically the sender's own institution or the institution
224    /// acting on behalf of the ordering party.
225    ///
226    /// **Format**: [/account]BIC code  
227    /// **Usage**: Optional, identifies ordering institution  
228    /// **Routing Role**: First institution in the transfer chain
229    #[field("52A")]
230    pub field_52a: Option<Field52A>,
231
232    /// **Sender's Correspondent** - Field 53A (Optional)
233    ///
234    /// Identifies the correspondent bank of the sending institution.
235    /// Used in correspondent banking arrangements where direct settlement
236    /// relationships may not exist between sender and receiver.
237    ///
238    /// **Format**: [/account]BIC code  
239    /// **Usage**: Optional, facilitates correspondent banking  
240    /// **Routing Role**: Intermediate institution in transfer chain
241    #[field("53A")]
242    pub field_53a: Option<Field53A>,
243
244    /// **Receiver's Correspondent** - Field 54A (Optional)
245    ///
246    /// Identifies the correspondent bank of the receiving institution.
247    /// Critical for cross-border transfers where direct correspondent
248    /// relationships may not exist between institutions.
249    ///
250    /// **Format**: [/account]BIC code  
251    /// **Usage**: Optional, enables correspondent banking  
252    /// **Routing Role**: Receiving-side correspondent institution
253    #[field("54A")]
254    pub field_54a: Option<Field54A>,
255
256    /// **Intermediary Institution** - Field 56A (Optional)
257    ///
258    /// Identifies an intermediary bank in the transfer routing chain.
259    /// Acts as a pass-through institution between correspondents or
260    /// provides additional routing capabilities.
261    ///
262    /// **Format**: [/account]BIC code  
263    /// **Usage**: Optional, facilitates complex routing  
264    /// **Routing Role**: Intermediate routing institution
265    #[field("56A")]
266    pub field_56a: Option<Field56A>,
267
268    /// **Account With Institution** - Field 57A (Optional)
269    ///
270    /// Identifies the institution where the beneficiary institution
271    /// maintains its account. Used for indirect settlement arrangements
272    /// where the beneficiary institution settles through another bank.
273    ///
274    /// **Format**: [/account]BIC code  
275    /// **Usage**: Optional, enables indirect settlement  
276    /// **Settlement Role**: Settlement institution for beneficiary
277    #[field("57A")]
278    pub field_57a: Option<Field57A>,
279
280    /// **Sender to Receiver Information** - Field 72 (Optional)
281    ///
282    /// Free-format field for processing instructions, operational information,
283    /// and special handling requirements. Critical for conveying processing
284    /// context and operational requirements.
285    ///
286    /// **Format**: Up to 6 lines of 35 characters each  
287    /// **Usage**: Optional, provides processing context  
288    /// **Content**: Instructions, references, operational information
289    #[field("72")]
290    pub field_72: Option<Field72>,
291
292    /// **Ordering Customer** - Field 50A (Optional, Cover Messages)
293    ///
294    /// Identifies the ultimate ordering customer when MT202 is used as a cover
295    /// message for customer credit transfers. This field links the institutional
296    /// transfer to the underlying customer transaction.
297    ///
298    /// **Usage**: Optional, mandatory for MT202.COV  
299    /// **Cover Role**: Ultimate originator of underlying transaction  
300    /// **Format**: [/account]BIC or name/address
301    #[field("50A")]
302    pub field_50a: Option<Field50>,
303
304    /// **Beneficiary Customer** - Field 59A (Optional, Cover Messages)
305    ///
306    /// Identifies the ultimate beneficiary customer when MT202 is used as a cover
307    /// message. This field identifies the final recipient of the underlying
308    /// customer transfer being covered.
309    ///
310    /// **Usage**: Optional, used in cover messages  
311    /// **Cover Role**: Ultimate beneficiary of underlying transaction  
312    /// **Format**: [/account]BIC or name/address
313    #[field("59A")]
314    pub field_59a: Option<Field59A>,
315
316    /// **Remittance Information** - Field 70 (Optional, Cover Messages)
317    ///
318    /// Provides details about the purpose and context of the underlying
319    /// customer transfer when MT202 is used as a cover message. Contains
320    /// payment references and remittance details.
321    ///
322    /// **Usage**: Optional, used in cover messages  
323    /// **Format**: Up to 4 lines of 35 characters each  
324    /// **Purpose**: Payment description, invoice numbers, contract references
325    #[field("70")]
326    pub field_70: Option<Field70>,
327
328    /// **Currency/Instructed Amount** - Field 33B (Optional, Cover Messages)
329    ///
330    /// Original amount and currency as instructed by the ordering customer
331    /// when different from the settlement amount in Field 32A. Used in
332    /// cross-currency cover scenarios.
333    ///
334    /// **Usage**: Optional, for cross-currency covers  
335    /// **Format**: Currency code + Amount  
336    /// **Relationship**: May differ from Field 32A for FX transactions
337    #[field("33B")]
338    pub field_33b: Option<Field33B>,
339
340    /// **Ordering Institution** - Field 52A Sequence B (Optional, Cover Messages)
341    ///
342    /// Identifies the ordering institution in the underlying customer transaction
343    /// when MT202 is used as a cover message. This can be different from the
344    /// institutional ordering institution in Sequence A.
345    ///
346    /// **Usage**: Optional, MT202.COV Sequence B only  
347    /// **Cover Role**: Ordering institution for underlying customer transaction  
348    /// **Difference**: Distinct from Sequence A Field 52A (institutional context)
349    #[field("52A_SEQ_B")]
350    pub field_52a_seq_b: Option<Field52A>,
351
352    /// **Intermediary Institution** - Field 56A Sequence B (Optional, Cover Messages)
353    ///
354    /// Identifies an intermediary institution in the underlying customer transaction
355    /// routing chain when MT202 is used as a cover message. This provides the
356    /// customer transaction routing context separate from institutional routing.
357    ///
358    /// **Usage**: Optional, MT202.COV Sequence B only  
359    /// **Cover Role**: Intermediary for underlying customer transaction  
360    /// **Routing**: Customer transaction routing, not institutional routing
361    #[field("56A_SEQ_B")]
362    pub field_56a_seq_b: Option<Field56A>,
363
364    /// **Account With Institution** - Field 57A Sequence B (Optional, Cover Messages)
365    ///
366    /// Identifies the account with institution in the underlying customer transaction
367    /// when MT202 is used as a cover message. This specifies where the beneficiary
368    /// customer's account is held in the underlying transaction.
369    ///
370    /// **Usage**: Optional, MT202.COV Sequence B only  
371    /// **Cover Role**: Beneficiary's bank for underlying customer transaction  
372    /// **Context**: Customer transaction settlement, not institutional settlement
373    #[field("57A_SEQ_B")]
374    pub field_57a_seq_b: Option<Field57A>,
375
376    /// **Sender to Receiver Information** - Field 72 Sequence B (Optional, Cover Messages)
377    ///
378    /// Provides additional processing instructions and information specific to
379    /// the underlying customer transaction when MT202 is used as a cover message.
380    /// This is separate from institutional processing instructions in Sequence A.
381    ///
382    /// **Usage**: Optional, MT202.COV Sequence B only  
383    /// **Cover Role**: Customer transaction processing instructions  
384    /// **Content**: Customer-specific instructions, references, operational information
385    #[field("72_SEQ_B")]
386    pub field_72_seq_b: Option<Field72>,
387}
388
389impl MT202 {
390    /// Create a new MT202 with required fields only
391    pub fn new(
392        field_20: Field20,
393        field_21: Field21,
394        field_32a: Field32A,
395        field_58a: Field58A,
396    ) -> Self {
397        Self {
398            field_20,
399            field_21,
400            field_32a,
401            field_58a,
402            field_13c: None,
403            field_52a: None,
404            field_53a: None,
405            field_54a: None,
406            field_56a: None,
407            field_57a: None,
408            field_72: None,
409            field_50a: None,
410            field_59a: None,
411            field_70: None,
412            field_33b: None,
413            field_52a_seq_b: None,
414            field_56a_seq_b: None,
415            field_57a_seq_b: None,
416            field_72_seq_b: None,
417        }
418    }
419
420    /// Create a new MT202 with all fields for complete functionality
421    #[allow(clippy::too_many_arguments)]
422    pub fn new_complete(
423        field_20: Field20,
424        field_21: Field21,
425        field_32a: Field32A,
426        field_58a: Field58A,
427        field_13c: Option<Vec<Field13C>>,
428        field_52a: Option<Field52A>,
429        field_53a: Option<Field53A>,
430        field_54a: Option<Field54A>,
431        field_56a: Option<Field56A>,
432        field_57a: Option<Field57A>,
433        field_72: Option<Field72>,
434        field_50a: Option<Field50>,
435        field_59a: Option<Field59A>,
436        field_70: Option<Field70>,
437        field_33b: Option<Field33B>,
438        field_52a_seq_b: Option<Field52A>,
439        field_56a_seq_b: Option<Field56A>,
440        field_57a_seq_b: Option<Field57A>,
441        field_72_seq_b: Option<Field72>,
442    ) -> Self {
443        Self {
444            field_20,
445            field_21,
446            field_32a,
447            field_58a,
448            field_13c,
449            field_52a,
450            field_53a,
451            field_54a,
452            field_56a,
453            field_57a,
454            field_72,
455            field_50a,
456            field_59a,
457            field_70,
458            field_33b,
459            field_52a_seq_b,
460            field_56a_seq_b,
461            field_57a_seq_b,
462            field_72_seq_b,
463        }
464    }
465
466    /// Get the transaction reference
467    pub fn transaction_reference(&self) -> &str {
468        self.field_20.transaction_reference()
469    }
470
471    /// Get the related reference
472    pub fn related_reference(&self) -> &str {
473        self.field_21.related_reference()
474    }
475
476    /// Get the currency code
477    pub fn currency_code(&self) -> &str {
478        self.field_32a.currency_code()
479    }
480
481    /// Get the transaction amount as decimal
482    pub fn amount_decimal(&self) -> f64 {
483        self.field_32a.amount_decimal()
484    }
485
486    /// Get the beneficiary institution BIC
487    pub fn beneficiary_institution_bic(&self) -> &str {
488        self.field_58a.bic()
489    }
490
491    /// Get time indications if present
492    pub fn time_indications(&self) -> Option<&Vec<Field13C>> {
493        self.field_13c.as_ref()
494    }
495
496    /// Get ordering institution if present
497    pub fn ordering_institution(&self) -> Option<&Field52A> {
498        self.field_52a.as_ref()
499    }
500
501    /// Get sender's correspondent if present
502    pub fn senders_correspondent(&self) -> Option<&Field53A> {
503        self.field_53a.as_ref()
504    }
505
506    /// Get receiver's correspondent if present
507    pub fn receivers_correspondent(&self) -> Option<&Field54A> {
508        self.field_54a.as_ref()
509    }
510
511    /// Get intermediary institution if present
512    pub fn intermediary_institution(&self) -> Option<&Field56A> {
513        self.field_56a.as_ref()
514    }
515
516    /// Get account with institution if present
517    pub fn account_with_institution(&self) -> Option<&Field57A> {
518        self.field_57a.as_ref()
519    }
520
521    /// Get sender to receiver information if present
522    pub fn sender_to_receiver_info(&self) -> Option<&Field72> {
523        self.field_72.as_ref()
524    }
525
526    /// Get ordering customer if present (cover message)
527    pub fn ordering_customer(&self) -> Option<&Field50> {
528        self.field_50a.as_ref()
529    }
530
531    /// Get beneficiary customer if present (cover message)
532    pub fn beneficiary_customer(&self) -> Option<&Field59A> {
533        self.field_59a.as_ref()
534    }
535
536    /// Get remittance information if present (cover message)
537    pub fn remittance_information(&self) -> Option<&Field70> {
538        self.field_70.as_ref()
539    }
540
541    /// Get instructed amount if present (cover message)
542    pub fn instructed_amount(&self) -> Option<&Field33B> {
543        self.field_33b.as_ref()
544    }
545
546    /// Check if this is a cover message (MT202.COV)
547    ///
548    /// According to METAFCT003 specification, a message is COVER when:
549    /// - Both fields 53A and 54A are present AND
550    /// - Field 53A BIC ≠ Message Sender BIC OR Field 54A BIC ≠ Message Receiver BIC
551    ///
552    /// # Parameters
553    /// - `sender_bic`: The BIC of the message sender (from SWIFT message header)
554    /// - `receiver_bic`: The BIC of the message receiver (from SWIFT message header)
555    pub fn is_cover_message(&self, sender_bic: &str, receiver_bic: &str) -> bool {
556        if let (Some(field_53a), Some(field_54a)) = (&self.field_53a, &self.field_54a) {
557            // Get first 6 characters for BIC comparison (institution identifier)
558            let message_sender_prefix = if sender_bic.len() >= 6 {
559                &sender_bic[0..6]
560            } else {
561                sender_bic
562            };
563            let message_receiver_prefix = if receiver_bic.len() >= 6 {
564                &receiver_bic[0..6]
565            } else {
566                receiver_bic
567            };
568
569            // Get correspondent BICs from fields 53A and 54A
570            let field_53a_bic = field_53a.bic();
571            let field_54a_bic = field_54a.bic();
572
573            let field_53a_prefix = if field_53a_bic.len() >= 6 {
574                &field_53a_bic[0..6]
575            } else {
576                field_53a_bic
577            };
578            let field_54a_prefix = if field_54a_bic.len() >= 6 {
579                &field_54a_bic[0..6]
580            } else {
581                field_54a_bic
582            };
583
584            // Cover logic: If 53A ≠ Sender OR 54A ≠ Receiver, then it's COVER
585            field_53a_prefix != message_sender_prefix || field_54a_prefix != message_receiver_prefix
586        } else {
587            false // No correspondent banks = not a cover
588        }
589    }
590
591    /// Check if this is a cover message using ordering/beneficiary institutions as fallback
592    ///
593    /// This is a convenience method when message header BICs are not available.
594    /// Uses field_52a (ordering institution) as sender and field_58a (beneficiary institution) as receiver.
595    pub fn is_cover_message_from_fields(&self) -> bool {
596        // Use ordering institution (52A) as sender, or empty string if not present
597        let sender_bic = self.field_52a.as_ref().map(|f| f.bic()).unwrap_or("");
598
599        // Use beneficiary institution (58A) as receiver
600        let receiver_bic = self.field_58a.bic();
601
602        self.is_cover_message(sender_bic, receiver_bic)
603    }
604
605    /// Check if this is a cross-currency transfer
606    pub fn is_cross_currency(&self) -> bool {
607        if let Some(field_33b) = &self.field_33b {
608            field_33b.currency() != self.field_32a.currency_code()
609        } else {
610            false
611        }
612    }
613
614    /// Get the message variant type
615    ///
616    /// Uses the fallback method since header BICs are not available at this level
617    pub fn get_variant(&self) -> &'static str {
618        if self.is_cover_message_from_fields() {
619            "MT202.COV"
620        } else {
621            "MT202"
622        }
623    }
624
625    /// Get all institution fields in routing order
626    pub fn get_routing_chain(&self) -> Vec<(&str, String)> {
627        let mut chain = Vec::new();
628
629        // Ordering Institution
630        if let Some(field_52a) = &self.field_52a {
631            chain.push(("Ordering Institution", field_52a.bic().to_string()));
632        }
633
634        // Sender's Correspondent
635        if let Some(field_53a) = &self.field_53a {
636            chain.push(("Sender's Correspondent", field_53a.bic().to_string()));
637        }
638
639        // Receiver's Correspondent
640        if let Some(field_54a) = &self.field_54a {
641            chain.push(("Receiver's Correspondent", field_54a.bic().to_string()));
642        }
643
644        // Intermediary Institution
645        if let Some(field_56a) = &self.field_56a {
646            chain.push(("Intermediary", field_56a.bic().to_string()));
647        }
648
649        // Account With Institution
650        if let Some(field_57a) = &self.field_57a {
651            chain.push(("Account With Institution", field_57a.bic().to_string()));
652        }
653
654        // Beneficiary Institution
655        chain.push(("Beneficiary Institution", self.field_58a.bic().to_string()));
656
657        chain
658    }
659
660    /// Check if all required fields are present and valid
661    pub fn validate_structure(&self) -> bool {
662        // All required fields are enforced by the struct
663        true
664    }
665
666    /// Validate cover message requirements
667    pub fn validate_cover_message(&self) -> bool {
668        if self.is_cover_message_from_fields() {
669            // Cover messages should have meaningful cover information
670            self.field_50a.is_some() || self.field_59a.is_some() || self.field_70.is_some()
671        } else {
672            true
673        }
674    }
675
676    /// Get all time indications with descriptions
677    pub fn get_time_indications_with_descriptions(&self) -> Vec<String> {
678        if let Some(time_indications) = &self.field_13c {
679            time_indications
680                .iter()
681                .map(|field| field.description())
682                .collect()
683        } else {
684            Vec::new()
685        }
686    }
687
688    /// Check if message has CLS timing requirements
689    pub fn has_cls_timing(&self) -> bool {
690        if let Some(time_indications) = &self.field_13c {
691            time_indications.iter().any(|field| field.is_cls_time())
692        } else {
693            false
694        }
695    }
696
697    /// Check if message has TARGET timing requirements
698    pub fn has_target_timing(&self) -> bool {
699        if let Some(time_indications) = &self.field_13c {
700            time_indications.iter().any(|field| field.is_target_time())
701        } else {
702            false
703        }
704    }
705
706    /// Get processing instructions from field 72
707    pub fn get_processing_instructions(&self) -> Vec<String> {
708        if let Some(field_72) = &self.field_72 {
709            field_72.information.clone()
710        } else {
711            Vec::new()
712        }
713    }
714
715    // ================================
716    // SEQUENCE B FIELD ACCESSORS (MT202.COV Cover Message Support)
717    // ================================
718
719    /// Get ordering institution from sequence B if present (cover message)
720    pub fn ordering_institution_seq_b(&self) -> Option<&Field52A> {
721        self.field_52a_seq_b.as_ref()
722    }
723
724    /// Get intermediary institution from sequence B if present (cover message)
725    pub fn intermediary_institution_seq_b(&self) -> Option<&Field56A> {
726        self.field_56a_seq_b.as_ref()
727    }
728
729    /// Get account with institution from sequence B if present (cover message)
730    pub fn account_with_institution_seq_b(&self) -> Option<&Field57A> {
731        self.field_57a_seq_b.as_ref()
732    }
733
734    /// Get sender to receiver information from sequence B if present (cover message)
735    pub fn sender_to_receiver_info_seq_b(&self) -> Option<&Field72> {
736        self.field_72_seq_b.as_ref()
737    }
738
739    /// Get customer transaction routing chain from sequence B (cover message)
740    pub fn get_customer_routing_chain(&self) -> Vec<(&str, String)> {
741        let mut chain = Vec::new();
742
743        // Sequence B Ordering Institution
744        if let Some(field_52a) = &self.field_52a_seq_b {
745            chain.push(("Customer Ordering Institution", field_52a.bic().to_string()));
746        }
747
748        // Sequence B Intermediary Institution
749        if let Some(field_56a) = &self.field_56a_seq_b {
750            chain.push(("Customer Intermediary", field_56a.bic().to_string()));
751        }
752
753        // Sequence B Account With Institution
754        if let Some(field_57a) = &self.field_57a_seq_b {
755            chain.push((
756                "Customer Account With Institution",
757                field_57a.bic().to_string(),
758            ));
759        }
760
761        chain
762    }
763
764    /// Get processing instructions from sequence B field 72 (cover message)
765    pub fn get_customer_processing_instructions(&self) -> Vec<String> {
766        if let Some(field_72_seq_b) = &self.field_72_seq_b {
767            field_72_seq_b.information.clone()
768        } else {
769            Vec::new()
770        }
771    }
772
773    /// Check if this message has RETN (return) indicators in field 72
774    pub fn has_return_codes(&self) -> bool {
775        // Check both sequence A and sequence B field 72 for return codes
776        let seq_a_has_return = if let Some(field_72) = &self.field_72 {
777            field_72
778                .information
779                .iter()
780                .any(|line| line.contains("/RETN/") || line.to_uppercase().contains("RETURN"))
781        } else {
782            false
783        };
784
785        let seq_b_has_return = if let Some(field_72_seq_b) = &self.field_72_seq_b {
786            field_72_seq_b
787                .information
788                .iter()
789                .any(|line| line.contains("/RETN/") || line.to_uppercase().contains("RETURN"))
790        } else {
791            false
792        };
793
794        seq_a_has_return || seq_b_has_return
795    }
796
797    /// Check if this message has REJT (reject) indicators in field 72
798    pub fn has_reject_codes(&self) -> bool {
799        // Check both sequence A and sequence B field 72 for reject codes
800        let seq_a_has_reject = if let Some(field_72) = &self.field_72 {
801            field_72
802                .information
803                .iter()
804                .any(|line| line.contains("/REJT/") || line.to_uppercase().contains("REJECT"))
805        } else {
806            false
807        };
808
809        let seq_b_has_reject = if let Some(field_72_seq_b) = &self.field_72_seq_b {
810            field_72_seq_b
811                .information
812                .iter()
813                .any(|line| line.contains("/REJT/") || line.to_uppercase().contains("REJECT"))
814        } else {
815            false
816        };
817
818        seq_a_has_reject || seq_b_has_reject
819    }
820}
821
822#[cfg(test)]
823mod tests {
824    use super::*;
825    use crate::SwiftMessageBody;
826    use chrono::NaiveDate;
827
828    #[test]
829    fn test_mt202_creation() {
830        let field_20 = Field20::new("FT21234567890".to_string());
831        let field_21 = Field21::new("REL20241201001".to_string());
832        let field_32a = Field32A::new(
833            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
834            "USD".to_string(),
835            1000000.00,
836        );
837        let field_58a = Field58A::new(None, None, "DEUTDEFFXXX").unwrap();
838
839        let mt202 = MT202::new(field_20, field_21, field_32a, field_58a);
840
841        assert_eq!(mt202.transaction_reference(), "FT21234567890");
842        assert_eq!(mt202.related_reference(), "REL20241201001");
843        assert_eq!(mt202.currency_code(), "USD");
844        assert_eq!(mt202.amount_decimal(), 1000000.00);
845        assert_eq!(mt202.beneficiary_institution_bic(), "DEUTDEFFXXX");
846    }
847
848    #[test]
849    fn test_mt202_with_multiple_field13c() {
850        let field_20 = Field20::new("FT21234567890".to_string());
851        let field_21 = Field21::new("REL20241201001".to_string());
852        let field_32a = Field32A::new(
853            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
854            "USD".to_string(),
855            1000000.00,
856        );
857        let field_58a = Field58A::new(None, None, "DEUTDEFFXXX").unwrap();
858
859        // Create multiple Field13C instances with correct constructor signature
860        let field_13c_1 = Field13C::new("090000+0", "+0100", "+0900").unwrap();
861        let field_13c_2 = Field13C::new("150000+1", "+0100", "-0500").unwrap();
862        let field_13c_vec = vec![field_13c_1, field_13c_2];
863
864        let mt202 = MT202::new_complete(
865            field_20,
866            field_21,
867            field_32a,
868            field_58a,
869            Some(field_13c_vec),
870            None,
871            None,
872            None,
873            None,
874            None,
875            None,
876            None,
877            None,
878            None,
879            None,
880            None,
881            None,
882            None,
883            None,
884        );
885
886        // Verify the structure
887        assert!(mt202.field_13c.is_some());
888        let field_13c_instances = mt202.field_13c.as_ref().unwrap();
889        assert_eq!(field_13c_instances.len(), 2);
890        assert_eq!(field_13c_instances[0].time(), "090000+0");
891        assert_eq!(field_13c_instances[1].time(), "150000+1");
892    }
893
894    #[test]
895    fn test_mt202_message_type() {
896        assert_eq!(MT202::message_type(), "202");
897    }
898
899    #[test]
900    fn test_mt202_required_fields() {
901        let required = MT202::required_fields();
902        assert!(required.contains(&"20"));
903        assert!(required.contains(&"21"));
904        assert!(required.contains(&"32A"));
905        assert!(required.contains(&"58A"));
906    }
907
908    #[test]
909    fn test_mt202_optional_fields() {
910        let optional = MT202::optional_fields();
911        assert!(optional.contains(&"13C"));
912        assert!(optional.contains(&"52A"));
913        assert!(optional.contains(&"53A"));
914        assert!(optional.contains(&"54A"));
915        assert!(optional.contains(&"56A"));
916        assert!(optional.contains(&"57A"));
917        assert!(optional.contains(&"72"));
918        assert!(optional.contains(&"50A"));
919        assert!(optional.contains(&"59A"));
920        assert!(optional.contains(&"70"));
921        assert!(optional.contains(&"33B"));
922    }
923
924    #[test]
925    fn test_mt202_cover_message_detection() {
926        let field_20 = Field20::new("FT21234567890".to_string());
927        let field_21 = Field21::new("REL20241201001".to_string());
928        let field_32a = Field32A::new(
929            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
930            "USD".to_string(),
931            1000000.00,
932        );
933        let field_58a = Field58A::new(None, None, "DEUTDEFFXXX").unwrap();
934        let field_50a = Field50::K(Field50K::new(vec!["JOHN DOE".to_string()]).unwrap());
935
936        // Standard MT202
937        let mt202_standard = MT202::new(
938            field_20.clone(),
939            field_21.clone(),
940            field_32a.clone(),
941            field_58a.clone(),
942        );
943        // Test with sample BICs - should not be cover since no 53A/54A fields
944        assert!(!mt202_standard.is_cover_message("BANKUS33XXX", "BANKDE55XXX"));
945        assert_eq!(mt202_standard.get_variant(), "MT202");
946
947        // MT202.COV with ordering customer and correspondent banks
948        let field_53a = Field53A::new(None, None, "CHASUS33XXX").unwrap();
949        let field_54a = Field54A::new(None, None, "RBOSGGSGXXX").unwrap();
950
951        let mt202_cover = MT202::new_complete(
952            field_20,
953            field_21,
954            field_32a,
955            field_58a,
956            None,
957            None,
958            Some(field_53a),
959            Some(field_54a),
960            None,
961            None,
962            None,
963            Some(field_50a),
964            None,
965            None,
966            None,
967            None,
968            None,
969            None,
970            None,
971        );
972        // Test with different sender/receiver BICs to trigger cover detection
973        assert!(mt202_cover.is_cover_message("BANKUS33XXX", "BANKDE55XXX"));
974        assert_eq!(mt202_cover.get_variant(), "MT202.COV");
975    }
976
977    #[test]
978    fn test_mt202_routing_chain() {
979        let field_20 = Field20::new("FT21234567890".to_string());
980        let field_21 = Field21::new("REL20241201001".to_string());
981        let field_32a = Field32A::new(
982            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
983            "USD".to_string(),
984            1000000.00,
985        );
986        let field_58a = Field58A::new(None, None, "DEUTDEFFXXX").unwrap();
987        let field_52a = Field52A::new(None, None, "CHASUS33XXX").unwrap();
988        let field_53a = Field53A::new(None, None, "BARCGB22XXX").unwrap();
989
990        let mt202 = MT202::new_complete(
991            field_20,
992            field_21,
993            field_32a,
994            field_58a,
995            None,
996            Some(field_52a),
997            Some(field_53a),
998            None,
999            None,
1000            None,
1001            None,
1002            None,
1003            None,
1004            None,
1005            None,
1006            None,
1007            None,
1008            None,
1009            None,
1010        );
1011
1012        let routing_chain = mt202.get_routing_chain();
1013        assert_eq!(routing_chain.len(), 3);
1014        assert_eq!(
1015            routing_chain[0],
1016            ("Ordering Institution", "CHASUS33XXX".to_string())
1017        );
1018        assert_eq!(
1019            routing_chain[1],
1020            ("Sender's Correspondent", "BARCGB22XXX".to_string())
1021        );
1022        assert_eq!(
1023            routing_chain[2],
1024            ("Beneficiary Institution", "DEUTDEFFXXX".to_string())
1025        );
1026    }
1027
1028    #[test]
1029    fn test_mt202_cross_currency() {
1030        let field_20 = Field20::new("FT21234567890".to_string());
1031        let field_21 = Field21::new("REL20241201001".to_string());
1032        let field_32a = Field32A::new(
1033            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1034            "USD".to_string(),
1035            1000000.00,
1036        );
1037        let field_58a = Field58A::new(None, None, "DEUTDEFFXXX").unwrap();
1038        let field_33b = Field33B::new("EUR", 850000.00).unwrap();
1039
1040        let mt202 = MT202::new_complete(
1041            field_20,
1042            field_21,
1043            field_32a,
1044            field_58a,
1045            None,
1046            None,
1047            None,
1048            None,
1049            None,
1050            None,
1051            None,
1052            None,
1053            None,
1054            None,
1055            Some(field_33b),
1056            None,
1057            None,
1058            None,
1059            None,
1060        );
1061
1062        assert!(mt202.is_cross_currency());
1063        assert!(mt202.instructed_amount().is_some());
1064    }
1065
1066    #[test]
1067    fn test_mt202_timing_detection() {
1068        let field_20 = Field20::new("FT21234567890".to_string());
1069        let field_21 = Field21::new("REL20241201001".to_string());
1070        let field_32a = Field32A::new(
1071            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1072            "USD".to_string(),
1073            1000000.00,
1074        );
1075        let field_58a = Field58A::new(None, None, "DEUTDEFFXXX").unwrap();
1076
1077        // Create time indications with CLS and TARGET timing
1078        let cls_time = Field13C::new("153045+1", "+0100", "-0500").unwrap();
1079        let target_time = Field13C::new("090000+0", "+0100", "+0900").unwrap();
1080        let time_indications = vec![cls_time, target_time];
1081
1082        let mt202 = MT202::new_complete(
1083            field_20,
1084            field_21,
1085            field_32a,
1086            field_58a,
1087            Some(time_indications),
1088            None,
1089            None,
1090            None,
1091            None,
1092            None,
1093            None,
1094            None,
1095            None,
1096            None,
1097            None,
1098            None,
1099            None,
1100            None,
1101            None,
1102        );
1103
1104        assert!(mt202.has_cls_timing());
1105        assert!(mt202.has_target_timing());
1106
1107        let descriptions = mt202.get_time_indications_with_descriptions();
1108        assert_eq!(descriptions.len(), 2);
1109        assert!(descriptions[0].contains("CLS Bank cut-off time"));
1110        assert!(descriptions[1].contains("TARGET system time"));
1111    }
1112
1113    #[test]
1114    fn test_mt202_return_reject_codes() {
1115        let field_20 = Field20::new("FT21234567890".to_string());
1116        let field_21 = Field21::new("REL20241201001".to_string());
1117        let field_32a = Field32A::new(
1118            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1119            "USD".to_string(),
1120            1000000.00,
1121        );
1122        let field_58a = Field58A::new(None, None, "DEUTDEFFXXX").unwrap();
1123
1124        // Test with return codes in sequence A field 72
1125        let field_72_return = Field72::new(vec![
1126            "/RETN/AC01/Account id incorrect".to_string(),
1127            "Additional return information".to_string(),
1128        ])
1129        .unwrap();
1130
1131        let mt202_return = MT202::new_complete(
1132            field_20.clone(),
1133            field_21.clone(),
1134            field_32a.clone(),
1135            field_58a.clone(),
1136            None,
1137            None,
1138            None,
1139            None,
1140            None,
1141            None,
1142            Some(field_72_return),
1143            None,
1144            None,
1145            None,
1146            None,
1147            None,
1148            None,
1149            None,
1150            None,
1151        );
1152
1153        assert!(mt202_return.has_return_codes());
1154        assert!(!mt202_return.has_reject_codes());
1155
1156        // Test with reject codes in sequence B field 72
1157        let field_72_reject = Field72::new(vec![
1158            "/REJT/AC03/Account id invalid".to_string(),
1159            "Additional reject information".to_string(),
1160        ])
1161        .unwrap();
1162
1163        let mt202_reject = MT202::new_complete(
1164            field_20.clone(),
1165            field_21.clone(),
1166            field_32a.clone(),
1167            field_58a.clone(),
1168            None,
1169            None,
1170            None,
1171            None,
1172            None,
1173            None,
1174            None,
1175            None,
1176            None,
1177            None,
1178            None,
1179            None,
1180            None,
1181            None,
1182            Some(field_72_reject),
1183        );
1184
1185        assert!(!mt202_reject.has_return_codes());
1186        assert!(mt202_reject.has_reject_codes());
1187
1188        // Test without return/reject codes
1189        let field_72_normal = Field72::new(vec![
1190            "/INT/Internal transfer own accts".to_string(),
1191            "Regular processing instructions".to_string(),
1192        ])
1193        .unwrap();
1194
1195        let mt202_normal = MT202::new_complete(
1196            field_20,
1197            field_21,
1198            field_32a,
1199            field_58a,
1200            None,
1201            None,
1202            None,
1203            None,
1204            None,
1205            None,
1206            Some(field_72_normal),
1207            None,
1208            None,
1209            None,
1210            None,
1211            None,
1212            None,
1213            None,
1214            None,
1215        );
1216
1217        assert!(!mt202_normal.has_return_codes());
1218        assert!(!mt202_normal.has_reject_codes());
1219    }
1220
1221    #[test]
1222    fn test_mt202_cover_payment_alias() {
1223        let field_20 = Field20::new("FT21234567890".to_string());
1224        let field_21 = Field21::new("REL20241201001".to_string());
1225        let field_32a = Field32A::new(
1226            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1227            "USD".to_string(),
1228            1000000.00,
1229        );
1230        let field_58a = Field58A::new(None, None, "DEUTDEFFXXX").unwrap();
1231        let field_50a = Field50::K(Field50K::new(vec!["ORDERING CUSTOMER".to_string()]).unwrap());
1232
1233        // Test cover payment fields presence
1234        let mt202_cover = MT202::new_complete(
1235            field_20,
1236            field_21,
1237            field_32a,
1238            field_58a,
1239            None,
1240            None,
1241            None,
1242            None,
1243            None,
1244            None,
1245            None,
1246            Some(field_50a),
1247            None,
1248            None,
1249            None,
1250            None,
1251            None,
1252            None,
1253            None,
1254        );
1255
1256        // Test that cover payment fields are accessible
1257        assert!(mt202_cover.ordering_customer().is_some());
1258        assert!(mt202_cover.validate_cover_message());
1259
1260        // Cover message detection requires correspondent banks or explicit BIC comparison
1261        // Having just customer fields doesn't make it a cover message by the SWIFT standard
1262        assert!(!mt202_cover.is_cover_message_from_fields());
1263    }
1264}