use crate::errors::ParseError;
use crate::errors::SwiftValidationError;
use crate::fields::*;
use crate::parser::MessageParser;
use crate::parser::utils::*;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct MT204 {
#[serde(rename = "20")]
pub transaction_reference: Field20,
#[serde(rename = "19")]
pub sum_of_amounts: Field19,
#[serde(rename = "30")]
pub execution_date: Field30,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub account_with_institution: Option<Field57>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub beneficiary_institution: Option<Field58>,
#[serde(rename = "72", skip_serializing_if = "Option::is_none")]
pub sender_to_receiver: Option<Field72>,
#[serde(rename = "#", default)]
pub transactions: Vec<MT204Transaction>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct MT204Transaction {
#[serde(rename = "20")]
pub transaction_reference: Field20,
#[serde(rename = "21", skip_serializing_if = "Option::is_none")]
pub related_reference: Option<Field21NoOption>,
#[serde(rename = "32B")]
pub currency_amount: Field32B,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub senders_correspondent: Option<Field53>,
#[serde(rename = "72", skip_serializing_if = "Option::is_none")]
pub sender_to_receiver: Option<Field72>,
}
impl MT204 {
pub fn parse_from_block4(block4: &str) -> Result<Self, ParseError> {
let mut parser = MessageParser::new(block4, "204");
let sum_of_amounts = parser.parse_field::<Field19>("19")?;
let transaction_reference = parser.parse_field::<Field20>("20")?;
let execution_date = parser.parse_field::<Field30>("30")?;
let account_with_institution = parser.parse_optional_variant_field::<Field57>("57")?;
let beneficiary_institution = parser.parse_optional_variant_field::<Field58>("58")?;
let sender_to_receiver = parser.parse_optional_field::<Field72>("72")?;
parser = parser.with_duplicates(true);
let mut transactions = Vec::new();
while parser.detect_field("20") {
let transaction_reference = parser.parse_field::<Field20>("20")?;
let related_reference = parser.parse_optional_field::<Field21NoOption>("21")?;
let currency_amount = parser.parse_field::<Field32B>("32B")?;
let senders_correspondent = parser.parse_optional_variant_field::<Field53>("53")?;
let sender_to_receiver = parser.parse_optional_field::<Field72>("72")?;
transactions.push(MT204Transaction {
transaction_reference,
related_reference,
currency_amount,
senders_correspondent,
sender_to_receiver,
});
if transactions.len() >= 10 {
break;
}
}
Ok(MT204 {
transaction_reference,
sum_of_amounts,
execution_date,
account_with_institution,
beneficiary_institution,
sender_to_receiver,
transactions,
})
}
const MAX_SEQUENCE_B_OCCURRENCES: usize = 10;
fn calculate_sum_of_transactions(&self) -> f64 {
self.transactions
.iter()
.map(|tx| tx.currency_amount.amount)
.sum()
}
fn get_transaction_currencies(&self) -> HashSet<String> {
self.transactions
.iter()
.map(|tx| tx.currency_amount.currency.clone())
.collect()
}
fn validate_c1_sum_of_amounts(&self) -> Option<SwiftValidationError> {
if self.transactions.is_empty() {
return None; }
let sum_of_transactions = self.calculate_sum_of_transactions();
let field_19_amount = self.sum_of_amounts.amount;
let difference = (field_19_amount - sum_of_transactions).abs();
if difference > 0.01 {
return Some(SwiftValidationError::content_error(
"C01",
"19",
&field_19_amount.to_string(),
&format!(
"Sum of amounts in field 19 ({:.2}) must equal the sum of all field 32B amounts ({:.2}). Difference: {:.2}",
field_19_amount, sum_of_transactions, difference
),
"The amount in field 19 must equal the sum of the amounts in all occurrences of field 32B",
));
}
None
}
fn validate_c2_currency_consistency(&self) -> Option<SwiftValidationError> {
if self.transactions.is_empty() {
return None;
}
let currencies = self.get_transaction_currencies();
if currencies.len() > 1 {
let currency_list: Vec<String> = currencies.into_iter().collect();
return Some(SwiftValidationError::content_error(
"C02",
"32B",
¤cy_list.join(", "),
&format!(
"All occurrences of field 32B must have the same currency code. Found currencies: {}",
currency_list.join(", ")
),
"The currency code in the amount field 32B must be the same for all occurrences of this field in the message",
));
}
None
}
fn validate_c3_max_sequences(&self) -> Option<SwiftValidationError> {
let count = self.transactions.len();
if count > Self::MAX_SEQUENCE_B_OCCURRENCES {
return Some(SwiftValidationError::content_error(
"T10",
"Sequence B",
&count.to_string(),
&format!(
"The repetitive sequence B appears {} times, which exceeds the maximum of {} occurrences",
count,
Self::MAX_SEQUENCE_B_OCCURRENCES
),
"The repetitive sequence must not appear more than ten times",
));
}
None
}
pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
let mut all_errors = Vec::new();
if let Some(error) = self.validate_c1_sum_of_amounts() {
all_errors.push(error);
if stop_on_first_error {
return all_errors;
}
}
if let Some(error) = self.validate_c2_currency_consistency() {
all_errors.push(error);
if stop_on_first_error {
return all_errors;
}
}
if let Some(error) = self.validate_c3_max_sequences() {
all_errors.push(error);
if stop_on_first_error {
return all_errors;
}
}
all_errors
}
}
impl crate::traits::SwiftMessageBody for MT204 {
fn message_type() -> &'static str {
"204"
}
fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
MT204::parse_from_block4(block4)
}
fn to_mt_string(&self) -> String {
let mut result = String::new();
append_field(&mut result, &self.sum_of_amounts);
append_field(&mut result, &self.transaction_reference);
append_field(&mut result, &self.execution_date);
append_optional_field(&mut result, &self.account_with_institution);
append_optional_field(&mut result, &self.beneficiary_institution);
append_optional_field(&mut result, &self.sender_to_receiver);
for txn in &self.transactions {
append_field(&mut result, &txn.transaction_reference);
append_optional_field(&mut result, &txn.related_reference);
append_field(&mut result, &txn.currency_amount);
append_optional_field(&mut result, &txn.senders_correspondent);
append_optional_field(&mut result, &txn.sender_to_receiver);
}
finalize_mt_string(result, false)
}
fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
MT204::validate_network_rules(self, stop_on_first_error)
}
}