aba 0.0.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|