///|
/// One twenty-character component of DE54 Additional Amounts.
pub(all) struct AdditionalAmount {
account_type : String
amount_type : String
currency : String
sign : UInt16
amount : String
} derive(Eq, Debug)
///|
/// Named meaning for common DE54 amount type codes.
pub(all) enum AdditionalAmountType {
LedgerBalance
AvailableBalance
CashAmount
PurchaseAmount
CreditLimit
UnknownAdditionalAmount(String)
} derive(Eq, Debug)
///|
/// Parse one fixed-width DE54 component.
pub fn parse_additional_amount_component(
value : String,
) -> Result[AdditionalAmount, IsoError] {
if value.length() != 20 {
return Err(InvalidAmount(value))
}
let account = value[0:2].to_owned()
let amount_type = value[2:4].to_owned()
let currency = value[4:7].to_owned()
let sign = value[7]
let amount = value[8:20].to_owned()
match validate_numeric_text(54, account) {
Err(_) => return Err(InvalidAmount(value))
Ok(_) => ()
}
match validate_numeric_text(54, amount_type) {
Err(_) => return Err(InvalidAmount(value))
Ok(_) => ()
}
match parse_currency(currency) {
Err(_) => return Err(InvalidAmount(value))
Ok(_) => ()
}
if sign != 'C' && sign != 'D' {
return Err(InvalidAmount(value))
}
match validate_numeric_text(54, amount) {
Err(_) => return Err(InvalidAmount(value))
Ok(_) => ()
}
Ok({ account_type: account, amount_type, currency, sign, amount, })
}
///|
/// Parse concatenated DE54 amount components.
pub fn parse_additional_amounts(
value : String,
) -> Result[Array[AdditionalAmount], IsoError] {
if value.length() == 0 || value.length() % 20 != 0 {
return Err(InvalidAmount(value))
}
let result : Array[AdditionalAmount] = []
let mut offset = 0
while offset < value.length() {
let component = value[offset:offset + 20].to_owned()
match parse_additional_amount_component(component) {
Ok(amount) => result.push(amount)
Err(error) => return Err(error)
}
offset += 20
}
Ok(result)
}
///|
/// Build one DE54 component from validated pieces.
pub fn additional_amount(
account_type_code : String,
amount_type_code : String,
currency : String,
debit : Bool,
minor_units : Int64,
) -> Result[AdditionalAmount, IsoError] {
if account_type_code.length() != 2 || amount_type_code.length() != 2 {
return Err(InvalidAmount(account_type_code + amount_type_code))
}
match validate_numeric_text(54, account_type_code) {
Err(_) => return Err(InvalidAmount(account_type_code))
Ok(_) => ()
}
match validate_numeric_text(54, amount_type_code) {
Err(_) => return Err(InvalidAmount(amount_type_code))
Ok(_) => ()
}
match parse_currency(currency) {
Err(error) => return Err(error)
Ok(_) => ()
}
let amount = match format_minor_amount(minor_units, 12) {
Ok(value) => value
Err(error) => return Err(error)
}
Ok({
account_type: account_type_code,
amount_type: amount_type_code,
currency,
sign: if debit {
'D'
} else {
'C'
},
amount,
})
}
///|
/// Render one DE54 component.
pub fn AdditionalAmount::to_string(self : AdditionalAmount) -> String {
self.account_type +
self.amount_type +
self.currency +
self.sign.to_int().unsafe_to_char().to_string() +
self.amount
}
///|
/// Render multiple DE54 components in wire order.
pub fn format_additional_amounts(values : Array[AdditionalAmount]) -> String {
let out = StringBuilder()
for value in values {
out.write_string(value.to_string())
}
out.to_string()
}
///|
/// Decode the amount type code into a useful label.
pub fn AdditionalAmount::kind(self : AdditionalAmount) -> AdditionalAmountType {
match self.amount_type {
"01" => LedgerBalance
"02" => AvailableBalance
"40" => CashAmount
"41" => PurchaseAmount
"61" => CreditLimit
other => UnknownAdditionalAmount(other)
}
}
///|
/// Signed minor units from the component.
pub fn AdditionalAmount::signed_minor_units(self : AdditionalAmount) -> Int64 {
let mut value : Int64 = 0
for i = 0; i < self.amount.length(); i = i + 1 {
value = value * 10 + (self.amount[i].to_int() - 48).to_int64()
}
if self.sign == 'D' {
0L - value
} else {
value
}
}
///|
/// Render the component with currency metadata and decimal scale.
pub fn AdditionalAmount::display(
self : AdditionalAmount,
) -> Result[String, IsoError] {
let currency = match parse_currency(self.currency) {
Ok(value) => value
Err(error) => return Err(error)
}
let scale = currency.minor_units.unwrap_or(2)
let unsigned = match parse_minor_amount(54, self.amount, 12, scale) {
Ok(value) => value
Err(error) => return Err(error)
}
let sign = if self.sign == 'D' { "-" } else { "" }
let name = currency.alpha.unwrap_or(currency.numeric)
Ok("\{name} \{sign}\{unsigned.display()}")
}
///|
/// Find the first component with the requested amount type.
pub fn find_additional_amount(
values : Array[AdditionalAmount],
amount_type : String,
) -> AdditionalAmount? {
for value in values {
if value.amount_type == amount_type {
return Some(value)
}
}
None
}