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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 989126f3d5c6f6c728d31e3330293a907e045d7b
4
- data.tar.gz: 44341ab465bc09d7a059bd7709c39b3cc004dce8
2
+ SHA256:
3
+ metadata.gz: f7d2996812bbfd9d6386cef252e680024545ad59b01eec0d6645d8cf75dafe6a
4
+ data.tar.gz: 584aad9a1c2b922680da873f1a95922dfa4864c5e54ac979d6ed6f5eb01e3094
5
5
  SHA512:
6
- metadata.gz: 626ece57dc5490418f7e55583669c72f03479fb62620e0cc94cfbc9e7d59e134373861dac585a88bf781d2497da44a5f5c632d4f7097dcb8ec577e2488117a1c
7
- data.tar.gz: b9422ab783d663911b8144bcd5d8e14f0ab3b2d5506d19c62927a57843ecd7413e8b1643e2a33a3b4d950a228ad35b4bb3e763c2f3d57b9f9eadb6f663fd9b29
6
+ metadata.gz: 4be17e99ad4fedbb404bb2305e0a310afafc96c0f2b39f57f49d76b198e7018a7fc9997b35441d2209bce37986487d683fcfd5f20d049acea8581804669ecc8b
7
+ data.tar.gz: b09e75f48ce6ddab89dc0fc82f1d9be3a87f573db08591d65158203b2b1a4d1ae6c3ec6cbf34437d1c4d15842e4e93f07f75977bbf5ac063338fbd2dc519a177
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
data/README.md CHANGED
@@ -1,34 +1,125 @@
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
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
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 },
82
+ { bsb: '456-789', account_number: '123-456-789', amount: '-10000', transaction_code: 13 }
83
+ ]
84
+ )
85
+
86
+ # NOTE: Be careful with negative transaction amounts! transaction_code will not
87
+ # be set to debit automatically!
30
88
  ```
31
89
 
90
+ Validation errors can be caught in several ways:
91
+
92
+ ```ruby
93
+ # Create an ABA object with invalid character in the user_name
94
+ aba = Aba.batch(
95
+ financial_institution: "ANZ",
96
+ user_name: "Jøhn Doe",
97
+ user_id: "123456",
98
+ process_at: Time.now.strftime("%d%m%y")
99
+ )
100
+
101
+ # Add a transaction with a bad BSB
102
+ aba.add_transaction(
103
+ bsb: "abc-123",
104
+ account_number: "000123456"
105
+ )
106
+
107
+ # Is the data valid?
108
+ aba.valid?
109
+ # Returns: false
110
+
111
+ # Return a structured array of errors
112
+ puts aba.errors
113
+ # Returns:
114
+ # {:aba => ["user_name must not contain invalid characters"],
115
+ # :transactions =>
116
+ # {0 => ["bsb format is incorrect", "trace_bsb format is incorrect"]}}
117
+ ```
118
+
119
+ Validation errors will stop parsing of the data to an ABA formatted string using
120
+ `to_s`. `aba.to_s` will raise a `RuntimeError` instead of returning output.
121
+
122
+
32
123
  ## Installation
33
124
 
34
125
  Add this line to your application's Gemfile:
@@ -6,10 +6,10 @@ 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"]
11
- spec.summary = ["ABA File Generator"]
12
- spec.description = ["ABA (Australian Bankers Association) File Generator"]
9
+ spec.authors = ["Andrey Bazhutkin", "Trevor Wistaff"]
10
+ spec.email = ["andrey.bazhutkin@gmail.com", "trev@a07.com.au"]
11
+ spec.summary = "ABA File Generator"
12
+ spec.description = "ABA (Australian Bankers Association) File Generator"
13
13
  spec.homepage = "https://github.com/andrba/aba"
14
14
  spec.license = "MIT"
15
15
 
@@ -18,9 +18,10 @@ 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"
21
+ spec.add_development_dependency "bundler"
22
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "pry"
23
24
  spec.add_development_dependency "rspec", "~> 3.0"
24
25
 
25
- spec.required_ruby_version = '>= 1.9.2'
26
+ spec.required_ruby_version = '>= 2.5.0'
26
27
  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,224 @@
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
+ net_total_amount = 0
169
+ credit_total_amount = 0
170
+ debit_total_amount = 0
171
+
172
+ entries.each do |t|
173
+ net_total_amount += t.amount.to_i
174
+ credit_total_amount += t.amount.to_i if t.amount.to_i > 0
175
+ debit_total_amount += t.amount.to_i if t.amount.to_i < 0
176
+ end
177
+
178
+ # Record type
179
+ # Max: 1
180
+ # Char position: 1
181
+ output = "7"
182
+
183
+ # BSB Format Filler
184
+ # Max: 7
185
+ # Char position: 2-8
186
+ output += "999-999"
187
+
188
+ # Reserved
189
+ # Max: 12
190
+ # Char position: 9-20
191
+ output += " " * 12
192
+
193
+ # Net total
194
+ # Max: 10
195
+ # Char position: 21-30
196
+ output += net_total_amount.abs.to_s.rjust(10, "0")
197
+
198
+ # Credit Total Amount
199
+ # Max: 10
200
+ # Char position: 31-40
201
+ output += credit_total_amount.abs.to_s.rjust(10, "0")
202
+
203
+ # Debit Total Amount
204
+ # Max: 10
205
+ # Char position: 41-50
206
+ output += debit_total_amount.abs.to_s.rjust(10, "0")
207
+
208
+ # Reserved
209
+ # Max: 24
210
+ # Char position: 51-74
211
+ output += " " * 24
212
+
213
+ # Total Item Count
214
+ # Max: 6
215
+ # Char position: 75-80
216
+ output += entries.size.to_s.rjust(6, "0")
217
+
218
+ # Reserved
219
+ # Max: 40
220
+ # Char position: 81-120
221
+ output += " " * 40
222
+ end
223
+ end
224
+ end