lib/aba/batch.rb
# frozen_string_literal: true
class Aba
class Batch
include Aba::Validations
attr_accessor :bsb, :financial_institution, :user_name, :user_id, :description, :process_at, :entries
# BSB
validates_bsb :bsb, allow_blank: true
# Financial Institution
validates_length :financial_institution, 3
# User Name
validates_presence_of :user_name
validates_max_length :user_name, 26
validates_becs :user_name
# User ID
validates_presence_of :user_id
validates_max_length :user_id, 6
validates_integer :user_id, false
# Description
validates_max_length :description, 12
validates_becs :description
# Process at Date
validates_length :process_at, 6
validates_integer :process_at, false
# Totals
validates_max_length :debit_total_amount, 10
validates_max_length :credit_total_amount, 10
def initialize(attrs = {}, transactions = [])
attrs.each do |key, value|
send("#{key}=", value)
end
@entries = []
unless transactions.nil? || transactions.empty?
transactions.to_a.each do |t|
self.add_transaction(t) unless t.nil? || t.empty?
end
end
yield self if block_given?
end
def to_s
raise RuntimeError, 'No entries present - add one using `add_transaction` or `add_return`' if entries.empty?
raise RuntimeError, 'ABA data is invalid - check the contents of `errors`' unless valid?
# Descriptive record
output = "#{descriptive_record}\r\n"
# Transactions records
output += entries.map { |t| t.to_s }.join("\r\n")
# Batch control record
output += "\r\n#{batch_control_record}"
end
def add_transaction(attrs = {})
add_entry(Aba::Transaction, attrs)
end
def add_return(attrs = {})
add_entry(Aba::Return, attrs)
end
def transactions
entries.select { |entry| entry.instance_of?(Aba::Transaction) }
end
def transactions_valid?
!transactions.map { |t| t.valid? }.include?(false)
end
def valid?
!has_errors? && !has_entry_errors?
end
def errors
# Run validations
has_errors?
has_entry_errors?
# Build errors
all_errors = {}
all_errors[:aba] = self.error_collection unless self.error_collection.empty?
entry_error_collection = entries.each_with_index
.map { |t, i| [i, t.error_collection] }
.reject { |e| e[1].nil? || e[1].empty? }
.to_h
all_errors[:entries] = entry_error_collection unless entry_error_collection.empty?
all_errors unless all_errors.empty?
end
private
def add_entry(type, attrs)
(attrs.instance_of?(type) ? attrs : type.new(attrs)).tap do |entry|
entries << entry
end
end
def has_entry_errors?
entries.map { |t| t.valid? }.include?(false)
end
def descriptive_record
# Record type
# Max: 1
# Char position: 1
output = "0"
# Optional branch number of the funds account with a hyphen in the 4th character position
# Char position: 2-18
# Max: 17
# Blank filled
output += self.bsb.nil? ? " " * 17 : self.bsb.to_s.ljust(17)
# Sequence number
# Char position: 19-20
# Max: 2
# Zero padded
output += "01"
# Name of user financial instituion
# Max: 3
# Char position: 21-23
output += self.financial_institution.to_s
# Reserved
# Max: 7
# Char position: 24-30
output += " " * 7
# Name of User supplying File
# Char position: 31-56
# Max: 26
# Full BECS character set valid
# Blank filled
output += self.user_name.to_s.ljust(26)
# Direct Entry User ID
# Char position: 57-62
# Max: 6
# Zero padded
output += self.user_id.to_s.rjust(6, "0")
# Description of payments in the file (e.g. Payroll, Creditors etc.)
# Char position: 63-74
# Max: 12
# Full BECS character set valid
# Blank filled
output += self.description.to_s.ljust(12)
# Date on which the payment is to be processed
# Char position: 75-80
# Max: 6
output += self.process_at.to_s.rjust(6, "0")
# Reserved
# Max: 40
# Char position: 81-120
output += " " * 40
end
def batch_control_record
cached_credit_total_amount = credit_total_amount
cached_debit_total_amount = debit_total_amount
# Record type
# Max: 1
# Char position: 1
output = "7"
# BSB Format Filler
# Max: 7
# Char position: 2-8
output += "999-999"
# Reserved
# Max: 12
# Char position: 9-20
output += " " * 12
# Net total
# Max: 10
# Char position: 21-30
output += (cached_credit_total_amount - cached_debit_total_amount).abs.to_s.rjust(10, "0")
# Credit Total Amount
# Max: 10
# Char position: 31-40
output += cached_credit_total_amount.to_s.rjust(10, "0")
# Debit Total Amount
# Max: 10
# Char position: 41-50
output += cached_debit_total_amount.to_s.rjust(10, "0")
# Reserved
# Max: 24
# Char position: 51-74
output += " " * 24
# Total Item Count
# Max: 6
# Char position: 75-80
output += entries.size.to_s.rjust(6, "0")
# Reserved
# Max: 40
# Char position: 81-120
output += " " * 40
end
def credit_total_amount
credit_total_amount ||= entries.reject(&:debit?).sum(&method(:entry_amount))
end
def debit_total_amount
entries.select(&:debit?).sum(&method(:entry_amount))
end
def entry_amount(entry)
entry.amount.to_s[/\A\-?\d+\z/] ? Integer(entry.amount).abs : 0
end
end
end