aba 0.3.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7db46f198fd52a43b83a85c383db00bb9c1f6c0a
4
- data.tar.gz: cfbf7860f3f136ee259c494fe88b0015c3a9d233
2
+ SHA256:
3
+ metadata.gz: 2156c80e41e17a9df07891a515adff66f0b41412dd83897067a5faa1a598e956
4
+ data.tar.gz: 853deb00316a156169fd1dbc81b5cea3b666122ff538fc61646fedb5bf44d15a
5
5
  SHA512:
6
- metadata.gz: 3166c7bbe642707defa8e890f915de79813e07a264d07a77c2c5a5053fce053724a2956c3627b4d0e484ea9f52613c11cacf1a5f1f3161936ef10e9790398a7a
7
- data.tar.gz: 4cfdf45552802bcd767524524602eabcf816695b39fe1f3ade9953c2e43fdb054934adaeffa028a6dd35d546545932629d44c956bd5d469cd07b74c2841bc7e8
6
+ metadata.gz: ccb0516b85f0b70af8374f8ef4a3f3effe157dcd19296283a2c274359926e567a9bf7120aad3cf784b4ae027f8bf9e4835edc3c02805d10ebca50d6feec4639e
7
+ data.tar.gz: fbce1803cff67a9bbff99f6c91720b05e20fc62837e39f6d170438b3f6e3d47972b2f24dcf969788487b65078b0822570f488541091847ae8238d8e9eb7542fd
data/.gitignore CHANGED
@@ -20,3 +20,4 @@ tmp
20
20
  *.o
21
21
  *.a
22
22
  mkmf.log
