swift_mt_message/fields/
field64.rs

1use super::swift_utils::{parse_amount, parse_currency, parse_date_yymmdd, parse_exact_length};
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4use chrono::NaiveDate;
5use serde::{Deserialize, Serialize};
6
7/// **Field 64: Closing Available Balance**
8///
9/// Funds immediately available for use, accounting for holds and pending transactions.
10///
11/// **Format:** `1!a6!n3!a15d` (D/C mark + YYMMDD + currency + amount)
12///
13/// **Example:**
14/// ```text
15/// :64:C231225USD1234,56
16/// ```
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct Field64 {
19    /// Debit/Credit mark (D or C)
20    pub debit_credit_mark: String,
21
22    /// Value date (YYMMDD)
23    pub value_date: NaiveDate,
24
25    /// ISO 4217 currency code
26    pub currency: String,
27
28    /// Available balance amount
29    pub amount: f64,
30}
31
32impl SwiftField for Field64 {
33    fn parse(input: &str) -> crate::Result<Self>
34    where
35        Self: Sized,
36    {
37        // Format: 1!a6!n3!a15d - DebitCredit + Date + Currency + Amount
38        if input.len() < 10 {
39            return Err(ParseError::InvalidFormat {
40                message: "Field 64 must be at least 10 characters long".to_string(),
41            });
42        }
43
44        // Parse debit/credit mark (1 character)
45        let debit_credit_mark = parse_exact_length(&input[0..1], 1, "Field 64 debit/credit mark")?;
46        if debit_credit_mark != "D" && debit_credit_mark != "C" {
47            return Err(ParseError::InvalidFormat {
48                message: "Field 64 debit/credit mark must be 'D' or 'C'".to_string(),
49            });
50        }
51
52        // Parse value date (6 digits)
53        let date_str = parse_exact_length(&input[1..7], 6, "Field 64 value date")?;
54        let value_date = parse_date_yymmdd(&date_str)?;
55
56        // Parse currency (3 characters)
57        let currency = parse_exact_length(&input[7..10], 3, "Field 64 currency")?;
58        let currency = parse_currency(&currency)?;
59
60        // Parse amount (remaining characters)
61        let amount_str = &input[10..];
62        let amount = parse_amount(amount_str)?;
63
64        Ok(Field64 {
65            debit_credit_mark,
66            value_date,
67            currency,
68            amount,
69        })
70    }
71
72    fn to_swift_string(&self) -> String {
73        format!(
74            ":64:{}{}{}{}",
75            self.debit_credit_mark,
76            self.value_date.format("%y%m%d"),
77            self.currency,
78            format!("{:.2}", self.amount).replace('.', ",")
79        )
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use chrono::NaiveDate;
87
88    #[test]
89    fn test_field64_parse_valid() {
90        let field = Field64::parse("C231225USD1234,56").unwrap();
91        assert_eq!(field.debit_credit_mark, "C");
92        assert_eq!(
93            field.value_date,
94            NaiveDate::from_ymd_opt(2023, 12, 25).unwrap()
95        );
96        assert_eq!(field.currency, "USD");
97        assert_eq!(field.amount, 1234.56);
98    }
99
100    #[test]
101    fn test_field64_parse_debit() {
102        let field = Field64::parse("D991231EUR500,00").unwrap();
103        assert_eq!(field.debit_credit_mark, "D");
104        assert_eq!(
105            field.value_date,
106            NaiveDate::from_ymd_opt(1999, 12, 31).unwrap()
107        );
108        assert_eq!(field.currency, "EUR");
109        assert_eq!(field.amount, 500.00);
110    }
111
112    #[test]
113    fn test_field64_invalid_debit_credit_mark() {
114        assert!(Field64::parse("X231225USD1234,56").is_err());
115    }
116
117    #[test]
118    fn test_field64_too_short() {
119        assert!(Field64::parse("C2312").is_err());
120    }
121
122    #[test]
123    fn test_field64_to_swift_string() {
124        let field = Field64 {
125            debit_credit_mark: "C".to_string(),
126            value_date: NaiveDate::from_ymd_opt(2023, 12, 25).unwrap(),
127            currency: "USD".to_string(),
128            amount: 1234.56,
129        };
130        assert_eq!(field.to_swift_string(), ":64:C231225USD1234,56");
131    }
132
133    #[test]
134    fn test_field64_debit_to_swift_string() {
135        let field = Field64 {
136            debit_credit_mark: "D".to_string(),
137            value_date: NaiveDate::from_ymd_opt(2023, 12, 25).unwrap(),
138            currency: "EUR".to_string(),
139            amount: 500.00,
140        };
141        assert_eq!(field.to_swift_string(), ":64:D231225EUR500,00");
142    }
143}