swift_mt_message/messages/
mt205.rs

1use crate::errors::{ParseError, SwiftValidationError};
2use crate::fields::*;
3use crate::parser::MessageParser;
4use crate::parser::utils::*;
5use serde::{Deserialize, Serialize};
6
7/// **MT205: Financial Institution Transfer Execution**
8///
9/// Advises execution of transfer previously initiated by MT200 or MT202.
10///
11/// **Usage:** Cover payments, transfer execution advice, additional transfer details
12/// **Category:** Category 2 (Financial Institution Transfers)
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct MT205 {
15    /// Transaction Reference Number (Field 20)
16    #[serde(rename = "20")]
17    pub transaction_reference: Field20,
18
19    /// Related Reference (Field 21)
20    #[serde(rename = "21")]
21    pub related_reference: Field21NoOption,
22
23    /// Time Indication (Field 13C)
24    #[serde(rename = "13C", skip_serializing_if = "Option::is_none")]
25    pub time_indication: Option<Vec<Field13C>>,
26
27    /// Bank Operation Code (Field 23B)
28    #[serde(rename = "23B", skip_serializing_if = "Option::is_none")]
29    pub bank_operation_code: Option<Field23B>,
30
31    /// Value Date, Currency Code, Amount (Field 32A)
32    #[serde(rename = "32A")]
33    pub value_date_amount: Field32A,
34
35    /// Currency Code, Instructed Amount (Field 33B)
36    #[serde(rename = "33B", skip_serializing_if = "Option::is_none")]
37    pub instructed_amount: Option<Field33B>,
38
39    /// Ordering Institution (Field 52)
40    #[serde(flatten, skip_serializing_if = "Option::is_none")]
41    pub ordering_institution: Option<Field52OrderingInstitution>,
42
43    /// Sender's Correspondent (Field 53)
44    #[serde(flatten, skip_serializing_if = "Option::is_none")]
45    pub senders_correspondent: Option<Field53>,
46
47    /// Receiver's Correspondent (Field 54)
48    #[serde(flatten, skip_serializing_if = "Option::is_none")]
49    pub receivers_correspondent: Option<Field54>,
50
51    /// Intermediary Institution (Field 56)
52    #[serde(flatten, skip_serializing_if = "Option::is_none")]
53    pub intermediary: Option<Field56>,
54
55    /// Account With Institution (Field 57)
56    #[serde(flatten, skip_serializing_if = "Option::is_none")]
57    pub account_with_institution: Option<Field57>,
58
59    /// Beneficiary Institution (Field 58)
60    #[serde(flatten)]
61    pub beneficiary_institution: Field58,
62
63    /// Sender to Receiver Information (Field 72)
64    #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
65    pub sender_to_receiver: Option<Field72>,
66}
67
68impl MT205 {
69    /// Parse MT205 from a raw SWIFT message string
70    pub fn parse_from_block4(block4: &str) -> Result<Self, ParseError> {
71        let mut parser = MessageParser::new(block4, "205");
72
73        // Parse mandatory fields
74        let transaction_reference = parser.parse_field::<Field20>("20")?;
75        let related_reference = parser.parse_field::<Field21NoOption>("21")?;
76
77        // Parse optional Field 13C (can be repeated) - enable duplicates mode
78        parser = parser.with_duplicates(true);
79        let mut time_indications = Vec::new();
80        while let Ok(field) = parser.parse_field::<Field13C>("13C") {
81            time_indications.push(field);
82        }
83        parser = parser.with_duplicates(false);
84
85        let time_indication = if time_indications.is_empty() {
86            None
87        } else {
88            Some(time_indications)
89        };
90
91        // Parse optional Field 23B
92        let bank_operation_code = parser.parse_optional_field::<Field23B>("23B")?;
93
94        // Parse mandatory Field 32A
95        let value_date_amount = parser.parse_field::<Field32A>("32A")?;
96
97        // Parse optional Field 33B
98        let instructed_amount = parser.parse_optional_field::<Field33B>("33B")?;
99
100        // Parse optional fields
101        let ordering_institution =
102            parser.parse_optional_variant_field::<Field52OrderingInstitution>("52")?;
103        let senders_correspondent = parser.parse_optional_variant_field::<Field53>("53")?;
104        let receivers_correspondent = parser.parse_optional_variant_field::<Field54>("54")?;
105        let intermediary = parser.parse_optional_variant_field::<Field56>("56")?;
106        let account_with_institution = parser.parse_optional_variant_field::<Field57>("57")?;
107
108        // Parse mandatory Field 58 - Beneficiary Institution
109        let beneficiary_institution = parser.parse_variant_field::<Field58>("58")?;
110
111        // Parse optional Field 72
112        let sender_to_receiver = parser.parse_optional_field::<Field72>("72")?;
113
114        Ok(MT205 {
115            transaction_reference,
116            related_reference,
117            time_indication,
118            bank_operation_code,
119            value_date_amount,
120            instructed_amount,
121            ordering_institution,
122            senders_correspondent,
123            receivers_correspondent,
124            intermediary,
125            account_with_institution,
126            beneficiary_institution,
127            sender_to_receiver,
128        })
129    }
130
131    /// Check if this message has reject codes
132    pub fn has_reject_codes(&self) -> bool {
133        if let Some(ref info) = self.sender_to_receiver {
134            info.information
135                .iter()
136                .any(|line| line.contains("/REJT/") || line.contains("/RJT/"))
137        } else {
138            false
139        }
140    }
141
142    /// Check if this message has return codes
143    pub fn has_return_codes(&self) -> bool {
144        if let Some(ref info) = self.sender_to_receiver {
145            info.information
146                .iter()
147                .any(|line| line.contains("/RETN/") || line.contains("/RET/"))
148        } else {
149            false
150        }
151    }
152
153    /// Check if this is a cover message
154    pub fn is_cover_message(&self) -> bool {
155        if let Some(ref info) = self.sender_to_receiver {
156            info.information
157                .iter()
158                .any(|line| line.contains("/COV/") || line.contains("/COVER/"))
159        } else {
160            false
161        }
162    }
163
164    // ========================================================================
165    // NETWORK VALIDATION RULES (SR 2025 MT205)
166    // ========================================================================
167
168    // ========================================================================
169    // VALIDATION RULES (C1)
170    // ========================================================================
171
172    /// C1: Intermediary and Account With Institution Dependency (Error code: C81)
173    /// If field 56a is present, then field 57a must also be present
174    fn validate_c1_intermediary_account_with(&self) -> Option<SwiftValidationError> {
175        if self.intermediary.is_some() && self.account_with_institution.is_none() {
176            return Some(SwiftValidationError::content_error(
177                "C81",
178                "57a",
179                "",
180                "Field 57a (Account With Institution) is mandatory when field 56a (Intermediary) is present",
181                "If field 56a is present, then field 57a must also be present",
182            ));
183        }
184
185        None
186    }
187
188    /// Main validation method - validates all network rules
189    /// Returns array of validation errors, respects stop_on_first_error flag
190    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
191        let mut all_errors = Vec::new();
192
193        // C1: Intermediary and Account With Institution Dependency
194        if let Some(error) = self.validate_c1_intermediary_account_with() {
195            all_errors.push(error);
196            if stop_on_first_error {
197                return all_errors;
198            }
199        }
200
201        all_errors
202    }
203}
204
205impl crate::traits::SwiftMessageBody for MT205 {
206    fn message_type() -> &'static str {
207        "205"
208    }
209
210    fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
211        Self::parse_from_block4(block4)
212    }
213
214    fn to_mt_string(&self) -> String {
215        let mut result = String::new();
216
217        append_field(&mut result, &self.transaction_reference);
218        append_field(&mut result, &self.related_reference);
219        append_vec_field(&mut result, &self.time_indication);
220        append_optional_field(&mut result, &self.bank_operation_code);
221        append_field(&mut result, &self.value_date_amount);
222        append_optional_field(&mut result, &self.instructed_amount);
223        append_optional_field(&mut result, &self.ordering_institution);
224        append_optional_field(&mut result, &self.senders_correspondent);
225        append_optional_field(&mut result, &self.receivers_correspondent);
226        append_optional_field(&mut result, &self.intermediary);
227        append_optional_field(&mut result, &self.account_with_institution);
228        append_field(&mut result, &self.beneficiary_institution);
229        append_optional_field(&mut result, &self.sender_to_receiver);
230
231        finalize_mt_string(result, false)
232    }
233
234    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
235        // Call the existing public method implementation
236        MT205::validate_network_rules(self, stop_on_first_error)
237    }
238}