aba 0.1.0 → 1.0.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.
@@ -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
+ Validations::CREDIT_TRANSACTION_CODES.include?(transaction_code.to_i)
11
+ end
12
+
13
+ def debit?
14
+ Validations::DEBIT_TRANSACTION_CODES.include?(transaction_code.to_i)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,71 @@
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_integer :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
+ # Allow dashes to be input, but remove them from output
49
+ def account_number
50
+ @account_number ? @account_number.to_s.delete('-') : nil
51
+ end
52
+
53
+ def to_s
54
+ raise 'Transaction data is invalid - check the contents of `errors`' unless valid?
55
+
56
+ format('2%-7s%9s%1d%2d%010d%-32s%-18s%-7s%9s%-16s%02d%6s',
57
+ bsb,
58
+ account_number,
59
+ return_code,
60
+ transaction_code,
61
+ amount.to_i.abs,
62
+ account_name,
63
+ lodgement_reference,
64
+ trace_bsb,
65
+ trace_account_number,
66
+ name_of_remitter,
67
+ original_processing_day.to_i,
68
+ original_user_id)
69
+ end
70
+ end
71
+ end
@@ -1,31 +1,60 @@
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, :payment_id,
6
- :bsb, :trace_bsb, :trace_account_number, :name_of_remitter, :witholding_amount,
7
- :indicator, :lodgement_reference
8
-
9
- validates_presence_of :bsb, :account_number, :amount, :account_name, :payment_id, :transaction_code,
10
- :lodgement_reference, :trace_bsb, :trace_account_number, :name_of_remitter
11
-
12
- validates_bsb :bsb, :trace_bsb
13
-
14
- validates_max_length :account_number, 9
15
- validates_max_length :indicator, 1
16
- validates_max_length :transaction_code, 2
17
- validates_max_length :account_name, 32
18
- validates_max_length :lodgement_reference, 18
19
- validates_max_length :trace_account_number, 9
20
- validates_max_length :name_of_remitter, 16
21
-
22
- def initialize(attrs = {})
23
- attrs.each do |key, value|
24
- send("#{key}=", value)
25
- end
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_integer :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
+ # Allow dashes to be input, but remove them from output
41
+ def account_number
42
+ @account_number ? @account_number.to_s.gsub('-', '') : nil
43
+ end
44
+
45
+ # Sane default for majority of use cases as per BECS
46
+ def indicator
47
+ @indicator || ' '
26
48
  end
27
-
49
+
50
+ # Optional as per BECS
51
+ def lodgement_reference
52
+ @lodgement_reference || ''
53
+ end
54
+
28
55
  def to_s
56
+ raise RuntimeError, 'Transaction data is invalid - check the contents of `errors`' unless valid?
57
+
29
58
  # Record type
30
59
  output = "1"
31
60
 
@@ -33,44 +62,49 @@ class Aba
33
62
  output += bsb
34
63
 
35
64
  # Account number
65
+ #raise RuntimeError, 'Transaction is missing account_number' unless account_number
36
66
  output += account_number.to_s.rjust(9, " ")
37
67
 
38
68
  # Withholding Tax Indicator
39
69
  # "N" – for new or varied Bank/State/Branch number or name details, otherwise blank filled.
70
+ # "T" - for a drawing under a Transaction Negotiation Authority.
40
71
  # "W" – dividend paid to a resident of a country where a double tax agreement is in force.
41
72
  # "X" – dividend paid to a resident of any other country.
42
73
  # "Y" – interest paid to all non-residents.
43
74
  output += indicator.to_s.ljust(1, " ")
44
75
 
45
76
  # Transaction Code
46
- # 50 General Credit.
47
- # 53 Payroll.
48
- # 54 Pension.
49
- # 56 Dividend.
50
- # 57 Debenture Interest.
77
+ # 50 General Credit.
78
+ # 53 Payroll.
79
+ # 54 Pension.
80
+ # 56 Dividend.
81
+ # 57 Debenture Interest.
51
82
  # 13 General Debit.
52
83
  output += transaction_code.to_s
53
84
 
54
85
  # Amount to be credited or debited
55
- output += amount.abs.to_s.rjust(10, "0")
86
+ output += amount.to_i.abs.to_s.rjust(10, "0")
56
87
 
57
88
  # Title of Account
58
- output += account_name.ljust(32, " ")
89
+ # Full BECS character set valid
90
+ output += account_name.to_s.ljust(32, " ")
59
91
 
60
- # Lodgement Reference Produced on the recipient’s Account Statement.
61
- output += lodgement_reference.ljust(18, " ")
92
+ # Lodgement Reference Produced on the recipient’s Account Statement.
93
+ # Full BECS character set valid
94
+ output += lodgement_reference.to_s.ljust(18, " ")
62
95
 
63
96
  # Trace BSB Number
64
97
  output += trace_bsb
65
98
 
66
- # Trace Account Number
99
+ # Trace Account Number
67
100
  output += trace_account_number.to_s.rjust(9, " ")
