aba 0.1.0 → 1.0.0

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: 4e304f03903a8b1cb21d29421192a35c2a8871bf
4
- data.tar.gz: fb95004c3d622d371e5402d754b3892cc1b2434c
2
+ SHA256:
3
+ metadata.gz: b184fa383958c27a1a91ae83d9cee93318ac763928fbef9e9adbed2888ef122f
4
+ data.tar.gz: 663ac724030286eb6ee155b2e756c1a01f675549ee857eee9688b450a364ba9b
5
5
  SHA512:
6
- metadata.gz: 317b8b821976c27476a74dfa084563e8245908d31a4d1c9caaea3c4ef1fb7d80543147ef1a6c258adfbd8f405620480159668d9035c40d66a5530f1ef1a482fe
7
- data.tar.gz: c71f432a1bde06cd05630a7e9b4fefaf0d16230c96c2a70bfd2b99f89dc63faddeeb7e6140692419d09307bbc13689ffa6b5ba86ecb5435f1f8f7a903ae803a0
6
+ metadata.gz: b64dadcc178a89b82eabcb89dcc3df12cdec4ecf4debea24714b45e25a8b8b9a7d711a2f12bd37746b902effd0aa347218f34545deb6b467b109faf5acaf0df1
7
+ data.tar.gz: 6d1b962350bd687c305668ab23d161878c9876f6560f2429c22cf204811d46b8c7f9c9b619bb6456c6c5a972c9d1f9b3e48ed7310cd270a83e9c9cdcea4503c7
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,14 @@
1
+ ## v1.0.0, 12 July 2020
2
+
3
+ ### BREAKING CHANGES
4
+
5
+ * Positive (`n`) and negative (`-n`) values are now treated the same.
6
+ e.g `5` and `-5` are both processed as `5`, without any signage.
7
+ 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)
8
+ * Removed default values for transactions records to avoid generation of potentially incorrect records. Safety first!
9
+ * Minimum Ruby version is now 2.5
10
+
11
+ ### NEW FEATURE
12
+
13
+ * You can now add a *return* record to be used when you'd like to return
14
+ a credit or a debit to another financial institution (OFI).
data/README.md CHANGED
@@ -1,34 +1,126 @@
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
+
1
3
  # Aba
2
4
 
3
- The purpose of this gem is to generate an ABA (Australian Banking Association) file. It is a format used by banks to allow for batch transaction.
5
+ Generates ABA (Australian Banking Association) file format output
4
6
 
5
7
  ## Usage
6
8
 
