use mx20022_model::common::ChoiceWrapper;
use mx20022_model::generated::pacs::pacs_008_001_13 as pacs008;
use crate::mappings::error::TranslationError;
use crate::mt::fields::common::{Account, PartyInfo};
pub fn party_to_party_id(party: &PartyInfo) -> pacs008::PartyIdentification272 {
let nm = party
.name
.as_deref()
.map(|n| pacs008::Max140Text(n.to_string()));
pacs008::PartyIdentification272 {
nm,
pstl_adr: None,
id: None,
ctry_of_res: None,
ctct_dtls: None,
}
}
pub fn party_to_fi_id(party: &PartyInfo) -> pacs008::BranchAndFinancialInstitutionIdentification8 {
let mut bicfi: Option<pacs008::BICFIDec2014Identifier> = None;
let mut nm: Option<pacs008::Max140Text> = None;
if let Some(acct) = &party.account {
if let Some(bic) = &acct.bic {
bicfi = Some(pacs008::BICFIDec2014Identifier(bic.clone()));
}
}
if bicfi.is_none() {
if let Some(n) = &party.name {
nm = Some(pacs008::Max140Text(n.clone()));
}
}
let fin_instn_id = pacs008::FinancialInstitutionIdentification23 {
bicfi,
clr_sys_mmb_id: None,
lei: None,
nm,
pstl_adr: None,
othr: None,
};
pacs008::BranchAndFinancialInstitutionIdentification8 {
fin_instn_id,
brnch_id: None,
}
}
pub fn empty_fi_id() -> pacs008::BranchAndFinancialInstitutionIdentification8 {
pacs008::BranchAndFinancialInstitutionIdentification8 {
fin_instn_id: pacs008::FinancialInstitutionIdentification23 {
bicfi: None,
clr_sys_mmb_id: None,
lei: None,
nm: None,
pstl_adr: None,
othr: None,
},
brnch_id: None,
}
}
pub fn account_to_cash_account(acct: &Account) -> Option<pacs008::CashAccount40> {
let id_choice = acct
.iban
.as_ref()
.map(|iban| {
ChoiceWrapper::new(pacs008::AccountIdentification4Choice::IBAN(
pacs008::IBAN2007Identifier(iban.clone()),
))
})
.or_else(|| {
acct.account.as_ref().map(|raw| {
ChoiceWrapper::new(pacs008::AccountIdentification4Choice::Othr(
pacs008::GenericAccountIdentification1 {
id: pacs008::Max34Text(raw.clone()),
schme_nm: None,
issr: None,
},
))
})
});
id_choice.map(|id| pacs008::CashAccount40 {
id: Some(id),
tp: None,
ccy: None,
nm: None,
prxy: None,
})
}
#[allow(clippy::match_same_arms)] pub fn charges_to_code(charges: &str) -> pacs008::ChargeBearerType1Code {
match charges.trim() {
"SHA" => pacs008::ChargeBearerType1Code::Shar,
"OUR" => pacs008::ChargeBearerType1Code::Debt,
"BEN" => pacs008::ChargeBearerType1Code::Cred,
_ => pacs008::ChargeBearerType1Code::Shar,
}
}
#[allow(clippy::match_same_arms)] pub fn code_to_charges(code: &pacs008::ChargeBearerType1Code) -> &'static str {
match code {
pacs008::ChargeBearerType1Code::Debt => "OUR",
pacs008::ChargeBearerType1Code::Cred => "BEN",
pacs008::ChargeBearerType1Code::Shar => "SHA",
pacs008::ChargeBearerType1Code::Slev => "SHA", }
}
pub fn iso_date_to_yymmdd(iso: &str) -> Result<String, TranslationError> {
let parts: Vec<&str> = iso.split('-').collect();
if parts.len() != 3 || parts[0].len() != 4 || parts[1].len() != 2 || parts[2].len() != 2 {
return Err(TranslationError::InvalidFieldValue {
field: "date".into(),
detail: format!("expected ISO date YYYY-MM-DD, got '{iso}'"),
});
}
let yy = &parts[0][2..]; Ok(format!("{}{}{}", yy, parts[1], parts[2]))
}
pub fn format_mt_message(
msg_type: &str,
sender_bic: &str,
receiver_bic: &str,
fields: &[(String, String)],
) -> String {
let sender_lt = pad_lt_address(sender_bic);
let receiver_lt = pad_lt_address(receiver_bic);
let mut body = String::new();
for (tag, value) in fields {
body.push(':');
body.push_str(tag);
body.push(':');
body.push_str(value);
body.push('\n');
}
format!(
"{{1:F01{sender_lt}0000000000}}{{2:I{msg_type}{receiver_lt}N}}{{4:\n{body}-}}{{5:{{CHK:000000000000}}}}"
)
}
pub(crate) fn pad_lt_address(bic: &str) -> String {
let clean: String = bic.chars().filter(char::is_ascii_alphanumeric).collect();
match clean.len() {
8 => format!("{clean}XXXX"),
11 => format!("{clean}X"),
12 => clean,
n if n < 8 => format!("{clean:X<12}"), _ => clean[..12].to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_iso_date_to_yymmdd() {
assert_eq!(iso_date_to_yymmdd("2023-06-15").unwrap(), "230615");
assert_eq!(iso_date_to_yymmdd("1999-12-31").unwrap(), "991231");
}
#[test]
fn test_iso_date_to_yymmdd_invalid() {
assert!(iso_date_to_yymmdd("2023-6-15").is_err());
assert!(iso_date_to_yymmdd("20230615").is_err());
}
#[test]
fn test_charges_to_code() {
assert!(matches!(
charges_to_code("SHA"),
pacs008::ChargeBearerType1Code::Shar
));
assert!(matches!(
charges_to_code("OUR"),
pacs008::ChargeBearerType1Code::Debt
));
assert!(matches!(
charges_to_code("BEN"),
pacs008::ChargeBearerType1Code::Cred
));
}
#[test]
fn test_code_to_charges() {
assert_eq!(
code_to_charges(&pacs008::ChargeBearerType1Code::Shar),
"SHA"
);
assert_eq!(
code_to_charges(&pacs008::ChargeBearerType1Code::Debt),
"OUR"
);
assert_eq!(
code_to_charges(&pacs008::ChargeBearerType1Code::Cred),
"BEN"
);
}
#[test]
fn test_party_to_party_id_name() {
let party = PartyInfo {
name: Some("JOHN DOE".into()),
..Default::default()
};
let id = party_to_party_id(&party);
assert_eq!(id.nm.as_ref().map(|n| n.0.as_str()), Some("JOHN DOE"));
}
#[test]
fn test_account_to_cash_account_iban() {
let acct = Account {
iban: Some("DE89370400440532013000".into()),
bic: None,
account: None,
};
let ca = account_to_cash_account(&acct).unwrap();
if let Some(ChoiceWrapper {
inner: pacs008::AccountIdentification4Choice::IBAN(iban),
}) = ca.id
{
assert_eq!(iban.0, "DE89370400440532013000");
} else {
panic!("expected IBAN variant");
}
}
#[test]
fn test_account_to_cash_account_empty() {
let acct = Account {
iban: None,
bic: None,
account: None,
};
assert!(account_to_cash_account(&acct).is_none());
}
}