aba 0.0.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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, :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_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
- output += account_name.ljust(32, " ")
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
- output += lodgement_reference.ljust(18, " ")
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
- output += name_of_remitter.ljust(16, " ")
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
@@ -1,6 +1,9 @@
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']
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
- self.errors = []
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.errors << "#{attribute} is empty" if value.nil? || value.to_s.empty?
34
+ self.error_collection << "#{attribute} is empty" if value.nil? || value.to_s.empty?
24
35
  when :bsb
25
- self.errors << "#{attribute} format is incorrect" unless value =~ /^\d{3}-\d{3}$/
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.errors << "#{attribute} length must not exceed #{param} characters" if value.to_s.length > param
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.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_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
@@ -1,3 +1,3 @@
1
1
  class Aba
2
- VERSION = "0.0.1"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -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