aba 0.0.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rspec +3 -1
- data/.travis.yml +6 -0
- data/README.md +109 -18
- data/aba.gemspec +7 -6
- data/lib/aba.rb +6 -114
- data/lib/aba/batch.rb +224 -0
- data/lib/aba/entry.rb +17 -0
- data/lib/aba/return.rb +72 -0
- data/lib/aba/transaction.rb +99 -35
- data/lib/aba/validations.rb +84 -14
- data/lib/aba/version.rb +1 -1
- data/spec/lib/aba/batch_spec.rb +101 -0
- data/spec/lib/aba/return_spec.rb +47 -0
- data/spec/{transaction_spec.rb → lib/aba/transaction_spec.rb} +6 -5
- data/spec/lib/aba/validations_spec.rb +215 -0
- data/spec/lib/aba_spec.rb +21 -0
- data/spec/spec_helper.rb +2 -0
- metadata +40 -17
- data/spec/aba_spec.rb +0 -40
- data/spec/validations_spec.rb +0 -47
data/lib/aba/entry.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Aba
|
2
|
+
class Entry
|
3
|
+
def initialize(attrs = {})
|
4
|
+
attrs.each do |key, value|
|
5
|
+
send("#{key}=", value)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def credit?
|
10
|
+
(50..57).include?(transaction_code.to_i)
|
11
|
+
end
|
12
|
+
|
13
|
+
def debit?
|
14
|
+
transaction_code.to_i == 13
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/aba/return.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
class Aba
|
2
|
+
class Return < Entry
|
3
|
+
include Aba::Validations
|
4
|
+
|
5
|
+
attr_accessor :account_number, :transaction_code, :amount, :account_name,
|
6
|
+
:bsb, :trace_bsb, :trace_account_number, :name_of_remitter,
|
7
|
+
:return_code, :lodgement_reference,
|
8
|
+
:original_processing_day, :original_user_id
|
9
|
+
|
10
|
+
# BSB
|
11
|
+
validates_bsb :bsb
|
12
|
+
|
13
|
+
# Account Number
|
14
|
+
validates_account_number :account_number
|
15
|
+
|
16
|
+
# Indicator
|
17
|
+
validates_return_code :return_code
|
18
|
+
|
19
|
+
# Transaction Code
|
20
|
+
validates_transaction_code :transaction_code
|
21
|
+
|
22
|
+
# Amount
|
23
|
+
validates_amount :amount
|
24
|
+
|
25
|
+
# Original Day of Processing
|
26
|
+
validates_integer :original_processing_day, :unsigned
|
27
|
+
|
28
|
+
# Original User Id
|
29
|
+
validates_max_length :original_user_id, 6
|
30
|
+
validates_integer :original_user_id, :unsigned
|
31
|
+
|
32
|
+
# Account Name
|
33
|
+
validates_max_length :account_name, 32
|
34
|
+
validates_becs :account_name
|
35
|
+
|
36
|
+
# Lodgement Reference
|
37
|
+
validates_max_length :lodgement_reference, 18
|
38
|
+
validates_becs :lodgement_reference
|
39
|
+
|
40
|
+
# Trace Record
|
41
|
+
validates_bsb :trace_bsb
|
42
|
+
validates_account_number :trace_account_number
|
43
|
+
|
44
|
+
# Name of Remitter
|
45
|
+
validates_max_length :name_of_remitter, 16
|
46
|
+
validates_becs :name_of_remitter
|
47
|
+
|
48
|
+
|
49
|
+
# Allow dashes to be input, but remove them from output
|
50
|
+
def account_number
|
51
|
+
@account_number ? @account_number.to_s.delete('-') : nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
raise 'Transaction data is invalid - check the contents of `errors`' unless valid?
|
56
|
+
|
57
|
+
format('2%-7s%9s%1d%2d%010d%-32s%-18s%-7s%9s%-16s%02d%6s',
|
58
|
+
bsb,
|
59
|
+
account_number,
|
60
|
+
return_code,
|
61
|
+
transaction_code,
|
62
|
+
amount.to_i.abs,
|
63
|
+
account_name,
|
64
|
+
lodgement_reference,
|
65
|
+
trace_bsb,
|
66
|
+
trace_account_number,
|
67
|
+
name_of_remitter,
|
68
|
+
original_processing_day.to_i,
|
69
|
+
original_user_id)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/aba/transaction.rb
CHANGED
@@ -1,31 +1,90 @@
|
|
1
1
|
class Aba
|
2
|
-
class Transaction
|
2
|
+
class Transaction < Entry
|
3
3
|
include Aba::Validations
|
4
4
|
|
5
|
-
attr_accessor :account_number, :transaction_code, :amount, :account_name,
|
6
|
-
:bsb, :trace_bsb, :trace_account_number, :name_of_remitter,
|
7
|
-
:indicator, :lodgement_reference
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
5
|
+
attr_accessor :account_number, :transaction_code, :amount, :account_name,
|
6
|
+
:bsb, :trace_bsb, :trace_account_number, :name_of_remitter,
|
7
|
+
:witholding_amount, :indicator, :lodgement_reference
|
8
|
+
|
9
|
+
# BSB
|
10
|
+
validates_bsb :bsb
|
11
|
+
|
12
|
+
# Account Number
|
13
|
+
validates_account_number :account_number
|
14
|
+
|
15
|
+
# Indicator
|
16
|
+
validates_indicator :indicator
|
17
|
+
|
18
|
+
# Transaction Code
|
19
|
+
validates_transaction_code :transaction_code
|
20
|
+
|
21
|
+
# Amount
|
22
|
+
validates_amount :amount
|
23
|
+
|
24
|
+
# Account Name
|
25
|
+
validates_max_length :account_name, 32
|
26
|
+
validates_becs :account_name
|
27
|
+
|
28
|
+
# Lodgement Reference
|
29
|
+
validates_max_length :lodgement_reference, 18
|
30
|
+
validates_becs :lodgement_reference
|
31
|
+
|
32
|
+
# Trace Record
|
33
|
+
validates_bsb :trace_bsb
|
34
|
+
validates_account_number :trace_account_number
|
35
|
+
|
36
|
+
# Name of Remitter
|
37
|
+
validates_max_length :name_of_remitter, 16
|
38
|
+
validates_becs :name_of_remitter
|
39
|
+
|
40
|
+
|
41
|
+
# Allow dashes to be input, but remove them from output
|
42
|
+
def account_number
|
43
|
+
@account_number ? @account_number.to_s.gsub('-', '') : nil
|
44
|
+
end
|
45
|
+
|
46
|
+
# Fall back to blank string
|
47
|
+
def indicator
|
48
|
+
@indicator || Aba::Validations::INDICATORS.first
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fall back to 50
|
52
|
+
def transaction_code
|
53
|
+
@transaction_code || 50
|
26
54
|
end
|
27
|
-
|
55
|
+
|
56
|
+
# Fall back to 0
|
57
|
+
def amount
|
58
|
+
@amount || 0
|
59
|
+
end
|
60
|
+
|
61
|
+
# Fall back to empty string
|
62
|
+
def account_name
|
63
|
+
@account_name || ''
|
64
|
+
end
|
65
|
+
|
66
|
+
# Fall back to empty string
|
67
|
+
def lodgement_reference
|
68
|
+
@lodgement_reference || ''
|
69
|
+
end
|
70
|
+
|
71
|
+
# Fall back to BSB
|
72
|
+
def trace_bsb
|
73
|
+
@trace_bsb || bsb
|
74
|
+
end
|
75
|
+
|
76
|
+
# Fall back to Account Number
|
77
|
+
def trace_account_number
|
78
|
+
@trace_account_number ? @trace_account_number.to_s.gsub('-', '') : account_number
|
79
|
+
end
|
80
|
+
|
81
|
+
def name_of_remitter
|
82
|
+
@name_of_remitter || ''
|
83
|
+
end
|
84
|
+
|
28
85
|
def to_s
|
86
|
+
raise RuntimeError, 'Transaction data is invalid - check the contents of `errors`' unless valid?
|
87
|
+
|
29
88
|
# Record type
|
30
89
|
output = "1"
|
31
90
|
|
@@ -33,44 +92,49 @@ class Aba
|
|
33
92
|
output += bsb
|
34
93
|
|
35
94
|
# Account number
|
95
|
+
#raise RuntimeError, 'Transaction is missing account_number' unless account_number
|
36
96
|
output += account_number.to_s.rjust(9, " ")
|
37
97
|
|
38
98
|
# Withholding Tax Indicator
|
39
99
|
# "N" – for new or varied Bank/State/Branch number or name details, otherwise blank filled.
|
100
|
+
# "T" - for a drawing under a Transaction Negotiation Authority.
|
40
101
|
# "W" – dividend paid to a resident of a country where a double tax agreement is in force.
|
41
102
|
# "X" – dividend paid to a resident of any other country.
|
42
103
|
# "Y" – interest paid to all non-residents.
|
43
104
|
output += indicator.to_s.ljust(1, " ")
|
44
105
|
|
45
106
|
# Transaction Code
|
46
|
-
# 50 General Credit.
|
47
|
-
# 53 Payroll.
|
48
|
-
# 54 Pension.
|
49
|
-
# 56 Dividend.
|
50
|
-
# 57 Debenture Interest.
|
107
|
+
# 50 General Credit.
|
108
|
+
# 53 Payroll.
|
109
|
+
# 54 Pension.
|
110
|
+
# 56 Dividend.
|
111
|
+
# 57 Debenture Interest.
|
51
112
|
# 13 General Debit.
|
52
113
|
output += transaction_code.to_s
|
53
114
|
|
54
115
|
# Amount to be credited or debited
|
55
|
-
output += amount.abs.to_s.rjust(10, "0")
|
116
|
+
output += amount.to_i.abs.to_s.rjust(10, "0")
|
56
117
|
|
57
118
|
# Title of Account
|
58
|
-
|
119
|
+
# Full BECS character set valid
|
120
|
+
output += account_name.to_s.ljust(32, " ")
|
59
121
|
|
60
|
-
# Lodgement Reference Produced on the recipient’s Account Statement.
|
61
|
-
|
122
|
+
# Lodgement Reference Produced on the recipient’s Account Statement.
|
123
|
+
# Full BECS character set valid
|
124
|
+
output += lodgement_reference.to_s.ljust(18, " ")
|
62
125
|
|
63
126
|
# Trace BSB Number
|
64
127
|
output += trace_bsb
|
65
128
|
|
66
|
-
# Trace Account Number
|
129
|
+
# Trace Account Number
|
67
130
|
output += trace_account_number.to_s.rjust(9, " ")
|
68
131
|
|
69
132
|
# Name of Remitter Produced on the recipient’s Account Statement
|
70
|
-
|
133
|
+
# Full BECS character set valid
|
134
|
+
output += name_of_remitter.to_s.ljust(16, " ")
|
71
135
|
|
72
136
|
# Withholding amount in cents
|
73
137
|
output += (witholding_amount || 0).abs.to_s.rjust(8, "0")
|
74
138
|
end
|
75
139
|
end
|
76
|
-
end
|
140
|
+
end
|
data/lib/aba/validations.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
class Aba
|
2
2
|
module Validations
|
3
|
-
attr_accessor :
|
3
|
+
attr_accessor :error_collection
|
4
|
+
|
5
|
+
BECS_PATTERN = /\A[\w\+\-\@\ \$\!\%\&\(\)\*\.\/\#\=\:\;\?\,\'\[\]\_\^]*\Z/
|
6
|
+
INDICATORS = [' ', 'N', 'T', 'W', 'X', 'Y']
|
4
7
|
|
5
8
|
def self.included(base)
|
6
9
|
base.instance_eval do
|
@@ -10,9 +13,17 @@ class Aba
|
|
10
13
|
base.send :extend, ClassMethods
|
11
14
|
end
|
12
15
|
|
13
|
-
# Run all validations
|
14
16
|
def valid?
|
15
|
-
|
17
|
+
!has_errors?
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method 'errors', 'error_collection'
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Run all validations
|
25
|
+
def has_errors?
|
26
|
+
self.error_collection = []
|
16
27
|
|
17
28
|
self.class.instance_variable_get(:@_validations).each do |attribute, validations|
|
18
29
|
value = send(attribute)
|
@@ -20,35 +31,94 @@ class Aba
|
|
20
31
|
validations.each do |type, param|
|
21
32
|
case type
|
22
33
|
when :presence
|
23
|
-
self.
|
34
|
+
self.error_collection << "#{attribute} is empty" if value.nil? || value.to_s.empty?
|
24
35
|
when :bsb
|
25
|
-
|
36
|
+
unless((param && value.nil?) || value =~ /^\d{3}-\d{3}$/)
|
37
|
+
self.error_collection << "#{attribute} format is incorrect"
|
38
|
+
end
|
26
39
|
when :max_length
|
27
|
-
self.
|
40
|
+
self.error_collection << "#{attribute} length must not exceed #{param} characters" if value.to_s.length > param
|
41
|
+
when :length
|
42
|
+
self.error_collection << "#{attribute} length must be exactly #{param} characters" if value.to_s.length != param
|
43
|
+
when :integer
|
44
|
+
if param
|
45
|
+
self.error_collection << "#{attribute} must be a number" unless value.to_s =~ /\A[+-]?\d+\Z/
|
46
|
+
else
|
47
|
+
self.error_collection << "#{attribute} must be an unsigned number" unless value.to_s =~ /\A\d+\Z/
|
48
|
+
end
|
49
|
+
when :matches_transaction_code
|
50
|
+
if debit? && value.to_i > 0
|
51
|
+
self.error_collection << "#{attribute} is positive but the transaction type is a debit"
|
52
|
+
elsif credit? && value.to_i < 0
|
53
|
+
self.error_collection << "#{attribute} is negative but the transaction type is a credit"
|
54
|
+
end
|
55
|
+
when :account_number
|
56
|
+
if value.to_s =~ /\A[0\ ]+\Z/ || value.to_s !~ /\A[a-z\d\ ]{1,9}\Z/
|
57
|
+
self.error_collection << "#{attribute} must be a valid account number"
|
58
|
+
end
|
59
|
+
when :becs
|
60
|
+
self.error_collection << "#{attribute} must not contain invalid characters" unless value.to_s =~ BECS_PATTERN
|
61
|
+
when :indicator
|
62
|
+
list = INDICATORS.join('\', \'')
|
63
|
+
self.error_collection << "#{attribute} must be a one of '#{list}'" unless INDICATORS.include?(value.to_s)
|
64
|
+
when :transaction_code
|
65
|
+
self.error_collection << "#{attribute} must be a 2 digit number" unless value.to_s =~ /\A\d{2,2}\Z/
|
28
66
|
end
|
29
67
|
end
|
30
68
|
end
|
31
69
|
|
32
|
-
self.
|
70
|
+
!self.error_collection.empty?
|
33
71
|
end
|
34
|
-
|
72
|
+
|
35
73
|
module ClassMethods
|
36
74
|
def validates_presence_of(*attributes)
|
37
|
-
attributes.each do |a|
|
75
|
+
attributes.each do |a|
|
38
76
|
add_validation_attribute(a, :presence)
|
39
77
|
end
|
40
78
|
end
|
41
79
|
|
42
|
-
def validates_bsb(
|
43
|
-
|
44
|
-
|
45
|
-
end
|
80
|
+
def validates_bsb(attribute, options = {})
|
81
|
+
options[:allow_blank] ||= false
|
82
|
+
add_validation_attribute(attribute, :bsb, options[:allow_blank])
|
46
83
|
end
|
47
84
|
|
48
85
|
def validates_max_length(attribute, length)
|
49
86
|
add_validation_attribute(attribute, :max_length, length)
|
50
87
|
end
|
51
88
|
|
89
|
+
def validates_length(attribute, length)
|
90
|
+
add_validation_attribute(attribute, :length, length)
|
91
|
+
end
|
92
|
+
|
93
|
+
def validates_integer(attribute, signed = true)
|
94
|
+
add_validation_attribute(attribute, :integer, signed)
|
95
|
+
end
|
96
|
+
|
97
|
+
def validates_amount(attribute)
|
98
|
+
add_validation_attribute(attribute, :integer, true)
|
99
|
+
add_validation_attribute(attribute, :matches_transaction_code)
|
100
|
+
end
|
101
|
+
|
102
|
+
def validates_account_number(attribute)
|
103
|
+
add_validation_attribute(attribute, :account_number)
|
104
|
+
end
|
105
|
+
|
106
|
+
def validates_becs(attribute)
|
107
|
+
add_validation_attribute(attribute, :becs)
|
108
|
+
end
|
109
|
+
|
110
|
+
def validates_indicator(attribute)
|
111
|
+
add_validation_attribute(attribute, :indicator)
|
112
|
+
end
|
113
|
+
|
114
|
+
def validates_transaction_code(attribute)
|
115
|
+
add_validation_attribute(attribute, :transaction_code)
|
116
|
+
end
|
117
|
+
|
118
|
+
def validates_return_code(attribute)
|
119
|
+
add_validation_attribute(attribute, :integer, :return_code)
|
120
|
+
end
|
121
|
+
|
52
122
|
private
|
53
123
|
|
54
124
|
def add_validation_attribute(attribute, type, param = true)
|
@@ -57,4 +127,4 @@ class Aba
|
|
57
127
|
end
|
58
128
|
end
|
59
129
|
end
|
60
|
-
end
|
130
|
+
end
|
data/lib/aba/version.rb
CHANGED
@@ -0,0 +1,101 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Aba::Batch do
|
6
|
+
subject(:batch) { Aba::Batch.new(financial_institution: "WPC", user_name: "John Doe",
|
7
|
+
user_id: "466364", description: "Payroll", process_at: "190615") }
|
8
|
+
let(:transaction_amounts) { [[53, 30], [13, -20]] }
|
9
|
+
let(:transactions) do
|
10
|
+
transaction_amounts.map do |(transaction_code, amount)|
|
11
|
+
Aba::Transaction.new(bsb: '342-342', account_number: '3244654', amount: amount,
|
12
|
+
account_name: 'John Doe', transaction_code: transaction_code,
|
13
|
+
lodgement_reference: 'R435564', trace_bsb: '453-543',
|
14
|
+
trace_account_number: '45656733', name_of_remitter: 'Remitter')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
before { transactions.each { |trx| batch.add_transaction(trx) } }
|
18
|
+
|
19
|
+
let(:return_amounts) { [[53, 3], [13, -2]] }
|
20
|
+
let(:returns) do
|
21
|
+
return_amounts.map do |(transaction_code, amount)|
|
22
|
+
Aba::Return.new(bsb: '453-543', account_number: '45656733', amount: amount,
|
23
|
+
account_name: 'John Doe', transaction_code: transaction_code,
|
24
|
+
lodgement_reference: 'R435564', trace_bsb: '342-342',
|
25
|
+
trace_account_number: '3244654', name_of_remitter: 'Remitter',
|
26
|
+
return_code: 8, original_user_id: 654321,
|
27
|
+
original_processing_day: 12,
|
28
|
+
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
before { returns.each { |trx| batch.add_return(trx) } }
|
33
|
+
|
34
|
+
describe "#to_s" do
|
35
|
+
|
36
|
+
context 'when descriptive record' do
|
37
|
+
|
38
|
+
context 'without bsb' do
|
39
|
+
it "should return a string containing the descriptive record without the bsb" do
|
40
|
+
expect(batch.to_s).to include("0 01WPC John Doe 466364Payroll 190615 \r\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with bsb' do
|
45
|
+
before { batch.bsb = "123-345" }
|
46
|
+
it "should return a string containing the descriptive record with the bsb" do
|
47
|
+
expect(batch.to_s).to include("0123-345 01WPC John Doe 466364Payroll 190615 \r\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
context 'when detail record' do
|
54
|
+
|
55
|
+
it "should contain transaction & return records" do
|
56
|
+
expect(batch.to_s).to include("1342-342 3244654 530000000030John Doe R435564 453-543 45656733Remitter 00000000\r\n")
|
57
|
+
expect(batch.to_s).to include("1342-342 3244654 130000000020John Doe R435564 453-543 45656733Remitter 00000000\r\n")
|
58
|
+
expect(batch.to_s).to include("2453-543 456567338530000000003John Doe R435564 342-342 3244654Remitter 12654321\r\n")
|
59
|
+
expect(batch.to_s).to include("2453-543 456567338130000000002John Doe R435564 342-342 3244654Remitter 12654321\r\n")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when file total record' do
|
64
|
+
|
65
|
+
context 'with unbalanced transactions' do
|
66
|
+
it "should return a string where the net total is not zero" do
|
67
|
+
expect(batch.to_s).to include("7999-999 000000001100000000330000000022 000004 ")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with balanced transactions' do
|
72
|
+
let(:transaction_amounts) { [[50, 30], [50, 30], [13, -60]] }
|
73
|
+
let(:return_amounts) { [[50, 3], [50, 3], [13, -6]] }
|
74
|
+
it "should return a string where the net total is zero" do
|
75
|
+
expect(batch.to_s).to include("7999-999 000000000000000000660000000066 000006 ")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#errors" do
|
82
|
+
|
83
|
+
let(:errors) { subject.errors }
|
84
|
+
|
85
|
+
it "is empty" do
|
86
|
+
expect(errors).to be_nil
|
87
|
+
end
|
88
|
+
|
89
|
+
context "with an invalid amount" do
|
90
|
+
let(:transaction_amounts) do
|
91
|
+
[[53, 1], [53, -1], [13, 'def']]
|
92
|
+
end
|
93
|
+
it "reports the errors" do
|
94
|
+
expect(errors).to eq(:entries => {
|
95
|
+
1 => ["amount is negative but the transaction type is a credit"],
|
96
|
+
2 => ["amount must be a number"]
|
97
|
+
})
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|