swift_mt_message/messages/
mt204.rs1use crate::errors::ParseError;
2use crate::errors::SwiftValidationError;
3use crate::fields::*;
4use crate::parser::MessageParser;
5use crate::parser::utils::*;
6use serde::{Deserialize, Serialize};
7use std::collections::HashSet;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct MT204 {
17 #[serde(rename = "20")]
19 pub transaction_reference: Field20,
20
21 #[serde(rename = "19")]
23 pub sum_of_amounts: Field19,
24
25 #[serde(rename = "30")]
27 pub execution_date: Field30,
28
29 #[serde(flatten, skip_serializing_if = "Option::is_none")]
31 pub account_with_institution: Option<Field57>,
32
33 #[serde(flatten, skip_serializing_if = "Option::is_none")]
35 pub beneficiary_institution: Option<Field58>,
36
37 #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
39 pub sender_to_receiver: Option<Field72>,
40
41 #[serde(rename = "#", default)]
43 pub transactions: Vec<MT204Transaction>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
48pub struct MT204Transaction {
49 #[serde(rename = "20")]
51 pub transaction_reference: Field20,
52
53 #[serde(rename = "21", skip_serializing_if = "Option::is_none")]
55 pub related_reference: Option<Field21NoOption>,
56
57 #[serde(rename = "32B")]
59 pub currency_amount: Field32B,
60
61 #[serde(flatten, skip_serializing_if = "Option::is_none")]
63 pub senders_correspondent: Option<Field53>,
64
65 #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
67 pub sender_to_receiver: Option<Field72>,
68}
69
70impl MT204 {
71 pub fn parse_from_block4(block4: &str) -> Result<Self, ParseError> {
73 let mut parser = MessageParser::new(block4, "204");
74
75 let sum_of_amounts = parser.parse_field::<Field19>("19")?;
77 let transaction_reference = parser.parse_field::<Field20>("20")?;
78 let execution_date = parser.parse_field::<Field30>("30")?;
79
80 let account_with_institution = parser.parse_optional_variant_field::<Field57>("57")?;
82
83 let beneficiary_institution = parser.parse_optional_variant_field::<Field58>("58")?;
85
86 let sender_to_receiver = parser.parse_optional_field::<Field72>("72")?;
88
89 parser = parser.with_duplicates(true);
91 let mut transactions = Vec::new();
92
93 while parser.detect_field("20") {
94 let transaction_reference = parser.parse_field::<Field20>("20")?;
96
97 let related_reference = parser.parse_optional_field::<Field21NoOption>("21")?;
99
100 let currency_amount = parser.parse_field::<Field32B>("32B")?;
102
103 let senders_correspondent = parser.parse_optional_variant_field::<Field53>("53")?;
105
106 let sender_to_receiver = parser.parse_optional_field::<Field72>("72")?;
108
109 transactions.push(MT204Transaction {
110 transaction_reference,
111 related_reference,
112 currency_amount,
113 senders_correspondent,
114 sender_to_receiver,
115 });
116
117 if transactions.len() >= 10 {
119 break;
120 }
121 }
122
123 Ok(MT204 {
124 transaction_reference,
125 sum_of_amounts,
126 execution_date,
127 account_with_institution,
128 beneficiary_institution,
129 sender_to_receiver,
130 transactions,
131 })
132 }
133
134 const MAX_SEQUENCE_B_OCCURRENCES: usize = 10;
140
141 fn calculate_sum_of_transactions(&self) -> f64 {
147 self.transactions
148 .iter()
149 .map(|tx| tx.currency_amount.amount)
150 .sum()
151 }
152
153 fn get_transaction_currencies(&self) -> HashSet<String> {
155 self.transactions
156 .iter()
157 .map(|tx| tx.currency_amount.currency.clone())
158 .collect()
159 }
160
161 fn validate_c1_sum_of_amounts(&self) -> Option<SwiftValidationError> {
168 if self.transactions.is_empty() {
169 return None; }
171
172 let sum_of_transactions = self.calculate_sum_of_transactions();
173 let field_19_amount = self.sum_of_amounts.amount;
174
175 let difference = (field_19_amount - sum_of_transactions).abs();
177
178 if difference > 0.01 {
179 return Some(SwiftValidationError::content_error(
180 "C01",
181 "19",
182 &field_19_amount.to_string(),
183 &format!(
184 "Sum of amounts in field 19 ({:.2}) must equal the sum of all field 32B amounts ({:.2}). Difference: {:.2}",
185 field_19_amount, sum_of_transactions, difference
186 ),
187 "The amount in field 19 must equal the sum of the amounts in all occurrences of field 32B",
188 ));
189 }
190
191 None
192 }
193
194 fn validate_c2_currency_consistency(&self) -> Option<SwiftValidationError> {
197 if self.transactions.is_empty() {
198 return None;
199 }
200
201 let currencies = self.get_transaction_currencies();
202
203 if currencies.len() > 1 {
204 let currency_list: Vec<String> = currencies.into_iter().collect();
205 return Some(SwiftValidationError::content_error(
206 "C02",
207 "32B",
208 ¤cy_list.join(", "),
209 &format!(
210 "All occurrences of field 32B must have the same currency code. Found currencies: {}",
211 currency_list.join(", ")
212 ),
213 "The currency code in the amount field 32B must be the same for all occurrences of this field in the message",
214 ));
215 }
216
217 None
218 }
219
220 fn validate_c3_max_sequences(&self) -> Option<SwiftValidationError> {
223 let count = self.transactions.len();
224
225 if count > Self::MAX_SEQUENCE_B_OCCURRENCES {
226 return Some(SwiftValidationError::content_error(
227 "T10",
228 "Sequence B",
229 &count.to_string(),
230 &format!(
231 "The repetitive sequence B appears {} times, which exceeds the maximum of {} occurrences",
232 count,
233 Self::MAX_SEQUENCE_B_OCCURRENCES
234 ),
235 "The repetitive sequence must not appear more than ten times",
236 ));
237 }
238
239 None
240 }
241
242 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
245 let mut all_errors = Vec::new();
246
247 if let Some(error) = self.validate_c1_sum_of_amounts() {
249 all_errors.push(error);
250 if stop_on_first_error {
251 return all_errors;
252 }
253 }
254
255 if let Some(error) = self.validate_c2_currency_consistency() {
257 all_errors.push(error);
258 if stop_on_first_error {
259 return all_errors;
260 }
261 }
262
263 if let Some(error) = self.validate_c3_max_sequences() {
265 all_errors.push(error);
266 if stop_on_first_error {
267 return all_errors;
268 }
269 }
270
271 all_errors
272 }
273}
274
275impl crate::traits::SwiftMessageBody for MT204 {
276 fn message_type() -> &'static str {
277 "204"
278 }
279
280 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
281 MT204::parse_from_block4(block4)
283 }
284
285 fn to_mt_string(&self) -> String {
286 let mut result = String::new();
288
289 append_field(&mut result, &self.sum_of_amounts);
290 append_field(&mut result, &self.transaction_reference);
291 append_field(&mut result, &self.execution_date);
292 append_optional_field(&mut result, &self.account_with_institution);
293 append_optional_field(&mut result, &self.beneficiary_institution);
294 append_optional_field(&mut result, &self.sender_to_receiver);
295
296 for txn in &self.transactions {
298 append_field(&mut result, &txn.transaction_reference);
299 append_optional_field(&mut result, &txn.related_reference);
300 append_field(&mut result, &txn.currency_amount);
301 append_optional_field(&mut result, &txn.senders_correspondent);
302 append_optional_field(&mut result, &txn.sender_to_receiver);
303 }
304
305 finalize_mt_string(result, false)
306 }
307
308 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
309 MT204::validate_network_rules(self, stop_on_first_error)
311 }
312}