7
9
  ```ruby
8
10
  require 'aba'
9
11
 
10
- aba = Aba.new(bsb: "123-345", financial_institution: "WPC", user_name: "John Doe",
11
- user_id: "466364", description: "Payroll", process_at: Time.now)
12
-
13
- # Add transactions
14
- transactions.each do |t|
15
- aba.transactions << Aba::Transaction.new(
16
- :bsb => "342-342",
17
- :account_number => "3244654",
18
- :amount => amount,
19
- :account_name => "John Doe",
20
- :payment_id => "P2345543",
21
- :transaction_code => 53,
22
- :lodgement_reference => "R435564",
23
- :trace_bsb => "453-543",
24
- :trace_account_number => "45656733",
25
- :name_of_remitter => "Remitter"
12
+ # Initialise ABA
13
+ aba = Aba.batch(
14
+ bsb: "123-345", # Optional (Not required by NAB)
15
+ financial_institution: "WPC",
16
+ user_name: "John Doe",
17
+ user_id: "466364",
18
+ description: "Payroll",
19
+ process_at: Time.now.strftime("%d%m%y")
20
+ )
21
+
22
+ # Add transactions...
23
+ 10.times do
24
+ aba.add_transaction(
25
+ {
26
+ bsb: "342-342",
27
+ account_number: "3244654",
28
+ amount: 10000, # Amount in cents
29
+ account_name: "John Doe",
30
+ transaction_code: 53,
31
+ lodgement_reference: "R435564",
32
+ trace_bsb: "453-543",
33
+ trace_account_number: "45656733",
34
+ name_of_remitter: "Remitter"
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
+ }
26
56
  )
27
57
  end
28
58
 
29
- puts aba.to_s
59
+ puts aba.to_s # View output
60
+ File.write("/Users/me/dd_#{Time.now.to_i}.aba", aba.to_s) # or write output to file
61
+ ```
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
+ )
30
85
  ```
31
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
+
32
124
  ## Installation
33
125
 
34
126
  Add this line to your application's Gemfile:
@@ -6,8 +6,8 @@ require 'aba/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "aba"
8
8
  spec.version = Aba::VERSION
9
- spec.authors = ["Andrey Bazhutkin"]
10
- spec.email = ["andrey.bazhutkin@gmail.com"]
9
+ spec.authors = ["Andrey Bazhutkin", "Trevor Wistaff"]
10
+ spec.email = ["andrey.bazhutkin@gmail.com", "trev@a07.com.au"]
11
11
  spec.summary = "ABA File Generator"
12
12
  spec.description = "ABA (Australian Bankers Association) File Generator"
13
13
  spec.homepage = "https://github.com/andrba/aba"
@@ -18,9 +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"
21
+ spec.add_development_dependency "rake", "~> 13.0"
22
+ spec.add_development_dependency "pry", "~> 0.13"
23
23
  spec.add_development_dependency "rspec", "~> 3.0"
24
24
 
25
- spec.required_ruby_version = '>= 1.9.2'
25
+ spec.required_ruby_version = '>= 2.5.0'
26
26
  end
data/lib/aba.rb CHANGED
@@ -1,120 +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, :account_number, :financial_institution, :user_name, :user_id,
9
- :description, :process_at, :name_of_remitter, :transactions
10
-
11
- validates_presence_of :bsb, :financial_institution, :user_name, :user_id, :description, :process_at
12
-
13
- validates_bsb :bsb
14
-
15
- validates_max_length :account_number, 9
16
- validates_max_length :financial_institution, 3
17
- validates_max_length :user_name, 26
18
- validates_max_length :user_id, 6
19
- validates_max_length :description, 12
20
-
21
- def initialize(attrs = {})
22
- attrs.each do |key, value|
23
- send("#{key}=", value)
24
- end
25
-
26
- self.transactions = []
27
-
28
- yield self if block_given?
29
- end
30
-
31
- def to_s
32
- # Descriptive record
33
- output = "#{descriptive_record}\r\n"
34
-
35
- # Transactions records
36
- output += @transactions.map{ |t| t.to_s }.join("\r\n")
37
-
38
- # Batch control record
39
- output += "\r\n#{batch_control_record}"
40
- end
41
-
42
- private
43
-
44
- def descriptive_record
45
- # Record type
46
- output = "0"
47
-
48
- # Bank/State/Branch number of the funds account with a hyphen in the 4th character position. e.g. 013-999.
49
- output += self.bsb
50
-
51
- # Funds account number.
52
- output += self.account_number.to_s.ljust(9, " ")
53
-
54
- # Reserved
55
- output += " "
56
-
57
- # Sequence number
58
- output += "01"
59
-
60
- # Must contain the bank mnemonic that is associated with the BSB of the funds account. e.g. ‘ANZ’.
61
- output += self.financial_institution[0..2].to_s
62
-
63
- # Reserved
64
- output += " " * 7
65
-
66
- # Name of User supplying File as advised by User's Financial Institution
67
- output += self.user_name.to_s.ljust(26, " ")
68
-
69
- # Direct Entry User ID.
70
- output += self.user_id.to_s.rjust(6, "0")
71
-
72
- # Description of payments in the file (e.g. Payroll, Creditors etc.).
73
- output += self.description.to_s.ljust(12, " ")
74
-
75
- # Date and time on which the payment is to be processed.
76
- output += self.process_at.strftime("%d%m%y%H%M")
77
-
78
- # Reserved
79
- output += " " * 36
80
- end
81
-
82
- def batch_control_record
83
- # Record type
84
- output = "7"
85
-
86
- # BSB Format Filler
87
- output += "999-999"
88
-
89
- # Reserved
90
- output += " " * 12
91
-
92
- net_total_amount = 0
93
- credit_total_amount = 0
94
- debit_total_amount = 0
95
-
96
- @transactions.each do |t|
97
- net_total_amount += t.amount
98
- credit_total_amount += t.amount if t.amount > 0
99
- debit_total_amount += t.amount if t.amount < 0
100
- end
101
-
102
- # Must equal the difference between File Credit & File Debit Total Amounts. Show in cents without punctuation
103
- output += net_total_amount.abs.to_s.rjust(10, "0")
104
-
105
- # Batch Credit Total Amount
106
- output += credit_total_amount.abs.to_s.rjust(10, "0")
107
-
108
- # Batch Debit Total Amount
109
- output += debit_total_amount.abs.to_s.rjust(10, "0")
110
-
111
- # Reserved
112
- output += " " * 24
113
-
114
- # Batch Total Item Count
115
- output += @transactions.size.to_s.rjust(6, "0")
116
-
117
- # Reserved
118
- output += " " * 40
9
+ def self.batch(attrs = {}, transactions = [])
10
+ Aba::Batch.new(attrs, transactions)
119
11
  end
120
- end
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