stagecoach 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>ach</name>
4
+ <comment></comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ </buildSpec>
9
+ <natures>
10
+ <nature>org.radrails.rails.core.railsnature</nature>
11
+ </natures>
12
+ </projectDescription>
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ gem "mocha", "0.9.11"
14
+ gem "extensions", "0.6.0"
15
+ end
@@ -0,0 +1,33 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ extensions (0.6.0)
6
+ git (1.2.5)
7
+ jeweler (1.6.4)
8
+ bundler (~> 1.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ mocha (0.9.11)
12
+ rake
13
+ rake (0.9.2.2)
14
+ rcov (0.9.11)
15
+ rspec (2.3.0)
16
+ rspec-core (~> 2.3.0)
17
+ rspec-expectations (~> 2.3.0)
18
+ rspec-mocks (~> 2.3.0)
19
+ rspec-core (2.3.1)
20
+ rspec-expectations (2.3.0)
21
+ diff-lcs (~> 1.1.2)
22
+ rspec-mocks (2.3.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ bundler (~> 1.0.0)
29
+ extensions (= 0.6.0)
30
+ jeweler (~> 1.6.4)
31
+ mocha (= 0.9.11)
32
+ rcov
33
+ rspec (~> 2.3.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Doug Reed
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ = ach
2
+
3
+ WellsFargo ACH file generation gem from Billfloat.
4
+
5
+ == Contributing to ach
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Doug Reed. See LICENSE.txt for
18
+ further details.
19
+
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "ach"
18
+ gem.homepage = "http://github.com/dougereed/ach"
19
+ gem.license = "MIT"
20
+ gem.summary = "WellsFargo ACH file generator"
21
+ gem.description = "WellsFargo ACH file generator"
22
+ gem.email = "dougereed@gmail.com"
23
+ gem.authors = ["Doug Reed"]
24
+ gem.files.include 'lib/ach/*'
25
+ # dependencies defined in Gemfile
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "ach #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
Binary file
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/ach/iterator.rb'
2
+ require File.dirname(__FILE__) + '/ach/creator.rb'
3
+ require File.dirname(__FILE__) + '/ach/observer.rb'
4
+ require File.dirname(__FILE__) + '/ach/parser.rb'
5
+
@@ -0,0 +1,485 @@
1
+ require 'extensions/object'
2
+
3
+ module Ach
4
+ class Creator
5
+
6
+ def self.block_count(filelines)
7
+ filelines = ((filelines.to_f+1)/10)
8
+ return filelines.round
9
+ end
10
+
11
+ def self.write_header(content,ach_filename)
12
+ ach_file = File.new(ach_filename.path,"a+")
13
+ ach_file.puts(content)
14
+ ach_file.close
15
+ end
16
+
17
+ def self.mod_operation(routing_num_mod)
18
+ @rtnum = routing_num_mod.to_s.split(//)
19
+ @multiply = Array.new
20
+ @multiply.push(@rtnum[0].to_i * 3)
21
+ @multiply.push(@rtnum[1].to_i * 7)
22
+ @multiply.push(@rtnum[2].to_i * 1)
23
+ @multiply.push(@rtnum[3].to_i * 3)
24
+ @multiply.push(@rtnum[4].to_i * 7)
25
+ @multiply.push(@rtnum[5].to_i * 1)
26
+ @multiply.push(@rtnum[6].to_i * 3)
27
+ @multiply.push(@rtnum[7].to_i * 7)
28
+ @add = (@multiply[0] + @multiply[1] + @multiply[2] + @multiply[3] + @multiply[4] + @multiply[5] + @multiply[6] + @multiply[7])
29
+ if (@add % 10) == 0
30
+ return (@add % 10)
31
+ else
32
+ return ((@add + 10 - (@add % 10)) - @add)
33
+ end
34
+ end
35
+
36
+ def self.create_file(structure,iterator,ach_filename)
37
+ # records = iterator.get_entry_details
38
+
39
+ @create_ach_file = File.new(ach_filename,"w+")
40
+ @create_ach_file.close
41
+
42
+ @addenda_count = 1
43
+ @addenda_record_count = Array.new
44
+ @trace_number = 0
45
+ @trace_number_counter = Array.new
46
+ @sequence_number = 1
47
+ @entry_hash_collector = Array.new
48
+
49
+ @filecontrol_debits = []
50
+ @filecontrol_credits = []
51
+ @entrydetails_counter= []
52
+
53
+ SecurityHeaderRecord.new(
54
+ :security_header => structure[:bank_params][:security_header],
55
+ :write_to_file => @create_ach_file)
56
+
57
+ file_header_obj = FileHeaderRecord.new(
58
+ :record_type => "1",
59
+ :priority_code => "01",
60
+ :record_size => "094",
61
+ :blocking_factor => "10",
62
+ :file_creation_date => structure[:file_header_record][:file_creation_date],
63
+ :file_creation_time => structure[:file_header_record][:file_creation_time],
64
+ :format_code => "1",
65
+ :file_id => structure[:bank_params][:file_id_yml],
66
+ :bank_name => structure[:bank_params][:name],
67
+ :company_name => structure[:file_header_record][:company_name],
68
+ :file_id_modifier => structure[:file_header_record][:file_id_modifier],
69
+ :origination_bank => "WELLS FARGO",
70
+ :wellsfargo_routing_number => " 091000019",
71
+ :write_to_file => @create_ach_file)
72
+
73
+ structure[:batch_header_records].each_with_index do |bhr,index|
74
+ @batch_debit_collector = []
75
+ @batch_credit_collector = []
76
+
77
+ @batch_header_obj = BatchHeaderRecord.new(
78
+ :record_type => '5',
79
+ :service_class_code => bhr[:service_class_code],
80
+ :company_name => bhr[:company_name],
81
+ :company_discretionary_data => (bhr[:company_discretionary_data] ? bhr[:company_discretionary_data] : nil),
82
+ :company_id => structure[:bank_params][:company_id_yml],
83
+ :standard_entry_class_code => bhr[:standard_entry_class_code],
84
+ :company_entry_description => bhr[:company_entry_description],
85
+ :company_descriptive_date => (bhr[:company_descriptive_date] ? bhr[:company_descriptive_date] : nil),
86
+ :effective_entry_date => bhr[:effective_entry_date],
87
+ :batch_number => (index+1).to_s,
88
+ :originator_status_code => "1",
89
+ :wellsfargo_routing_number => structure[:bank_params][:routing_num_originating_dfi_yml],
90
+ :write_to_file => @create_ach_file)
91
+
92
+ #chunk which supports creation of entry_detail db entries. Send in batch db entry's id
93
+ @entry_details = iterator.get_entry_details
94
+ @entrydetails_counter << @entry_details.size.to_i
95
+ @entry_details.each do |i|
96
+ @receiving_dfi_routing_num = i[:receiving_dfi_routing_number]
97
+ rt_num_check_mod = mod_operation(@receiving_dfi_routing_num)
98
+ @trace_number_counter.push @trace_number
99
+ @trace_number+=1
100
+
101
+ entry_detail = EntryDetailRecord.new(
102
+ :transaction_code => i[:transaction_code],
103
+ :receiving_dfi_routing_num => @receiving_dfi_routing_num,
104
+ :rt_num_check => rt_num_check_mod,
105
+ :receiving_dfi_acc_num => i[:receiving_dfi_account_number],
106
+ :amount => i[:amount].to_s.sub(/[.]/,''),
107
+ :individual_id => i[:individual_id],
108
+ :individual_name => (i[:individual_name].nil? ? "" : i[:individual_name]),
109
+ :routing_number => structure[:bank_params][:routing_num_originating_dfi_yml],
110
+ :trace_number => @trace_number,
111
+ :settlement_id => i.respond_to?(:settlement_id) ? i[:settlement_id] : nil,
112
+ :loan_payment_id => i.respond_to?(:loan_payment_id) ? i[:loan_payment_id] : nil,
113
+ :write_to_file => @create_ach_file,
114
+ :db_entry_amount => i[:amount]) #amount for storing the ach_@entry_details_table
115
+
116
+ if (i[:transaction_code]=="22" or i[:transaction_code]=="32")
117
+ @batch_credit_collector.push i[:amount].to_f
118
+ else
119
+ @batch_debit_collector.push i[:amount].to_f
120
+ end
121
+
122
+ @rdfi_rtn_split = @receiving_dfi_routing_num.slice(0..7)
123
+ @rdfi_rtn_split.each {|i| @entry_hash_collector.push i.to_i}
124
+ @addenda_count+=1
125
+ @addenda_record_count.push @addenda_count
126
+ end
127
+ #TODO: REPLACE addendacount!!! @entry_details[:records].size
128
+ batch_control = BatchControlRecord.new(
129
+ :service_class_code => bhr[:service_class_code],
130
+ :addenda_count => @entry_details.size,
131
+ :entry_hash => @entry_hash_collector.inject(0) {|sum,i| sum+i},
132
+ :total_debits => @batch_debit_collector.inject(0) {|sum,i| sum+i}.to_s.sub(/[.]/,''),
133
+ :total_credits => @batch_credit_collector.inject(0) {|sum,i| sum+i}.to_s.sub(/[.]/,''),
134
+ :company_id => structure[:bank_params][:company_id_yml],
135
+ :routing_num => structure[:bank_params][:routing_num_originating_dfi_yml],
136
+ :batch_number => (index+1).to_s,
137
+ :write_to_file => @create_ach_file)
138
+ @batch_credit_collector.each {|x| @filecontrol_credits << x}
139
+ @batch_debit_collector.each {|x| @filecontrol_debits << x}
140
+ end
141
+
142
+ # rt_num_check_mod = mod_operation(structure[:bank_params][:receiving_dfi_routing_num])
143
+
144
+ @count_filelines = %x(wc -l #{"#{ach_filename}"}).split.first.to_i
145
+ @block_count = block_count(@count_filelines)
146
+ #((@trace_number_counter.last-1)+(@addenda_record_count.last-1)), #TODO: REPLACE THIS!!! @entry_details[:records].size
147
+ file_control = FileControlRecord.new(
148
+ :batch_count => structure[:batch_header_records].size,
149
+ :block_count => @block_count,
150
+ :entry_addenda => @entrydetails_counter.inject(0) {|sum,i| sum+i},
151
+ :entry_hash_total => @entry_hash_collector.inject(0) {|sum,i| sum+i},
152
+ :total_debits => @filecontrol_debits.inject(0) {|sum,i| sum+i}.to_s.sub(/[.]/,''),
153
+ :total_credits => @filecontrol_credits.inject(0) {|sum,i| sum+i}.to_s.sub(/[.]/,''),
154
+ :write_to_file => @create_ach_file)
155
+ end
156
+
157
+ end
158
+
159
+ class SecurityHeaderRecord
160
+ attr_accessor :security_header
161
+ def initialize(options={})
162
+ @security_header = options[:security_header]
163
+ @write_to_file = options[:write_to_file]
164
+ encode
165
+ end
166
+
167
+ protected
168
+
169
+ def encode
170
+ begin
171
+ file = @security_header.strip
172
+ Ach::Creator.write_header(file,@write_to_file)
173
+ rescue Exception => ex
174
+ puts "Exception while writing ach file -- #{ex.class} #{ex.message} #{ex.backtrace.pp_s}"
175
+ end
176
+ end
177
+ end
178
+
179
+ class FileHeaderRecord
180
+ attr_accessor :record_type, :priority_code, :routing_number, :file_id, :record_size, :file_id_modifier
181
+ attr_accessor :blocking_factor, :format_code, :origination_bank, :bank,:company_name,:bank_name,:call_encode
182
+ attr_accessor :file_creation_date,:file_creation_time
183
+ def initialize(options={})
184
+ @record_type = options[:record_type]
185
+ @priority_code = options[:priority_code]
186
+ @routing_number = options[:routing_number]
187
+ @file_id = options[:file_id]
188
+ @record_size = options[:record_size]
189
+ @blocking_factor = options[:blocking_factor]
190
+ @file_creation_date = options[:file_creation_date]
191
+ @file_creation_time = options[:file_creation_time]
192
+ @format_code = options[:format_code]
193
+ @company_name = options[:company_name]
194
+ @bank_name = options[:bank_name]
195
+ @write_to_file = options[:write_to_file]
196
+ @file_id_modifier = options[:file_id_modifier]
197
+ @origination_bank = options[:origination_bank]
198
+ @wellsfargo_routing_number = options[:wellsfargo_routing_number]
199
+ @call_encode = encode
200
+ end
201
+
202
+ def self.decode(record,processor)
203
+ # change method sig to decode(record,processor)
204
+ file_header = CustomOrderedHash.new
205
+ file_header.insert(:record_type,record[0].chr)
206
+ file_header.insert(:priority_code,record[1,2])
207
+ file_header.insert(:routing_number,record[4,9])
208
+ file_header.insert(:file_id,record[13,10])
209
+ file_header.insert(:file_creation_date, record[23,6])
210
+ file_header.insert(:file_creation_time,record[29,4])
211
+ file_header.insert(:file_id_mod,record[33,1])
212
+ file_header.insert(:record_size ,record[34,3])
213
+ file_header.insert(:blocking_factor, record[37,2])
214
+ file_header.insert(:format_code, record[39,1])
215
+ file_header.insert(:origination_bank, record[40,23])
216
+ file_header.insert(:company_name,record[63,23])
217
+ processor.process(file_header)
218
+ # log_dump ="#{@record_type}#{@priority_code}#{@routing_number}#{@file_id}#{@file_creation_time}#{@file_id_mod}#{@record_size}#{@blocking_factor}#{@format_code}#{@origination_bank}#{@company_name}"
219
+ # Batch::PartnersAchGenerateLog::AchLogGenerator.log_return_process(log_dump)
220
+ end
221
+
222
+ protected
223
+
224
+ def encode
225
+ begin
226
+ # file = "#{@record_type}#{@priority_code} #{@routing_number}#{@file_id}#{file_creation_date.strftime('%y%m%d')}#{file_creation_time.strftime('%H%M')}#{@file_id_modifier}094#{@blocking_factor}#{@format_code}WELLS FARGO Billfloat "
227
+ file = "#{@record_type}#{@priority_code}#{@wellsfargo_routing_number}#{@file_id}#{@file_creation_date.strftime('%y%m%d')}#{@file_creation_time.strftime('%H%M')}#{@file_id_modifier}#{@record_size}#{@blocking_factor}#{@format_code}#{@origination_bank} #{@company_name} "
228
+ Ach::Creator.write_header(file,@write_to_file)
229
+ rescue Exception => ex
230
+ puts "Exception writing ach file -- #{ex.message} #{ex.backtrace.pp_s}"
231
+ end
232
+ end
233
+
234
+ end
235
+
236
+ class BatchHeaderRecord
237
+ attr_accessor :record_type, :service_class_code, :sec_code, :company_entry_desc,:company_name
238
+ attr_accessor :effective_entry_date, :batch_number, :company_id, :originator_status_code, :routing_number,:company_descriptive_date
239
+ attr_accessor :company_discretionary_data,:company_entry_description,:company_desc_date,:settlement_date,:ach_file_id,:call_encode
240
+ def initialize(options={})
241
+ @record_type = options[:record_type]
242
+ @service_class_code = options[:service_class_code]
243
+ @company_name = options[:company_name]
244
+ @company_discretionary_data = options[:company_discretionary_data]
245
+ @company_id = options[:company_id]
246
+ @sec_code = options[:standard_entry_class_code]
247
+ @company_entry_desc = options[:company_entry_description]
248
+ @company_desc_date = options[:company_descriptive_date]
249
+ @posting_date = options[:effective_entry_date]
250
+ @batch_number = options[:batch_number]
251
+ @originator_status_code = options[:originator_status_code]
252
+ @wellsfargo_routing_number = options[:wellsfargo_routing_number]
253
+ @ach_file_id = options[:ach_file_id]
254
+ @write_to_file = options[:write_to_file]
255
+ @call_encode = encode
256
+ end
257
+
258
+ def self.decode(record,processor)
259
+ batch_header = CustomOrderedHash.new
260
+ batch_header.insert(:record_type,record[0].chr)
261
+ batch_header.insert(:@service_class_code,record[1,3])
262
+ batch_header.insert(:company_name,record[4,16])
263
+ batch_header.insert(:company_disc_data,record[20,20])
264
+ batch_header.insert(:company_id,record[40,10])
265
+ batch_header.insert(:sec_code,record[50,3])
266
+ batch_header.insert(:company_entry_desc,record[53,10])
267
+ batch_header.insert(:company_desc_date,record[63,6])
268
+ batch_header.insert(:posting_date, record[69,6])
269
+ batch_header.insert(:settlement_date,record[75,3])
270
+ batch_header.insert(:originator_status_code,record[78,1])
271
+ batch_header.insert(:routing_number,record[79,8])
272
+ batch_header.insert(:batch_number,record[87,7])
273
+ processor.process(batch_header)
274
+ end
275
+
276
+ protected
277
+
278
+ def encode
279
+ begin
280
+ file = @record_type
281
+ file << @service_class_code
282
+ file << @company_name.ljust(16)
283
+ file << (@company_discretionary_data ? @company_discretionary_data.ljust(20) : "".ljust(20))
284
+ file << @company_id.to_s.ljust(10)
285
+ file << @sec_code.upcase.ljust(3)
286
+ file << @company_entry_desc.ljust(10)
287
+ file << (@company_desc_date ? @company_desc_date.ljust(6) : "".ljust(6))
288
+ # file << (Date.today-1).strftime("%m%d%y").ljust(6) # Company Descriptive Date
289
+ if @posting_date.is_a?(String)
290
+ file << Date.parse(@posting_date).strftime("%y%m%d").to_s.ljust(6)
291
+ else
292
+ file << @posting_date.strftime("%y%m%d").to_s.ljust(6)
293
+ end
294
+ file << "".ljust(3) # Settlement date - leave blank
295
+ file << @originator_status_code.to_s.ljust(1)
296
+ file << @wellsfargo_routing_number.to_s.ljust(8)
297
+ @batch_number = "0000000000000"+@batch_number.to_s
298
+ file << @batch_number[-7,7] #added one number and minus from batch num digit count
299
+ Ach::Creator.write_header(file,@write_to_file)
300
+ # batch_header_entry.reload
301
+ # return batch_header_entry.id
302
+ rescue Exception => ex
303
+ puts "Exception while writing ach file -- #{ex.class} #{ex.message} #{ex.backtrace.pp_s}"
304
+ end
305
+ end
306
+
307
+ end
308
+
309
+ class EntryDetailRecord
310
+ attr_accessor :record_type, :transaction_code, :routing_number_check, :receiving_dfi_acc_num,:rt_num_check
311
+ attr_accessor :amount, :individual_id,:receiving_dfi_routing_num,:individual_id,:individual_name
312
+ attr_accessor :routing_number, :trace_number,:disc_data,:addenda_record_indicator,:ach_batch_id,:settlement_id,:db_entry_amount
313
+ def initialize(options = {})
314
+ @record_type = '6'
315
+ @transaction_code = options[:transaction_code]
316
+ @receiving_dfi_routing_num = options[:receiving_dfi_routing_num]
317
+ @receiving_dfi_acc_num = options[:receiving_dfi_acc_num]
318
+ @rt_num_check = options[:rt_num_check]
319
+ @amount = options[:amount]
320
+ @individual_id = options[:individual_id]
321
+ @individual_name = options[:individual_name]
322
+ @routing_number = options[:routing_number]
323
+ @trace_number = options[:trace_number]
324
+ @ach_batch_id = options[:ach_batch_id]
325
+ @settlement_id = options[:settlement_id]
326
+ @db_entry_amount = options[:db_entry_amount]
327
+ @write_to_file = options[:write_to_file]
328
+ encode
329
+ end
330
+
331
+ def self.decode(record,processor)
332
+ entry_detail = CustomOrderedHash.new
333
+ entry_detail.insert(:record_type,record[0].chr)
334
+ entry_detail.insert(:transaction_code, record[1,2])
335
+ entry_detail.insert(:receiving_dfi_routing_num,record[3,8])
336
+ entry_detail.insert(:rt_num_check,record[11,1])
337
+ entry_detail.insert(:receiving_dfi_acc_num,record[12,17])
338
+ entry_detail.insert(:amount,record[29,10])
339
+ entry_detail.insert(:individual_id,record[39,15])
340
+ entry_detail.insert(:individual_name,record[54,22])
341
+ entry_detail.insert(:disc_data,record[76,2])
342
+ entry_detail.insert(:addenda_record_indicator,record[78,1])
343
+ entry_detail.insert(:@trace_number,record[79,15])
344
+ processor.process(entry_detail)
345
+ end
346
+
347
+ protected
348
+
349
+ def encode
350
+ begin
351
+ file = @record_type
352
+ file << @transaction_code
353
+ file << @receiving_dfi_routing_num.to_s.split(//).first(8).to_s
354
+ file << @rt_num_check.to_s
355
+ file << @receiving_dfi_acc_num.to_s.split(//).last(17).to_s.ljust(17)
356
+ file << @amount.rjust(10,"0")
357
+ file << @individual_id.to_s.ljust(15)
358
+ file << @individual_name.slice(0..21).ljust(22)
359
+ file << "".ljust(2) #9. discretionary data
360
+ file << "0"
361
+ @trace_number = "000000000000000"+@trace_number.to_s
362
+ file << @routing_number+@trace_number[-7,7]
363
+ Ach::Creator.write_header(file,@write_to_file)
364
+ rescue Exception => ex
365
+ puts "Exception while writing ach file -- #{ex.class} #{ex.message} #{ex.backtrace.pp_s}"
366
+ end
367
+ end
368
+
369
+ end
370
+
371
+ class BatchControlRecord
372
+ attr_accessor :record_type, :service_class_code, :addenda_count,:entry_hash,:company_id
373
+ attr_accessor :total_debits,:total_credits,:batch_number,:routing_num,:msg_authentication_code
374
+ def initialize(options={})
375
+ @record_type = '8'
376
+ @service_class_code = options[:service_class_code]
377
+ @addenda_count = options[:addenda_count]
378
+ @entry_hash=options[:entry_hash]
379
+ @total_debits = options[:total_debits]
380
+ @total_credits = options[:total_credits]
381
+ @company_id = options[:company_id]
382
+ @routing_num = options[:routing_num]
383
+ @batch_number = options[:batch_number]
384
+ @write_to_file = options[:write_to_file]
385
+ encode
386
+ end
387
+
388
+ def self.decode(record,processor)
389
+ batch_control = CustomOrderedHash.new
390
+ batch_control.insert(:record_type,record[0].chr)
391
+ batch_control.insert(:service_class_code,record[1,3])
392
+ batch_control.insert(:addenda_count,record[4,6])
393
+ batch_control.insert(:entry_hash,record[10,10])
394
+ batch_control.insert(:total_debits, record[20,12])
395
+ batch_control.insert(:total_credits, record[32,12])
396
+ batch_control.insert(:company_id, record[44,10])
397
+ batch_control.insert(:msg_authentication_code, record[54,19])
398
+ batch_control.insert(:reserved,record[73,6])
399
+ batch_control.insert(:routing_num, record[79,8])
400
+ batch_control.insert(:batch_number, record[87,7])
401
+ processor.process(batch_control)
402
+ end
403
+
404
+ protected
405
+
406
+ def encode
407
+ begin
408
+ file = @record_type
409
+ file << @service_class_code.to_s
410
+ @addenda_digit_count = @addenda_count.to_s.split(//)
411
+ if @addenda_digit_count.size > 1
412
+ file << @addenda_count.to_s.rjust(5,"0")
413
+ else
414
+ file << @addenda_count.to_s.rjust(6,"0")
415
+ end
416
+ file << @entry_hash.to_s.rjust(10,"0")
417
+ file << @total_debits.rjust(12,"0")
418
+ file << @total_credits.rjust(12,"0")
419
+ file << @company_id.to_s
420
+ file << "".ljust(19)
421
+ file << "".ljust(6)
422
+ file << @routing_num
423
+ @batch_number = "0000000000000" + @batch_number.to_s
424
+ file << @batch_number[-7,7]
425
+ Ach::Creator.write_header(file,@write_to_file)
426
+ rescue Exception => ex
427
+ puts "Exception while writing ach file -- #{ex.class} #{ex.message} #{ex.backtrace.pp_s}"
428
+ end
429
+ end
430
+
431
+ end
432
+
433
+ class FileControlRecord
434
+ attr_accessor :record_type,:batch_count,:block_count,:entry_addenda
435
+ attr_accessor :entry_hash_total,:total_debits,:total_credits
436
+ def initialize(options={})
437
+ @record_type = '9'
438
+ @batch_count = options[:batch_count]
439
+ @block_count = options[:block_count]
440
+ @entry_addenda = options[:entry_addenda]
441
+ @entry_hash_total = options[:entry_hash_total]
442
+ @total_debits = options[:total_debits]
443
+ @total_credits = options[:total_credits]
444
+ @write_to_file = options[:write_to_file]
445
+ encode
446
+ end
447
+
448
+ def self.decode(record,processor)
449
+ file_control = CustomOrderedHash.new
450
+ file_control.insert(:record_type,record[0].chr)
451
+ file_control.insert(:batch_count, record[1,6])
452
+ file_control.insert(:block_count, record[7,6])
453
+ file_control.insert(:entry_addenda, record[13,8])
454
+ file_control.insert(:entry_hash_total, record[21,10])
455
+ file_control.insert(:total_debits, record[31,12])
456
+ file_control.insert(:total_credits, record[43,12])
457
+ processor.process(file_control)
458
+ end
459
+
460
+ protected
461
+
462
+ def encode
463
+ begin
464
+ file = @record_type
465
+ file << @batch_count.to_s.rjust(6,"0")
466
+ file << @block_count.to_s.rjust(6,"0")
467
+ file << @entry_addenda.to_s.rjust(8,"0")
468
+ @entry_hash_total_digit_count = @entry_hash_total.to_s.split(//)
469
+ if @entry_hash_total_digit_count.size >= 10
470
+ file << @entry_hash_total.to_s.slice(1..10).rjust(10,"0")
471
+ else
472
+ file << @entry_hash_total.to_s.slice(0..10).rjust(10,"0")
473
+ end
474
+ file << @total_debits.rjust(12,"0")
475
+ file << @total_credits.rjust(12,"0")
476
+ file << "".ljust(39)
477
+ Ach::Creator.write_header(file,@write_to_file)
478
+ rescue Exception => ex
479
+ puts "\n Exception writing ACH file -- #{ex}"
480
+ end
481
+ end
482
+
483
+ end
484
+
485
+ end
@@ -0,0 +1,7 @@
1
+ module Ach
2
+ class Iterator
3
+ def get_entry_details
4
+ raise Exception.new "This method should be implemented!"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ module Ach
2
+ class Observer
3
+
4
+ def file_header(hash)
5
+ raise Exception.new "This should be implemented!"
6
+ end
7
+
8
+ def batch_header(hash)
9
+ raise Exception.new "This should be implemented!"
10
+ end
11
+
12
+ def entry_detail(hash)
13
+ raise Exception.new "This should be implemented!"
14
+ end
15
+
16
+ def batch_control(hash)
17
+ raise Exception.new "This should be implemented!"
18
+ end
19
+
20
+ def file_control(hash)
21
+ raise Exception.new "This should be implemented!"
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,124 @@
1
+ module Ach
2
+ class Parser
3
+ def self.parse_file(observer)
4
+ raise Exception.new "No file supplied. Cannot proceed!" if observer.filename.nil?
5
+ File.open(observer.filename).each do |l|
6
+ case l[0].chr
7
+ when "1"
8
+ file_header = FileHeaderRecord.decode(l)
9
+ observer.file_header(file_header)
10
+ when "5"
11
+ batch_header = BatchHeaderRecord.decode(l)
12
+ observer.batch_header(batch_header)
13
+ when "6"
14
+ entry_detail = EntryDetailRecord.decode(l)
15
+ observer.entry_detail(entry_detail)
16
+ when "8"
17
+ batch_control = BatchControlRecord.decode(l)
18
+ observer.batch_control(batch_control)
19
+ when "9"
20
+ unless (l.count "9") == 94
21
+ file_control = FileControlRecord.decode(l)
22
+ observer.file_control(file_control)
23
+ end
24
+ else
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class FileHeaderRecord
31
+ def self.decode(record)
32
+ fh = {}
33
+ fh[:record_type] = record[0].chr
34
+ fh[:priority_code] = record[1,2]
35
+ fh[:wellsfargo_routing_number] = record[4,9]
36
+ fh[:file_id] = record[13,10]
37
+ fh[:file_creation_date] = record[23,6]
38
+ fh[:file_creation_time] = record[29,4]
39
+ fh[:file_id_modifier] = record[33,1]
40
+ fh[:record_size] = record[34,3]
41
+ fh[:blocking_factor] = record[37,2]
42
+ fh[:format_code] = record[39,1]
43
+ fh[:origination_bank] = record[40,23]
44
+ fh[:company_name] = record[63,23]
45
+ return fh
46
+ end
47
+ end
48
+
49
+ class BatchHeaderRecord
50
+ def self.decode(record)
51
+ bh = {}
52
+ bh[:record_type] = record[0].chr
53
+ bh[:service_class_code] = record[1,3]
54
+ bh[:company_name] = record[4,16]
55
+ bh[:company_discretionary_data] = record[20,20]
56
+ bh[:company_id] = record[40,10]
57
+ bh[:standard_entry_class_code] = record[50,3]
58
+ bh[:company_entry_description] = record[53,10]
59
+ bh[:company_descriptive_date] = record[63,6]
60
+ bh[:effective_entry_date] = record[69,6]
61
+ bh[:settlement_date] = record[75,3]
62
+ bh[:originator_status_code] = record[78,1]
63
+ bh[:wellsfargo_routing_number] = record[79,8]
64
+ bh[:batch_number] = record[87,7]
65
+ return bh
66
+ end
67
+ end
68
+
69
+ class EntryDetailRecord
70
+ def self.decode(record)
71
+ ed = {}
72
+ ed[:record_type] = record[0].chr
73
+ ed[:transaction_code] = record[1,2]
74
+ ed[:receiving_dfi_routing_number] = record[3,8]
75
+ ed[:routing_number_check_digit] = record[11,1]
76
+ ed[:receiving_dfi_account_number] = record[12,17]
77
+ ed[:amount] = record[29,10]
78
+ ed[:individual_id] = record[39,15]
79
+ ed[:individual_name] = record[54,22]
80
+ ed[:discretionary_data] = record[76,2]
81
+ ed[:addenda_record_indicator] = record[78,1]
82
+ ed[:trace_number] = record[79,15]
83
+ return ed
84
+ end
85
+ end
86
+
87
+ class BatchControlRecord
88
+ def self.decode(record)
89
+ bcr = {}
90
+ bcr[:record_type] = record[0].chr
91
+ bcr[:service_class_code] = record[1,3]
92
+ bcr[:entry_addenda_count] = record[4,6]
93
+ bcr[:entry_hash] = record[10,10]
94
+ bcr[:total_batch_debit_entry_dollar_amount] = record[20,12]
95
+ bcr[:total_batch_credit_entry_dollar_amount] = record[32,12]
96
+ bcr[:company_id] = record[44,10]
97
+ bcr[:message_authentication_code] = record[54,19]
98
+ bcr[:blank] = record[73,6]
99
+ bcr[:wellsfargo_routing_number] = record[79,8]
100
+ bcr[:batch_number] = record[87,7]
101
+ return bcr
102
+ end
103
+ end
104
+
105
+ class FileControlRecord
106
+ def self.decode(record)
107
+ fcr = {}
108
+ msg_codes = []
109
+ msg_codes << record[56,2] unless record[56,2].match(/\s/)
110
+ msg_codes << record [58,2] unless record[58,2].match(/\s/)
111
+ msg_codes << record[60,2] unless record[60,2].match(/\s/)
112
+ fcr[:record_type] = record[0].chr
113
+ fcr[:batch_count] = record[1,6]
114
+ fcr[:block_count] = record[7,6]
115
+ fcr[:entry_addenda_record_count] = record[13,8]
116
+ fcr[:entry_hash_total] = record[21,10]
117
+ fcr[:total_file_debit_entry_amount] = record[31,12]
118
+ fcr[:total_file_credit_entry_amount] = record[43,12]
119
+ fcr[:message_codes] = msg_codes unless msg_codes.empty?
120
+ return fcr
121
+ end
122
+ end
123
+
124
+ end
@@ -0,0 +1,99 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ include Ach
4
+ describe "Ach file generation" do
5
+
6
+ before(:each) do
7
+ @structure = {}
8
+ @structure[:bank_params] = {}
9
+ @structure[:bank_params][:name] = "wellsfargo"
10
+ # bank_params[:receiving_dfi_routing_num] = BankRoutingNumber.find(25).routing_number
11
+ @structure[:bank_params][:routing_num_immediate_yml] = "082000073"
12
+ @structure[:bank_params][:routing_num_originating_dfi_yml] = "082000073"
13
+ @structure[:bank_params][:tax_id_yml] = "1234567895"
14
+ @structure[:bank_params][:file_id_yml] = "1234567895"
15
+ @structure[:bank_params][:company_id_yml] = "1234567895"
16
+ @structure[:bank_params][:security_header] = "security_header"
17
+ @structure[:bank_params][:originator_status_code] = "1"
18
+ @iterator = Ach::Iterator.new
19
+ filename = "testfile.txt"
20
+ bank_account_nums = []
21
+ bank_account_nums << "10022001"
22
+ inputs = {}
23
+ inputs[:records] = @settlements
24
+ inputs[:bank_data] = bank_account_nums
25
+ file_creation_date = Date.today
26
+ file_creation_time = DateTime.now
27
+ @structure[:file_header_record] = {}
28
+ @structure[:file_header_record][:file_creation_date] = file_creation_date
29
+ @structure[:file_header_record][:file_creation_time] = file_creation_time
30
+ @structure[:file_header_record][:company_name] = "Billfloat"
31
+ @structure[:file_header_record][:file_id_modifier] = "A"
32
+
33
+ batch = []
34
+ bhr = {}
35
+ bhr[:service_class_code] = '200'
36
+ bhr[:company_name] = "Billfloat"
37
+ bhr[:standard_entry_class_code] = "CCD"
38
+ bhr[:company_entry_description] = "SETTLED"
39
+ bhr[:effective_entry_date] = Date.today.to_s
40
+ batch << bhr
41
+
42
+ @structure[:batch_header_records] = batch
43
+
44
+ ed = {}
45
+ ed[:transaction_code] = "27"
46
+ ed[:individual_name] = "test"
47
+ ed[:individual_id] = "20"
48
+ ed[:amount] = "100.23"
49
+ ed[:receiving_dfi_routing_number] = "082000073"
50
+ ed[:receiving_dfi_account_number] = "654564654564"
51
+ ed[:loan_payment_id] = "32"
52
+ entry_detail = []
53
+ entry_detail << ed
54
+
55
+ @iterator.stubs(:get_entry_details).returns(entry_detail)
56
+ @observer = Ach::Observer.new
57
+ @observer.stubs(:file_header).returns(Hash.new)
58
+ @observer.stubs(:batch_header).returns(Hash.new)
59
+ @observer.stubs(:batch_control).returns(Hash.new)
60
+ @observer.stubs(:file_control).returns(Hash.new)
61
+ end
62
+
63
+ after(:all) do
64
+ test_files = Dir.glob("#{Dir.pwd}/*.txt")
65
+ unless test_files.empty?
66
+ test_files.each do |f|
67
+ if f.include?("_ach_")
68
+ File.delete(f)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ it "should create ach file" do
75
+ filename = "_ach_#{rand(999)}.txt"
76
+ Ach::Creator.create_file(@structure,@iterator,filename)
77
+ File.exists?(filename).should == true
78
+ end
79
+
80
+ it "test block count" do
81
+ fn = Ach::Creator.block_count(10)
82
+ fn.should_not be nil
83
+ fn.class.name.should == "Fixnum"
84
+ fn.should == 1
85
+ end
86
+
87
+ it "test mod_operation" do
88
+ fn = Ach::Creator.mod_operation("082000073")
89
+ fn.should==3
90
+ fn = Ach::Creator.mod_operation("011000536")
91
+ fn.should==6
92
+ end
93
+
94
+ it "should raise exception when no file is supplied" do
95
+ obs = Ach::Observer.new
96
+ expect {Ach::Parser.parse_file(nil,obs)}.should raise_error
97
+ end
98
+
99
+ end
@@ -0,0 +1,6 @@
1
+ $$ADD ID=W5S87LF6 BID='NWFACH32708654'
2
+ 101 09100001952708654971111181128A094101WELLS FARGO Billfloat
3
+ 5200Billfloat 5270865497CCDPAYMENT 111117 1091000010000001
4
+ 62708200007365498494654 00000113242 asim e 0091000010000001
5
+ 820000000100082000070000000113240000000000005270865497 091000010000001
6
+ 9000001000001000000010008200007000000011324000000000000 01
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rubygems'
4
+ require 'rspec'
5
+ require 'ach'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ RSpec.configure do |config|
12
+ config.color_enabled = true
13
+ config.mock_with :mocha
14
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stagecoach
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Doug Reed
14
+ - Asim Abdul
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2012-01-11 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 0
34
+ version: 2.3.0
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: bundler
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 23
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 0
50
+ version: 1.0.0
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: jeweler
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 7
62
+ segments:
63
+ - 1
64
+ - 6
65
+ - 4
66
+ version: 1.6.4
67
+ type: :development
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: rcov
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ type: :development
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - "="
90
+ - !ruby/object:Gem::Version
91
+ hash: 45
92
+ segments:
93
+ - 0
94
+ - 9
95
+ - 11
96
+ version: 0.9.11
97
+ type: :development
98
+ version_requirements: *id005
99
+ - !ruby/object:Gem::Dependency
100
+ name: extensions
101
+ prerelease: false
102
+ requirement: &id006 !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - "="
106
+ - !ruby/object:Gem::Version
107
+ hash: 7
108
+ segments:
109
+ - 0
110
+ - 6
111
+ - 0
112
+ version: 0.6.0
113
+ type: :development
114
+ version_requirements: *id006
115
+ description: WellsFargo ACH file generator
116
+ email: dougereed@gmail.com
117
+ executables: []
118
+
119
+ extensions: []
120
+
121
+ extra_rdoc_files:
122
+ - LICENSE.txt
123
+ - README.rdoc
124
+ files:
125
+ - .DS_Store
126
+ - .document
127
+ - .project
128
+ - .rspec
129
+ - Gemfile
130
+ - Gemfile.lock
131
+ - LICENSE.txt
132
+ - README.rdoc
133
+ - Rakefile
134
+ - VERSION
135
+ - lib/.DS_Store
136
+ - lib/ach.rb
137
+ - lib/ach/creator.rb
138
+ - lib/ach/iterator.rb
139
+ - lib/ach/observer.rb
140
+ - lib/ach/parser.rb
141
+ - spec/ach_spec.rb
142
+ - spec/cach_conf
143
+ - spec/spec_helper.rb
144
+ homepage: http://github.com/dougereed/ach
145
+ licenses:
146
+ - MIT
147
+ post_install_message:
148
+ rdoc_options: []
149
+
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ hash: 3
158
+ segments:
159
+ - 0
160
+ version: "0"
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ none: false
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ hash: 3
167
+ segments:
168
+ - 0
169
+ version: "0"
170
+ requirements: []
171
+
172
+ rubyforge_project:
173
+ rubygems_version: 1.8.15
174
+ signing_key:
175
+ specification_version: 3
176
+ summary: WellsFargo ACH file generator
177
+ test_files: []
178
+