swift_mt_message/messages/
mt205.rs

1use crate::fields::*;
2use serde::{Deserialize, Serialize};
3use swift_mt_message_macros::{SwiftMessage, serde_swift_fields};
4
5/// # MT205: General Financial Institution Transfer
6///
7/// This message enables financial institutions to transfer funds between themselves for their own
8/// account or for the account of their customers. Similar to MT202 but with key structural
9/// differences: field 54a is not present and field 52a is always mandatory.
10///
11/// ## Key Differences from MT202
12/// - **Field 54a**: Not present in MT205 (completely absent)
13/// - **Field 52a**: Always mandatory (no fallback to sender BIC)
14/// - **Settlement Logic**: Uses METAFCT003 (simplified scenarios)
15/// - **Cover Detection**: Based on Sequence B presence
16///
17/// ## Message Variants
18/// - **MT205**: Standard financial institution transfer
19/// - **MT205.COV**: Cover message for customer credit transfers
20/// - **MT205.REJT**: Rejection message
21/// - **MT205.RETN**: Return message
22///
23/// ## Structure
24/// - **Sequence A**: Bank-to-bank financial institution details
25/// - **Sequence B**: Customer details (COV variant only)
26#[serde_swift_fields]
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, SwiftMessage)]
28#[validation_rules(MT205_VALIDATION_RULES)]
29pub struct MT205 {
30    // Sequence A: Mandatory Fields
31    #[field("20", mandatory)]
32    pub field_20: GenericReferenceField, // Transaction Reference Number
33
34    #[field("21", mandatory)]
35    pub field_21: GenericReferenceField, // Related Reference
36
37    #[field("32A", mandatory)]
38    pub field_32a: Field32A, // Value Date/Currency/Amount
39
40    #[field("52A", mandatory)]
41    pub field_52a: GenericBicField, // Ordering Institution (MANDATORY in MT205)
42
43    #[field("58A", mandatory)]
44    pub field_58a: GenericBicField, // Beneficiary Institution
45
46    // Sequence A: Optional Fields
47    #[field("13C", optional)]
48    pub field_13c: Option<Vec<Field13C>>, // Time Indication (repetitive)
49
50    #[field("53A", optional)]
51    pub field_53a: Option<GenericBicField>, // Sender's Correspondent
52
53    #[field("56A", optional)]
54    pub field_56a: Option<GenericBicField>, // Intermediary Institution
55
56    #[field("57A", optional)]
57    pub field_57a: Option<GenericBicField>, // Account With Institution
58
59    #[field("72", optional)]
60    pub field_72: Option<GenericMultiLine6x35>, // Sender to Receiver Information
61
62    // Sequence B: COV Cover Message Fields (Optional)
63    #[field("50A", optional)]
64    pub field_50a: Option<Field50>, // Ordering Customer
65
66    #[field("52A_SEQ_B", optional)]
67    pub field_52a_seq_b: Option<GenericBicField>, // Ordering Institution (Seq B)
68
69    #[field("56A_SEQ_B", optional)]
70    pub field_56a_seq_b: Option<GenericBicField>, // Intermediary Institution (Seq B)
71
72    #[field("57A_SEQ_B", optional)]
73    pub field_57a_seq_b: Option<GenericBicField>, // Account With Institution (Seq B)
74
75    #[field("59A", optional)]
76    pub field_59a: Option<GenericBicField>, // Beneficiary Customer
77
78    #[field("70", optional)]
79    pub field_70: Option<GenericMultiLine4x35>, // Remittance Information
80
81    #[field("72_SEQ_B", optional)]
82    pub field_72_seq_b: Option<GenericMultiLine6x35>, // Sender to Receiver Info (Seq B)
83
84    #[field("33B", optional)]
85    pub field_33b: Option<GenericCurrencyAmountField>, // Currency/Instructed Amount
86}
87
88impl MT205 {
89    /// Check if this MT202 message contains reject codes
90    ///
91    /// Reject messages are identified by checking:
92    /// 1. Field 20 (Transaction Reference) for "REJT" prefix or content
93    /// 2. Field 72 (Sender to Receiver Information) containing `/REJT/` codes
94    /// 3. Additional structured reject information in field 72
95    pub fn has_reject_codes(&self) -> bool {
96        // Check field 20 (transaction reference)
97        if self.field_20.value.to_uppercase().contains("REJT") {
98            return true;
99        }
100
101        // Check field 72 for structured reject codes
102        if let Some(field_72) = &self.field_72 {
103            let content = field_72.lines.join(" ").to_uppercase();
104            if content.contains("/REJT/") || content.contains("REJT") {
105                return true;
106            }
107        }
108
109        false
110    }
111
112    /// Check if this MT202 message contains return codes
113    ///
114    /// Return messages are identified by checking:
115    /// 1. Field 20 (Transaction Reference) for "RETN" prefix or content
116    /// 2. Field 72 (Sender to Receiver Information) containing `/RETN/` codes
117    /// 3. Additional structured return information in field 72
118    pub fn has_return_codes(&self) -> bool {
119        // Check field 20 (transaction reference)
120        if self.field_20.value.to_uppercase().contains("RETN") {
121            return true;
122        }
123
124        // Check field 72 for structured return codes
125        if let Some(field_72) = &self.field_72 {
126            let content = field_72.lines.join(" ").to_uppercase();
127            if content.contains("/RETN/") || content.contains("RETN") {
128                return true;
129            }
130        }
131
132        false
133    }
134
135    /// Check if this MT205 message is a Cover (COV) message
136    ///
137    /// COV messages are distinguished by:
138    /// - Presence of Sequence B customer fields (50a, 59a)
139    /// - Additional underlying customer credit transfer details
140    ///
141    /// Based on the MT205 specification: "Cover Detection: Based on presence of Sequence B customer fields (50a, 59a)"
142    pub fn is_cover_message(&self) -> bool {
143        // The key distinguishing feature of COV is the presence of Sequence B customer fields
144        // According to spec: field 50a (Ordering Customer) or field 59a (Beneficiary Customer)
145        self.field_50a.is_some() || self.field_59a.is_some()
146    }
147}
148
149/// Validation rules for MT205 - General Financial Institution Transfer
150const MT205_VALIDATION_RULES: &str = r#"{
151  "rules": [
152    {
153      "id": "C1",
154      "description": "Transaction Reference (20) must not start or end with '/' and must not contain '//'",
155      "condition": {
156        "and": [
157          {"!": {"matches": [{"var": "field_20.value"}, "^/"]}},
158          {"!": {"matches": [{"var": "field_20.value"}, "/$"]}},
159          {"!": {"matches": [{"var": "field_20.value"}, "//"]}}
160        ]
161      }
162    },
163    {
164      "id": "C2",
165      "description": "Related Reference (21) must not start or end with '/' and must not contain '//'",
166      "condition": {
167        "and": [
168          {"!": {"matches": [{"var": "field_21.value"}, "^/"]}},
169          {"!": {"matches": [{"var": "field_21.value"}, "/$"]}},
170          {"!": {"matches": [{"var": "field_21.value"}, "//"]}}
171        ]
172      }
173    },
174    {
175      "id": "C3",
176      "description": "Field 52a is mandatory in MT205 (no fallback to sender BIC)",
177      "condition": {
178        "!=": [{"var": "field_52a.bic"}, ""]
179      }
180    },
181    {
182      "id": "C4",
183      "description": "Field 54a is not present in MT205 (structural difference from MT202)",
184      "condition": true
185    },
186    {
187      "id": "C5",
188      "description": "Cover message detection based on Sequence B customer fields presence",
189      "condition": {
190        "if": [
191          {"or": [
192            {"var": "field_50a.is_some"},
193            {"var": "field_59a.is_some"},
194            {"var": "field_70.is_some"}
195          ]},
196          {"var": "field_52a_seq_b.is_some"},
197          true
198        ]
199      }
200    },
201    {
202      "id": "C6",
203      "description": "Cross-currency validation: if 33B present, currency should differ from 32A",
204      "condition": {
205        "if": [
206          {"var": "field_33b.is_some"},
207          {"!=": [{"var": "field_33b.currency"}, {"var": "field_32a.currency"}]},
208          true
209        ]
210      }
211    },
212    {
213      "id": "C7",
214      "description": "REJT/RETN indicator validation in field 72",
215      "condition": {
216        "if": [
217          {"var": "field_72.is_some"},
218          {"or": [
219            {"!": {"matches": [{"var": "field_72.lines"}, "/REJT/"]}},
220            {"!": {"matches": [{"var": "field_72.lines"}, "/RETN/"]}},
221            true
222          ]},
223          true
224        ]
225      }
226    },
227    {
228      "id": "C8",
229      "description": "Time indication validation for CLS/TARGET timing",
230      "condition": {
231        "if": [
232          {"var": "field_13c.is_some"},
233          {"allValid": [
234            {"var": "field_13c"},
235            {"matches": [{"var": "time_code"}, "^(SNDTIME|RNCTIME|CLSTIME|TILTIME|FROTIME|REJTIME)$"]}
236          ]},
237          true
238        ]
239      }
240    },
241    {
242      "id": "C9",
243      "description": "Settlement method determination (METAFCT003 - simplified scenarios)",
244      "condition": {
245        "if": [
246          {"var": "field_53a.is_some"},
247          {"!=": [{"var": "field_53a.bic"}, {"var": "field_52a.bic"}]},
248          true
249        ]
250      }
251    }
252  ]
253}"#;