68
101
 
69
102
  # Name of Remitter Produced on the recipient’s Account Statement
70
- output += name_of_remitter.ljust(16, " ")
103
+ # Full BECS character set valid
104
+ output += name_of_remitter.to_s.ljust(16, " ")
71
105
 
72
106
  # Withholding amount in cents
73
107
  output += (witholding_amount || 0).abs.to_s.rjust(8, "0")
74
108
  end
75
109
  end
76
- end
110
+ end
@@ -1,6 +1,11 @@
1
1
  class Aba
2
2
  module Validations
3
- attr_accessor :errors
3
+ attr_accessor :error_collection
4
+
5
+ BECS_PATTERN = /\A[\w\+\-\@\ \$\!\%\&\(\)\*\.\/\#\=\:\;\?\,\'\[\]\_\^]*\Z/
6
+ INDICATORS = [' ', 'N', 'T', 'W', 'X', 'Y']
7
+ DEBIT_TRANSACTION_CODES = [13]
8
+ CREDIT_TRANSACTION_CODES = [50, 51, 52, 53, 54, 55, 56, 57]
4
9
 
5
10
  def self.included(base)
6
11
  base.instance_eval do
@@ -10,9 +15,21 @@ class Aba
10
15
  base.send :extend, ClassMethods
11
16
  end
12
17
 
13
- # Run all validations
18
+ def self.transaction_codes
19
+ DEBIT_TRANSACTION_CODES + CREDIT_TRANSACTION_CODES
20
+ end
21
+
14
22
  def valid?
15
- self.errors = []
23
+ !has_errors?
24
+ end
25
+
26
+ alias_method 'errors', 'error_collection'
27
+
28
+ private
29
+
30
+ # Run all validations
31
+ def has_errors?
32
+ self.error_collection = []
16
33
 
17
34
  self.class.instance_variable_get(:@_validations).each do |attribute, validations|
18
35
  value = send(attribute)
@@ -20,35 +37,83 @@ class Aba
20
37
  validations.each do |type, param|
21
38
  case type
22
39
  when :presence
23
- self.errors << "#{attribute} is empty" if value.nil? || value.to_s.empty?
40
+ self.error_collection << "#{attribute} is empty" if value.nil? || value.to_s.empty?
24
41
  when :bsb
25
- self.errors << "#{attribute} format is incorrect" unless value =~ /^\d{3}-\d{3}$/
42
+ unless((param && value.nil?) || value =~ /^\d{3}-\d{3}$/)
43
+ self.error_collection << "#{attribute} format is incorrect"
44
+ end
26
45
  when :max_length
27
- self.errors << "#{attribute} length must not exceed #{param} characters" if value.to_s.length > param
46
+ self.error_collection << "#{attribute} length must not exceed #{param} characters" if value.to_s.length > param
47
+ when :length
48
+ self.error_collection << "#{attribute} length must be exactly #{param} characters" if value.to_s.length != param
49
+ when :integer
50
+ if param
51
+ self.error_collection << "#{attribute} must be a number" unless value.to_s =~ /\A[+-]?\d+\Z/
52
+ else
53
+ self.error_collection << "#{attribute} must be an unsigned number" unless value.to_s =~ /\A\d+\Z/
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 one of #{Validations.transaction_codes.join(', ')}" unless Validations.transaction_codes.include?(value.to_i)
28
66
  end
29
67
  end
30
68
  end
31
69
 
32
- self.errors.empty?
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(*attributes)
43
- attributes.each do |a|
44
- add_validation_attribute(a, :bsb)
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_account_number(attribute)
98
+ add_validation_attribute(attribute, :account_number)
99
+ end
100
+
101
+ def validates_becs(attribute)
102
+ add_validation_attribute(attribute, :becs)
103
+ end
104
+
105
+ def validates_indicator(attribute)
106
+ add_validation_attribute(attribute, :indicator)
107
+ end
108
+
109
+ def validates_transaction_code(attribute)
110
+ add_validation_attribute(attribute, :transaction_code)
111
+ end
112
+
113
+ def validates_return_code(attribute)
114
+ add_validation_attribute(attribute, :integer, :return_code)
115
+ end
116
+
52
117
  private
53
118
 
54
119
  def add_validation_attribute(attribute, type, param = true)
@@ -57,4 +122,4 @@ class Aba
57
122
  end
58
123
  end
59
124
  end
60
- end
125
+ end
@@ -1,3 +1,3 @@
1
1
  class Aba
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,133 @@
1
+ # encoding: UTF-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Aba::Batch do
6
+ subject(:batch) do
7
+ Aba::Batch.new(
8
+ financial_institution: "WPC",
9
+ user_name: "John Doe",
10
+ user_id: "466364",
11
+ description: "Payroll",
12
+ process_at: "190615",
13
+ )
14
+ end
15
+
16
+ let(:transactions_attributes) { [{amount: 30, transaction_code: 50}, {amount: 20, transaction_code: 13}] }
17
+ let(:returns_attributes) { [{amount: 3, transaction_code: 50}, {amount: 2, transaction_code: 13}] }
18
+
19
+ before do
20
+ transactions_attributes.each do |attr|
21
+ transaction = Aba::Transaction.new(
22
+ bsb: '342-342',
23
+ account_number: '3244654',
24
+ amount: attr[:amount],
25
+ account_name: 'John Doe',
26
+ transaction_code: attr[:transaction_code],
27
+ lodgement_reference: 'R435564',
28
+ trace_bsb: '453-543',
29
+ trace_account_number: '45656733',
30
+ name_of_remitter: 'Remitter',
31
+ )
32
+
33
+ batch.add_transaction(transaction)
34
+ end
35
+
36
+ returns_attributes.map do |attr|
37
+ ret = Aba::Return.new(
38
+ bsb: '453-543',
39
+ account_number: '45656733',
40
+ amount: attr[:amount],
41
+ account_name: 'John Doe',
42
+ transaction_code: attr[:transaction_code],
43
+ lodgement_reference: 'R435564',
44
+ trace_bsb: '342-342',
45
+ trace_account_number: '3244654',
46
+ name_of_remitter: 'Remitter',
47
+ return_code: 8,
48
+ original_user_id: 654321,
49
+ original_processing_day: 12,
50
+ )
51
+
52
+ batch.add_return(ret)
53
+ end
54
+ end
55
+
56
+ describe "#to_s" do
57
+ context 'when descriptive record' do
58
+ context 'without bsb' do
59
+ it "should return a string containing the descriptive record without the bsb" do
60
+ expect(batch.to_s).to include("0 01WPC John Doe 466364Payroll 190615 \r\n")
61
+ end
62
+ end
63
+
64
+ context 'with bsb' do
65
+ before { batch.bsb = "123-345" }
66
+
67
+ it "should return a string containing the descriptive record with the bsb" do
68
+ expect(batch.to_s).to include("0123-345 01WPC John Doe 466364Payroll 190615 \r\n")
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+ context 'when detail record' do
75
+ it "should contain transaction & return records" do
76
+ expect(batch.to_s).to include("1342-342 3244654 500000000030John Doe R435564 453-543 45656733Remitter 00000000\r\n")
77
+ expect(batch.to_s).to include("1342-342 3244654 130000000020John Doe R435564 453-543 45656733Remitter 00000000\r\n")
78
+ expect(batch.to_s).to include("2453-543 456567338500000000003John Doe R435564 342-342 3244654Remitter 12654321\r\n")
79
+ expect(batch.to_s).to include("2453-543 456567338130000000002John Doe R435564 342-342 3244654Remitter 12654321\r\n")
80
+ end
81
+ end
82
+
83
+ context 'when file total record' do
84
+ context 'with unbalanced transactions' do
85
+ it "should return a string where the net total is not zero" do
86
+ expect(batch.to_s).to include("7999-999 000000001100000000330000000022 000004 ")
87
+ # | Total || Credit || Debit |
88
+ end
89
+ end
90
+
91
+ context 'with balanced transactions' do
92
+ let(:transactions_attributes) do
93
+ [{amount: 30, transaction_code: 50}, {amount: 30, transaction_code: 13}, {amount: 30, transaction_code: 50}]
94
+ end
95
+ let(:returns_attributes) do
96
+ [{amount: 3, transaction_code: 50}, {amount: 3, transaction_code: 13}, {amount: 30, transaction_code: 13}]
97
+ end
98
+
99
+ it "should return a string where the net total is zero" do
100
+ expect(batch.to_s).to include("7999-999 000000000000000000630000000063 000006 ")
101
+ # | Total || Credit || Debit |
102
+ end
103
+ end
104
+
105
+ context 'with negative values supplied for the amounts' do
106
+ let(:transactions_attributes) { [{amount: -30, transaction_code: 50}, {amount: -20, transaction_code: 13}] }
107
+ let(:returns_attributes) { [{amount: -3, transaction_code: 50}, {amount: 2, transaction_code: 13}] }
108
+
109
+ it 'should return a string where the amount segments rely on the transaction code, not the amount sign' do
110
+ expect(batch.to_s).to include("7999-999 000000001100000000330000000022 000004 ")
111
+ # | Total || Credit || Debit |
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "#errors" do
119
+ it "is empty" do
120
+ expect(batch.errors).to be_nil
121
+ end
122
+
123
+ context "with an invalid amount" do
124
+ let(:transactions_attributes) do
125
+ [{amount: 1, transaction_code: 50}, {amount: 'abc', transaction_code: 13}, {amount: 'def', transaction_code: 50}]
126
+ end
127
+
128
+ it "reports the errors" do
129
+ expect(batch.errors).to eq(:entries => { 1 => ["amount must be a number"], 2 => ["amount must be a number"] })
130
+ end
131
+ end
132
+ end
133
+ end