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.
@@ -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