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}