23
+ .ruby-version
data/.rspec CHANGED
@@ -1 +1,3 @@
1
- --color
1
+ --color
2
+ --require spec_helper
3
+ --format d
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.5.8
4
+ - 2.6.6
5
+ - 2.7.1
6
+ script: bundle exec rspec spec
@@ -0,0 +1,20 @@
1
+ ## V1.0.1, 11 January 2021
2
+
3
+ ### BUG FIX
4
+
5
+ Account numbers/alphas validation should be case insensitive
6
+
7
+ ## v1.0.0, 12 July 2020
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * Positive (`n`) and negative (`-n`) values are now treated the same.
12
+ e.g `5` and `-5` are both processed as `5`, without any signage.
13
+ To differentiate between a debit and credit, use the correct [Transaction Code](https://github.com/andrba/aba/blob/58446f5b0ef822e9792e9399b4af647319b13515/lib/aba/transaction.rb#L106-L112)
14
+ * Removed default values for transactions records to avoid generation of potentially incorrect records. Safety first!
15
+ * Minimum Ruby version is now 2.5
16
+
17
+ ### NEW FEATURE
18
+
19
+ * You can now add a *return* record to be used when you'd like to return
20
+ a credit or a debit to another financial institution (OFI).
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [ ![Codeship Status for krakendevelopments/aba](https://codeship.com/projects/982382f0-f22b-0132-a80d-0acc257ded9c/status?branch=master)](https://codeship.com/projects/85083)
1
+ [![Build Status](https://travis-ci.org/andrba/aba.svg?branch=master)](https://travis-ci.org/andrba/aba) [![Code Climate](https://codeclimate.com/github/andrba/aba/badges/gpa.svg)](https://codeclimate.com/github/andrba/aba)
2
2
 
3
3
  # Aba
4
4
 
@@ -10,29 +10,49 @@ Generates ABA (Australian Banking Association) file format output
10
10
  require 'aba'
11
11
 
12
12
  # Initialise ABA
13
- aba = Aba.new(
13
+ aba = Aba.batch(
14
14
  bsb: "123-345", # Optional (Not required by NAB)
15
- financial_institution: "WPC",
15
+ financial_institution: "WPC",
16
16
  user_name: "John Doe",
17
- user_id: "466364",
18
- description: "Payroll",
17
+ user_id: "466364",
18
+ description: "Payroll",
19
19
  process_at: Time.now.strftime("%d%m%y")
20
20
  )
21
21
 
22
- # Add transactions
22
+ # Add transactions...
23
23
  10.times do
24
24
  aba.add_transaction(
25
- Aba::Transaction.new(
26
- bsb: "342-342",
27
- account_number: "3244654",
25
+ {
26
+ bsb: "342-342",
27
+ account_number: "3244654",
28
28
  amount: 10000, # Amount in cents
29
- account_name: "John Doe",
29
+ account_name: "John Doe",
30
30
  transaction_code: 53,
31
- lodgement_reference: "R435564",
32
- trace_bsb: "453-543",
33
- trace_account_number: "45656733",
31
+ lodgement_reference: "R435564",
32
+ trace_bsb: "453-543",
33
+ trace_account_number: "45656733",
34
34
  name_of_remitter: "Remitter"
35
- )
35
+ }
36
+ )
37
+ end
38
+
39
+ # ...or add returns
40
+ 10.times do
41
+ aba.add_return(
42
+ {
43
+ bsb: '453-543',
44
+ account_number: '45656733',
45
+ amount: 10000,
46
+ account_name: 'John Doe',
47
+ transaction_code: 53,
48
+ lodgement_reference: 'R435564',
49
+ trace_bsb: '342-342',
50
+ trace_account_number: '3244654',
51
+ name_of_remitter: 'Remitter',
52
+ return_code: 8,
53
+ original_user_id: 654321,
54
+ original_processing_day: 12,
55
+ }
36
56
  )
37
57
  end
38
58
 
@@ -40,6 +60,67 @@ puts aba.to_s # View output
40
60
  File.write("/Users/me/dd_#{Time.now.to_i}.aba", aba.to_s) # or write output to file
41
61
  ```
42
62
 
63
+ There are a few ways to create a complete set of ABA data:
64
+
65
+ ```ruby
66
+ # Transactions added to the defined ABA object variable
67
+ aba = Aba.batch financial_institution: 'ANZ', user_name: 'Joe Blow', user_id: 123456, process_at: 200615
68
+ aba.add_transaction bsb: '123-456', account_number: '000-123-456', amount: 50000, transaction_code: 50
69
+ aba.add_transaction bsb: '456-789', account_number: '123-456-789', amount: 10000, transaction_code: 13
70
+
71
+ # Transactions passed individually inside a block
72
+ aba = Aba.batch financial_institution: 'ANZ', user_name: 'Joe Blow', user_id: 123456, process_at: 200615 do |a|
73
+ a.add_transaction bsb: '123-456', account_number: '000-123-456', amount: 50000, transaction_code: 50
74
+ a.add_transaction bsb: '456-789', account_number: '123-456-789', amount: 10000, transaction_code: 13
75
+ end
76
+
77
+ # Transactions as an array passed to the second param of Aba.batch
78
+ aba = Aba.batch(
79
+ { financial_institution: 'ANZ', user_name: 'Joe Blow', user_id: 123456, process_at: 200615 },
80
+ [
81
+ { bsb: '123-456', account_number: '000-123-456', amount: 50000, transaction_code: 50 },
82
+ { bsb: '456-789', account_number: '123-456-789', amount: 10000, transaction_code: 13 }
83
+ ]
84
+ )
85
+ ```
86
+
87
+ > **Note:** Positive (`n`) and negative (`-n`) values are now treated the same.
88
+ > e.g `5` and `-5` are both processed as `5`, without any signage.
89
+ > To differentiate between a debit and credit, use the correct [Transaction Code](https://github.com/andrba/aba/blob/58446f5b0ef822e9792e9399b4af647319b13515/lib/aba/transaction.rb#L106-L112)
90
+
91
+ Validation errors can be caught in several ways:
92
+
93
+ ```ruby
94
+ # Create an ABA object with invalid character in the user_name
95
+ aba = Aba.batch(
96
+ financial_institution: "ANZ",
97
+ user_name: "Jøhn Doe",
98
+ user_id: "123456",
99
+ process_at: Time.now.strftime("%d%m%y")
100
+ )
101
+
102
+ # Add a transaction with a bad BSB
103
+ aba.add_transaction(
104
+ bsb: "abc-123",
105
+ account_number: "000123456"
106
+ )
107
+
108
+ # Is the data valid?
109
+ aba.valid?
110
+ # Returns: false
111
+
112
+ # Return a structured array of errors
113
+ puts aba.errors
114
+ # Returns:
115
+ # {:aba => ["user_name must not contain invalid characters"],
116
+ # :transactions =>
117
+ # {0 => ["bsb format is incorrect", "trace_bsb format is incorrect"]}}
118
+ ```
119
+
120
+ Validation errors will stop parsing of the data to an ABA formatted string using
121
+ `to_s`. `aba.to_s` will raise a `RuntimeError` instead of returning output.
122
+
123
+
43
124
  ## Installation
44
125
 
45
126
  Add this line to your application's Gemfile:
@@ -18,10 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.6"
22
- spec.add_development_dependency "rake", "~> 10.4"
23
- spec.add_development_dependency "pry", "~> 0.10"
21
+ spec.add_development_dependency "rake", "~> 13.0"
22
+ spec.add_development_dependency "pry", "~> 0.13"
24
23
  spec.add_development_dependency "rspec", "~> 3.0"
25
24
 
26
- spec.required_ruby_version = '>= 1.9.2'
25
+ spec.required_ruby_version = '>= 2.5.0'
27
26
  end
data/lib/aba.rb CHANGED
@@ -1,159 +1,12 @@
1
1
  require "aba/version"
2
2
  require "aba/validations"
3
+ require "aba/entry"
4
+ require "aba/batch"
5
+ require "aba/return"
3
6
  require "aba/transaction"
4
7
 
5
8
  class Aba
6
- include Aba::Validations
7
-
8
- attr_accessor :bsb, :financial_institution, :user_name, :user_id, :description, :process_at
9
-
10
- validates_bsb :bsb, allow_blank: true
11
-
12
- validates_max_length :user_name, 26
13
- validates_max_length :user_id, 6
14
- validates_max_length :description, 12
15
-
16
- validates_length :financial_institution, 3
17
- validates_length :process_at, 6
18
-
19
- def initialize(attrs = {})
20
- attrs.each do |key, value|
21
- send("#{key}=", value)
22
- end
23
-
24
- @transactions = []
25
-
26
- yield self if block_given?
27
- end
28
-
29
- def to_s
30
- # Descriptive record
31
- output = "#{descriptive_record}\r\n"
32
-
33
- # Transactions records
34
- output += @transactions.map{ |t| t.to_s }.join("\r\n")
35
-
36
- # Batch control record
37
- output += "\r\n#{batch_control_record}"
38
- end
39
-
40
- def add_transaction(transaction)
41
- @transactions << transaction
42
- end
43
-
44
- private
45
-
46
- def descriptive_record
47
- # Record type
48
- # Max: 1
49
- # Char position: 1
50
- output = "0"
51
-
52
- # Optional branch number of the funds account with a hyphen in the 4th character position
53
- # Char position: 2-18
54
- # Max: 17
55
- # Blank filled
56
- output += self.bsb.nil? ? " " * 17 : self.bsb.to_s.ljust(17)
57
-
58
- # Sequence number
59
- # Char position: 19-20
60
- # Max: 2
61
- # Zero padded
62
- output += "01"
63
-
64
- # Name of user financial instituion
65
- # Max: 3
66
- # Char position: 21-23
67
- output += self.financial_institution.to_s
68
-
69
- # Reserved
70
- # Max: 7
71
- # Char position: 24-30
72
- output += " " * 7
73
-
74
- # Name of User supplying File
75
- # Char position: 31-56
76
- # Max: 26
77
- # Blank filled
78
- output += self.user_name.to_s.ljust(26)
79
-
80
- # Direct Entry User ID
81
- # Char position: 57-62
82
- # Max: 6
83
- # Zero padded
84
- output += self.user_id.to_s.rjust(6, "0")
85
-
86
- # Description of payments in the file (e.g. Payroll, Creditors etc.)
87
- # Char position: 63-74
88
- # Max: 12
89
- # Blank filled
90
- output += self.description.to_s.ljust(12)
91
-
92
- # Date on which the payment is to be processed
93
- # Char position: 75-80
94
- # Max: 6
95
- output += self.process_at.rjust(6, "0")
96
-
97
- # Reserved
98
- # Max: 40
99
- # Char position: 81-120
100
- output += " " * 40
101
- end
102
-
103
- def batch_control_record
104
- net_total_amount = 0
105
- credit_total_amount = 0
106
- debit_total_amount = 0
107
-
108
- @transactions.each do |t|
109
- net_total_amount += t.amount
110
- credit_total_amount += t.amount if t.amount > 0
111
- debit_total_amount += t.amount if t.amount < 0
112
- end
113
-
114
- # Record type
115
- # Max: 1
116
- # Char position: 1
117
- output = "7"
118
-
119
- # BSB Format Filler
120
- # Max: 7
121
- # Char position: 2-8
122
- output += "999-999"
123
-
124
- # Reserved
125
- # Max: 12
126
- # Char position: 9-20
127
- output += " " * 12
128
-
129
- # Net total
130
- # Max: 10
131
- # Char position: 21-30
132
- output += net_total_amount.abs.to_s.rjust(10, "0")
133
-
134
- # Credit Total Amount
135
- # Max: 10
136
- # Char position: 31-40
137
- output += credit_total_amount.abs.to_s.rjust(10, "0")
138
-
139
- # Debit Total Amount
140
- # Max: 10
141
- # Char position: 41-50
142
- output += debit_total_amount.abs.to_s.rjust(10, "0")
143
-
144
- # Reserved
145
- # Max: 24
146
- # Char position: 51-74
147
- output += " " * 24
148
-
149
- # Total Item Count
150
- # Max: 6
151
- # Char position: 75-80
152
- output += @transactions.size.to_s.rjust(6, "0")
153
-
154
- # Reserved
155
- # Max: 40
156
- # Char position: 81-120
157
- output += " " * 40
9
+ def self.batch(attrs = {}, transactions = [])
10
+ Aba::Batch.new(attrs, transactions)
158
11
  end
159
12
  end
@@ -0,0 +1,225 @@
1
+ class Aba
2
+ class Batch
3
+ include Aba::Validations
4
+
5
+ attr_accessor :bsb, :financial_institution, :user_name, :user_id, :description, :process_at, :entries
6
+
7
+ # BSB
8
+ validates_bsb :bsb, allow_blank: true
9
+
10
+ # Financial Institution
11
+ validates_length :financial_institution, 3
12
+
13
+ # User Name
14
+ validates_presence_of :user_name
15
+ validates_max_length :user_name, 26
16
+ validates_becs :user_name
17
+
18
+ # User ID
19
+ validates_presence_of :user_id
20
+ validates_max_length :user_id, 6
21
+ validates_integer :user_id, false
22
+
23
+ # Description
24
+ validates_max_length :description, 12
25
+ validates_becs :description
26
+
27
+ # Process at Date
28
+ validates_length :process_at, 6
29
+ validates_integer :process_at, false
30
+
31
+
32
+ def initialize(attrs = {}, transactions = [])
33
+ attrs.each do |key, value|
34
+ send("#{key}=", value)
35
+ end
36
+
37
+ @entries = []
38
+
39
+ unless transactions.nil? || transactions.empty?
40
+ transactions.to_a.each do |t|
41
+ self.add_transaction(t) unless t.nil? || t.empty?
42
+ end
43
+ end
44
+
45
+ yield self if block_given?
46
+ end
47
+
48
+ def to_s
49
+ raise RuntimeError, 'No entries present - add one using `add_transaction` or `add_return`' if entries.empty?
50
+ raise RuntimeError, 'ABA data is invalid - check the contents of `errors`' unless valid?
51
+
52
+ # Descriptive record
53
+ output = "#{descriptive_record}\r\n"
54
+
55
+ # Transactions records
56
+ output += entries.map { |t| t.to_s }.join("\r\n")
57
+
58
+ # Batch control record
59
+ output += "\r\n#{batch_control_record}"
60
+ end
61
+
62
+ def add_transaction(attrs = {})
63
+ add_entry(Aba::Transaction, attrs)
64
+ end
65
+
66
+ def add_return(attrs = {})
67
+ add_entry(Aba::Return, attrs)
68
+ end
69
+
70
+ def transactions
71
+ entries.select { |entry| entry.instance_of?(Aba::Transaction) }
72
+ end
73
+
74
+ def transactions_valid?
75
+ !transactions.map { |t| t.valid? }.include?(false)
76
+ end
77
+
78
+ def valid?
79
+ !has_errors? && !has_entry_errors?
80
+ end
81
+
82
+ def errors
83
+ # Run validations
84
+ has_errors?
85
+ has_entry_errors?
86
+
87
+ # Build errors
88
+ all_errors = {}
89
+ all_errors[:aba] = self.error_collection unless self.error_collection.empty?
90
+ entry_error_collection = entries.each_with_index.map { |t, i| [i, t.error_collection] }.reject { |e| e[1].nil? || e[1].empty? }.to_h
91
+ all_errors[:entries] = entry_error_collection unless entry_error_collection.empty?
92
+
93
+ all_errors unless all_errors.empty?
94
+ end
95
+
96
+ private
97
+
98
+ def add_entry(type, attrs)
99
+ (attrs.instance_of?(type) ? attrs : type.new(attrs)).tap do |entry|
100
+ entries << entry
101
+ end
102
+ end
103
+
104
+ def has_entry_errors?
105
+ entries.map { |t| t.valid? }.include?(false)
106
+ end
107
+
108
+ def descriptive_record
109
+ # Record type
110
+ # Max: 1
111
+ # Char position: 1
112
+ output = "0"
113
+
114
+ # Optional branch number of the funds account with a hyphen in the 4th character position
115
+ # Char position: 2-18
116
+ # Max: 17
117
+ # Blank filled
118
+ output += self.bsb.nil? ? " " * 17 : self.bsb.to_s.ljust(17)
119
+
120
+ # Sequence number
121
+ # Char position: 19-20
122
+ # Max: 2
123
+ # Zero padded
124
+ output += "01"
125
+
126
+ # Name of user financial instituion
127
+ # Max: 3
128
+ # Char position: 21-23
129
+ output += self.financial_institution.to_s
130
+
131
+ # Reserved
132
+ # Max: 7
133
+ # Char position: 24-30
134
+ output += " " * 7
135
+
136
+ # Name of User supplying File
137
+ # Char position: 31-56
138
+ # Max: 26
139
+ # Full BECS character set valid
140
+ # Blank filled
141
+ output += self.user_name.to_s.ljust(26)
142
+
143
+ # Direct Entry User ID
144
+ # Char position: 57-62
145
+ # Max: 6
146
+ # Zero padded
147
+ output += self.user_id.to_s.rjust(6, "0")
148
+
149
+ # Description of payments in the file (e.g. Payroll, Creditors etc.)
150
+ # Char position: 63-74
151
+ # Max: 12
152
+ # Full BECS character set valid
153
+ # Blank filled
154
+ output += self.description.to_s.ljust(12)
155
+
156
+ # Date on which the payment is to be processed
157
+ # Char position: 75-80
158
+ # Max: 6
159
+ output += self.process_at.to_s.rjust(6, "0")
160
+
161
+ # Reserved
162
+ # Max: 40
163
+ # Char position: 81-120
164
+ output += " " * 40
165
+ end
166
+
167
+ def batch_control_record
168
+ credit_total_amount = 0
169
+ debit_total_amount = 0
170
+
171
+ entries.each do |entry|
172
+ if entry.debit?
173
+ debit_total_amount += Integer(entry.amount).abs
174
+ else
175
+ credit_total_amount += Integer(entry.amount).abs
176
+ end
177
+ end
178
+
179
+ # Record type
180
+ # Max: 1
181
+ # Char position: 1
182
+ output = "7"
183
+
184
+ # BSB Format Filler
185
+ # Max: 7
186
+ # Char position: 2-8
187
+ output += "999-999"
188
+
189
+ # Reserved
190
+ # Max: 12
191
+ # Char position: 9-20
192
+ output += " " * 12
193
+
194
+ # Net total
195
+ # Max: 10
196
+ # Char position: 21-30
197
+ output += (credit_total_amount - debit_total_amount).abs.to_s.rjust(10, "0")
198
+
199
+ # Credit Total Amount
200
+ # Max: 10
201
+ # Char position: 31-40
202
+ output += credit_total_amount.to_s.rjust(10, "0")
203
+
204
+ # Debit Total Amount
205
+ # Max: 10
206
+ # Char position: 41-50
207
+ output += debit_total_amount.to_s.rjust(10, "0")
208
+
209
+ # Reserved
210
+ # Max: 24
211
+ # Char position: 51-74
212
+ output += " " * 24
213
+
214
+ # Total Item Count
215
+ # Max: 6
216
+ # Char position: 75-80
217
+ output += entries.size.to_s.rjust(6, "0")
218
+
219
+ # Reserved
220
+ # Max: 40
221
+ # Char position: 81-120
222
+ output += " " * 40
223
+ end
224
+ end
225
+ end