ach_builder 0.0.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -1
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +11 -2
- data/MIT-LICENSE +1 -1
- data/README.md +19 -11
- data/Rakefile +44 -0
- data/ach_builder.gemspec +9 -5
- data/lib/ach/batch.rb +10 -32
- data/lib/ach/batch/builder.rb +60 -0
- data/lib/ach/batch/control.rb +18 -3
- data/lib/ach/batch/header.rb +21 -4
- data/lib/ach/component.rb +125 -43
- data/lib/ach/component/has_many_association.rb +155 -0
- data/lib/ach/constants.rb +15 -1
- data/lib/ach/file.rb +50 -44
- data/lib/ach/file/builder.rb +81 -0
- data/lib/ach/file/control.rb +15 -3
- data/lib/ach/file/header.rb +20 -3
- data/lib/ach/file/reader.rb +103 -0
- data/lib/ach/file/transmission_header.rb +77 -0
- data/lib/ach/formatter.rb +113 -76
- data/lib/ach/formatter/rule.rb +27 -0
- data/lib/ach/record.rb +13 -64
- data/lib/ach/record/addenda.rb +25 -0
- data/lib/ach/record/base.rb +109 -0
- data/lib/ach/record/dynamic.rb +58 -0
- data/lib/ach/record/entry.rb +49 -0
- data/lib/ach/record/tail.rb +10 -0
- data/lib/ach/validations.rb +5 -3
- data/lib/ach/version.rb +1 -1
- data/lib/ach_builder.rb +20 -33
- data/spec/batch_spec.rb +23 -11
- data/spec/componenet/has_many_association_spec.rb +111 -0
- data/spec/file_spec.rb +173 -81
- data/spec/formatter_spec.rb +9 -9
- data/spec/reader_spec.rb +55 -0
- data/spec/record/addenda_spec.rb +8 -0
- data/spec/record/base_spec.rb +53 -0
- data/spec/record/dynamic_spec.rb +74 -0
- data/spec/record/entry_spec.rb +7 -0
- data/spec/record/tail_spec.rb +7 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/ach_file_factory.rb +70 -0
- data/spec/support/examples/well_fargo_empty.ach +2 -0
- data/spec/support/examples/well_fargo_with_data.ach +6 -0
- data/spec/support/helpers/ach_files_examples.rb +17 -0
- metadata +69 -26
- data/lib/ach/entry.rb +0 -31
- data/lib/ach/tail.rb +0 -6
- data/spec/entry_spec.rb +0 -7
- data/spec/record_spec.rb +0 -34
- data/spec/tail_spec.rb +0 -7
@@ -0,0 +1,155 @@
|
|
1
|
+
module ACH
|
2
|
+
# Objects of this class host essential functionality required to create
|
3
|
+
# associated object from within owner objects.
|
4
|
+
#
|
5
|
+
# Newly instantiated +HasManyAssociation+ object has no owner, and should
|
6
|
+
# be used to assign it's copies to owners via +for+ method. This technique
|
7
|
+
# has following application:
|
8
|
+
# class Batch < ACH::Component
|
9
|
+
# association = HasManyAssociation.new(:entries)
|
10
|
+
#
|
11
|
+
# association.delegation_methods.each do |method_name|
|
12
|
+
# delegate method_name, :to => '@batches_association'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# after_initialize_hooks << lambda{ instance_variable_set('@batches_association', association.for(self)) }
|
16
|
+
# # All these lines of code are macrosed by <tt>ACH::Component.has_many</tt> method
|
17
|
+
# end
|
18
|
+
# # Now, whenever new batch is created, it will have it's own @batches_association,
|
19
|
+
# # and essential methods +batches+, +batch+, +build_batch+ delegated to it
|
20
|
+
# # (accordingly, to +container+, +create+, and +build+ methods)
|
21
|
+
class Component::HasManyAssociation
|
22
|
+
# If Record should be attached to (preceded by) other Record, this
|
23
|
+
# exception is raised on attempt to create attachment record without
|
24
|
+
# having preceded record. For example, Addenda records should be
|
25
|
+
# created after Entry records. Each new Addenda record will be attached
|
26
|
+
# to the latest Entry record.
|
27
|
+
class NoLinkError < ArgumentError
|
28
|
+
def initialize(link, klass)
|
29
|
+
super "No #{link} was found to attach a new #{klass}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Exception thrown if an association object, assigned for particular
|
34
|
+
# owner object, is used to assign to another owner object
|
35
|
+
class DoubleAssignmentError < StandardError
|
36
|
+
def initialize(name, owner)
|
37
|
+
super "Association #{name} has alredy been assigned to #{owner}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :name, :linked_to, :proc_defaults
|
42
|
+
private :linked_to, :proc_defaults
|
43
|
+
|
44
|
+
def initialize(plural_name, options = {})
|
45
|
+
@name = plural_name.to_s
|
46
|
+
@linked_to, @proc_defaults = options.values_at(:linked_to, :proc_defaults)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Clones +self+ and assigns +owner+ to clone. Also, for newly created
|
50
|
+
# clone association that has owner, aliases main methods so that +owner+
|
51
|
+
# may delegate to them.
|
52
|
+
def for(owner)
|
53
|
+
raise DoubleAssignmentError.new(@name, @owner) if @owner
|
54
|
+
|
55
|
+
clone.tap do |association|
|
56
|
+
plural, singular = name, singular_name
|
57
|
+
association.instance_variable_set('@owner', owner)
|
58
|
+
association.singleton_class.class_eval do
|
59
|
+
alias_method "build_#{singular}", :build
|
60
|
+
alias_method singular, :create
|
61
|
+
alias_method plural, :container
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns an array of methods to be delegated by +owner+ of the association.
|
67
|
+
# For example, for association named :items, it will include:
|
68
|
+
# * +build_item+ - for instantiating Item from the string (used by parsing functionality)
|
69
|
+
# * +item+ - for instantiating Item during common ACH File creation
|
70
|
+
# * +items+ - that returns set of Item objects
|
71
|
+
def delegation_methods
|
72
|
+
["build_#{singular_name}", singular_name, name]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Uses <tt>klass#from_s</tt> to instantiate object from a string. Thus, +klass+ should be
|
76
|
+
# descendant of ACH::Record::Base. Then pushes object to appropriate container.
|
77
|
+
def build(str)
|
78
|
+
obj = klass.from_s(str)
|
79
|
+
container_for_associated << obj
|
80
|
+
end
|
81
|
+
|
82
|
+
# Creates associated object using common to ACH controls pattern, and pushes it to
|
83
|
+
# appropriate container. For example, for :items association, this method is
|
84
|
+
# aliased to +item+, so you will have:
|
85
|
+
# item(:code => 'WEB') do
|
86
|
+
# other_code 'BEW'
|
87
|
+
# # ...
|
88
|
+
# end
|
89
|
+
def create(*args, &block)
|
90
|
+
fields = args.first || {}
|
91
|
+
|
92
|
+
defaults = proc_defaults ? @owner.instance_exec(&proc_defaults) : {}
|
93
|
+
|
94
|
+
klass.new(@owner.fields_for(klass).merge(defaults).merge(fields)).tap do |component|
|
95
|
+
component.instance_eval(&block) if block
|
96
|
+
container_for_associated << component
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns main container for association. For plain (without :linked_to option), it is
|
101
|
+
# array. For linked associations, it is a hash, which keys are records from linking
|
102
|
+
# associations, and values are arrays for association's objects
|
103
|
+
def container
|
104
|
+
@container ||= linked? ? {} : []
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns array for associated object to be pushed in. For plain associations, it is
|
108
|
+
# equivalent to +container+. For linked associations, uses +@owner+ and linking
|
109
|
+
# association's name to get the latest record from linking associations. If it does
|
110
|
+
# not exist, +NoLinkError+ will be raised.
|
111
|
+
#
|
112
|
+
# Example:
|
113
|
+
# class Batch < ACH::Component
|
114
|
+
# has_many :entries
|
115
|
+
# has_many :addendas, :linked_to => :entries
|
116
|
+
# end
|
117
|
+
# batch = Batch.new
|
118
|
+
# batch.entry(:amount => 100)
|
119
|
+
# batch.addenda(:text => 'Foo')
|
120
|
+
# batch.entry(:amount => 200)
|
121
|
+
# batch.addenda(:text => 'Bar')
|
122
|
+
# batch.addenda(:text => 'Baz')
|
123
|
+
#
|
124
|
+
# batch.entries # => [<Entry, amount=100>, <Entry, amount=200>]
|
125
|
+
# batch.addendas # => {<Entry, amount=100> => [<Addenda, text='Foo'>],
|
126
|
+
# # <Entry, amount=200> => [<Addenda, text='Bar'>, <Addenda, text='Baz'>]}
|
127
|
+
def container_for_associated
|
128
|
+
return container unless linked?
|
129
|
+
|
130
|
+
last_link = @owner.send(linked_to).last
|
131
|
+
raise NoLinkError.new(linked_to.to_s.singularize, klass.name) unless last_link
|
132
|
+
container[last_link] ||= []
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns +true+ if association is linked to another association (thus, it's records must
|
136
|
+
# be preceded by other association's records). Returns +false+ otherwise
|
137
|
+
def linked?
|
138
|
+
!!linked_to
|
139
|
+
end
|
140
|
+
private :linked?
|
141
|
+
|
142
|
+
# Returns +klass+ that corresponds to association name. Should be defined either in
|
143
|
+
# ACH module, or in ACH::Record module
|
144
|
+
def klass
|
145
|
+
@klass ||= ACH.to_const(@name.classify.to_sym)
|
146
|
+
end
|
147
|
+
private :klass
|
148
|
+
|
149
|
+
# Returns singular name of the association
|
150
|
+
def singular_name
|
151
|
+
@singular_name ||= name.singularize
|
152
|
+
end
|
153
|
+
private :singular_name
|
154
|
+
end
|
155
|
+
end
|
data/lib/ach/constants.rb
CHANGED
@@ -1,7 +1,21 @@
|
|
1
1
|
module ACH
|
2
2
|
module Constants
|
3
|
+
# The length of each record in characters.
|
3
4
|
RECORD_SIZE = 94
|
5
|
+
# The file's total record count must be a multiple of this number. The
|
6
|
+
# file must be padded with blocking file control records (consisting
|
7
|
+
# entirely of 9s) to satisfy this condition.
|
4
8
|
BLOCKING_FACTOR = 10
|
9
|
+
# Always "1".
|
5
10
|
FORMAT_CODE = 1
|
6
|
-
|
11
|
+
# This character must be used to delimit each row.
|
12
|
+
ROWS_DELIMITER = "\n"
|
13
|
+
|
14
|
+
FILE_HEADER_RECORD_TYPE = 1
|
15
|
+
FILE_CONTROL_RECORD_TYPE = 9
|
16
|
+
BATCH_HEADER_RECORD_TYPE = 5
|
17
|
+
BATCH_ENTRY_RECORD_TYPE = 6
|
18
|
+
BATCH_ADDENDA_RECORD_TYPE = 7
|
19
|
+
BATCH_CONTROL_RECORD_TYPE = 8
|
20
|
+
end
|
7
21
|
end
|
data/lib/ach/file.rb
CHANGED
@@ -1,49 +1,55 @@
|
|
1
1
|
module ACH
|
2
|
+
# An ACH::File instance represents an actual ACH file. Every file has an
|
3
|
+
# ACH::File::Header and ACH::File::Control records and a variable number of
|
4
|
+
# ACH::Batches. The ACH::File::TransmissionHeader is optional. (Refer to the
|
5
|
+
# target financial institution's documentation.)
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
#
|
9
|
+
# # Subclass ACH::File to set default values:
|
10
|
+
# class CustomAchFile < ACH::File
|
11
|
+
# immediate_dest '123123123'
|
12
|
+
# immediate_dest_name 'COMMERCE BANK'
|
13
|
+
# immediate_origin '123123123'
|
14
|
+
# immediate_origin_name 'MYCOMPANY'
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # Create a new instance:
|
18
|
+
# ach_file = CustomAchFile.new do
|
19
|
+
# batch(:entry_class_code => "WEB", :company_entry_descr => "TV-TELCOM") do
|
20
|
+
# effective_date Time.now.strftime('%y%m%d')
|
21
|
+
# desc_date Time.now.strftime('%b %d').upcase
|
22
|
+
# origin_dfi_id "00000000"
|
23
|
+
# entry :customer_name => 'JOHN SMITH',
|
24
|
+
# :customer_acct => '61242882282',
|
25
|
+
# :amount => '2501',
|
26
|
+
# :routing_number => '010010101',
|
27
|
+
# :bank_account => '103030030'
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # convert to string
|
32
|
+
# ach_file.to_s! # => returns string representation of file
|
33
|
+
#
|
34
|
+
# # write to file
|
35
|
+
# ach_file.write('custom_ach.txt')
|
2
36
|
class File < Component
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
def total_debit_amount
|
22
|
-
batches.map(&:total_debit_amount).compact.inject(&:+)
|
23
|
-
end
|
24
|
-
|
25
|
-
def total_credit_amount
|
26
|
-
batches.map(&:total_credit_amount).compact.inject(&:+)
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_ach
|
30
|
-
extra = block_count * BLOCKING_FACTOR - file_entry_count
|
31
|
-
tail = ([Tail.new] * extra).unshift(control)
|
32
|
-
[header] + batches.map(&:to_ach).flatten + tail
|
33
|
-
end
|
34
|
-
|
35
|
-
def to_s!
|
36
|
-
to_ach.map(&:to_s!).join("\r\n") + "\r\n"
|
37
|
-
end
|
38
|
-
|
39
|
-
def record_count
|
40
|
-
2 + batches.length * 2 + file_entry_count
|
41
|
-
end
|
42
|
-
|
43
|
-
def write filename
|
44
|
-
return false unless valid?
|
45
|
-
::File.open(filename, 'w') do |fh|
|
46
|
-
fh.write(to_s!)
|
37
|
+
autoload :Builder
|
38
|
+
autoload :Control
|
39
|
+
autoload :Header
|
40
|
+
autoload :TransmissionHeader
|
41
|
+
autoload :Reader
|
42
|
+
|
43
|
+
include Builder
|
44
|
+
include TransmissionHeader
|
45
|
+
|
46
|
+
has_many :batches, :proc_defaults => lambda{ {:batch_number => batches.length + 1} }
|
47
|
+
|
48
|
+
# Opens a +filename+ and passes it's handler to the ACH::Reader object, which uses it as
|
49
|
+
# enum to scan for ACH contents line by line.
|
50
|
+
def self.read(filename)
|
51
|
+
::File.open(filename) do |fh|
|
52
|
+
Reader.new(fh).to_ach
|
47
53
|
end
|
48
54
|
end
|
49
55
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module ACH
|
2
|
+
# This module hosts all the methods required for building string representation of an ACH file,
|
3
|
+
# and writing it to an actual file in the filesystem. Included by the ACH::File.
|
4
|
+
module File::Builder
|
5
|
+
# Returns amount of +batches+ in file
|
6
|
+
def batch_count
|
7
|
+
batches.length
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns amount of blocks, used in count. This amount is based on <tt>blocking factor</tt>,
|
11
|
+
# which is usually equals to 10, and on overall amount of records in a file. Return value
|
12
|
+
# represents the least amount of blocks taken by records in file.
|
13
|
+
def block_count
|
14
|
+
(record_count.to_f / Constants::BLOCKING_FACTOR).ceil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns total amount of +entry+ and +addenda+ records of all batches within file.
|
18
|
+
def file_entry_addenda_count
|
19
|
+
batches.map{ |batch| batch.entry_addenda_count }.inject(&:+) || 0
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns sum of +entry_hash+ values of all batches within self
|
23
|
+
def entry_hash
|
24
|
+
batch_sum_of(:entry_hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns sum of +total_debit_amount+ values of all batches within self
|
28
|
+
def total_debit_amount
|
29
|
+
batch_sum_of(:total_debit_amount)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns sum of +total_credit_amount+ values of all batches within self
|
33
|
+
def total_credit_amount
|
34
|
+
batch_sum_of(:total_credit_amount)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns complete string representation of a ACH file by converting each interval record
|
38
|
+
# to a string and joining the result by <tt>Constants::ROWS_DELIMITER</tt>
|
39
|
+
def to_s!
|
40
|
+
to_ach.map(&:to_s!).join(Constants::ROWS_DELIMITER)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns total amount of records hosted by a file.
|
44
|
+
def record_count
|
45
|
+
2 + batch_count * 2 + file_entry_addenda_count
|
46
|
+
end
|
47
|
+
|
48
|
+
# Writes string representation of self to passed +filename+
|
49
|
+
def write(filename)
|
50
|
+
return false unless valid?
|
51
|
+
::File.open(filename, 'w') do |fh|
|
52
|
+
fh.write(to_s!)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Helper method for calculating different properties of batches within file
|
57
|
+
def batch_sum_of(meth)
|
58
|
+
batches.map(&meth).compact.inject(&:+)
|
59
|
+
end
|
60
|
+
private :batch_sum_of
|
61
|
+
|
62
|
+
# Returns well-fetched array of all ACH records in the file, appending proper
|
63
|
+
# amount, based on number of blocks, of tail records to it.
|
64
|
+
def to_ach
|
65
|
+
head = [ header ]
|
66
|
+
head.unshift(transmission_header) if have_transmission_header?
|
67
|
+
head + batches.map(&:to_ach).flatten + [control] + tail
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns array of ACH::Record::Tail records, based on +tails_count+
|
71
|
+
def tail
|
72
|
+
[ Record::Tail.new ] * tails_count
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns amount of ACH::Record::Tail records, required to append to
|
76
|
+
# string representation of a file to match proper amount of blocks.
|
77
|
+
def tails_count
|
78
|
+
block_count * Constants::BLOCKING_FACTOR - record_count
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/ach/file/control.rb
CHANGED
@@ -1,15 +1,27 @@
|
|
1
1
|
module ACH
|
2
|
-
|
2
|
+
# Every ACH::File ends with an ACH::File::Control record.
|
3
|
+
#
|
4
|
+
# == Fields:
|
5
|
+
#
|
6
|
+
# * record_type
|
7
|
+
# * batch_count
|
8
|
+
# * block_count
|
9
|
+
# * file_entry_addenda_count
|
10
|
+
# * entry_hash
|
11
|
+
# * total_debit_amount
|
12
|
+
# * total_credit_amount
|
13
|
+
# * bank_39
|
14
|
+
class File::Control < Record::Base
|
3
15
|
fields :record_type,
|
4
16
|
:batch_count,
|
5
17
|
:block_count,
|
6
|
-
:
|
18
|
+
:file_entry_addenda_count,
|
7
19
|
:entry_hash,
|
8
20
|
:total_debit_amount,
|
9
21
|
:total_credit_amount,
|
10
22
|
:bank_39
|
11
23
|
|
12
|
-
defaults :record_type =>
|
24
|
+
defaults :record_type => FILE_CONTROL_RECORD_TYPE,
|
13
25
|
:bank_39 => ''
|
14
26
|
end
|
15
27
|
end
|
data/lib/ach/file/header.rb
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
module ACH
|
2
|
-
|
2
|
+
# An ACH::File::Header record is the first record of every ACH::File
|
3
|
+
# (in case the ACH::File::TransmissionHeader record is absent).
|
4
|
+
#
|
5
|
+
# == Fields:
|
6
|
+
#
|
7
|
+
# * record_type
|
8
|
+
# * priority_code
|
9
|
+
# * immediate_dest
|
10
|
+
# * immediate_origin
|
11
|
+
# * date
|
12
|
+
# * time
|
13
|
+
# * file_id_modifier
|
14
|
+
# * record_size
|
15
|
+
# * blocking_factor
|
16
|
+
# * format_code
|
17
|
+
# * immediate_dest_name
|
18
|
+
# * immediate_origin_name
|
19
|
+
# * reference_code
|
20
|
+
class File::Header < Record::Base
|
3
21
|
fields :record_type,
|
4
22
|
:priority_code,
|
5
23
|
:immediate_dest,
|
@@ -14,9 +32,8 @@ module ACH
|
|
14
32
|
:immediate_origin_name,
|
15
33
|
:reference_code
|
16
34
|
|
17
|
-
defaults :record_type =>
|
35
|
+
defaults :record_type => FILE_HEADER_RECORD_TYPE,
|
18
36
|
:priority_code => 1,
|
19
|
-
:reference_code => '',
|
20
37
|
:date => lambda{ Time.now.strftime("%y%m%d") },
|
21
38
|
:time => lambda{ Time.now.strftime("%H%M") },
|
22
39
|
:file_id_modifier => 'A',
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module ACH
|
2
|
+
# The +ACH::File::Reader+ class builds a corresponding +ACH::File+
|
3
|
+
# object from a given set of data. The constructor takes an +enum+ object
|
4
|
+
# representing a sequence of ACH lines (strings). This +enum+ object may be
|
5
|
+
# a file handler, an array, or any other object that responds to the +#each+
|
6
|
+
# method.
|
7
|
+
class File::Reader
|
8
|
+
include Constants
|
9
|
+
|
10
|
+
def initialize(enum)
|
11
|
+
@enum = enum
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_ach
|
15
|
+
header_line, batch_lines, control_line = ach_data
|
16
|
+
|
17
|
+
File.new do
|
18
|
+
build_header header_line
|
19
|
+
|
20
|
+
batch_lines.each do |batch_data|
|
21
|
+
batch do
|
22
|
+
build_header batch_data[:header]
|
23
|
+
|
24
|
+
batch_data[:entries].each do |entry_line|
|
25
|
+
build_entry entry_line
|
26
|
+
|
27
|
+
if batch_data[:addendas].key?(entry_line)
|
28
|
+
batch_data[:addendas][entry_line].each do |addenda_line|
|
29
|
+
build_addenda addenda_line
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
build_control batch_data[:control]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
build_control control_line
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ach_data
|
43
|
+
process! unless processed?
|
44
|
+
|
45
|
+
return @header, batches, @control
|
46
|
+
end
|
47
|
+
private :ach_data
|
48
|
+
|
49
|
+
def process!
|
50
|
+
each_line do |record_type, line|
|
51
|
+
case record_type
|
52
|
+
when FILE_HEADER_RECORD_TYPE
|
53
|
+
@header = line
|
54
|
+
when BATCH_HEADER_RECORD_TYPE
|
55
|
+
initialize_batch!
|
56
|
+
current_batch[:header] = line
|
57
|
+
when BATCH_ENTRY_RECORD_TYPE
|
58
|
+
current_batch[:entries] << line
|
59
|
+
when BATCH_ADDENDA_RECORD_TYPE
|
60
|
+
(current_batch[:addendas][current_entry] ||= []) << line
|
61
|
+
when BATCH_CONTROL_RECORD_TYPE
|
62
|
+
current_batch[:control] = line
|
63
|
+
when FILE_CONTROL_RECORD_TYPE
|
64
|
+
@control = line
|
65
|
+
end
|
66
|
+
end
|
67
|
+
@processed = true
|
68
|
+
end
|
69
|
+
private :process!
|
70
|
+
|
71
|
+
def processed?
|
72
|
+
!!@processed
|
73
|
+
end
|
74
|
+
private :processed?
|
75
|
+
|
76
|
+
def each_line
|
77
|
+
@enum.each do |line|
|
78
|
+
yield line[0..0].to_i, line.chomp
|
79
|
+
end
|
80
|
+
end
|
81
|
+
private :each_line
|
82
|
+
|
83
|
+
def batches
|
84
|
+
@batches ||= []
|
85
|
+
end
|
86
|
+
private :batches
|
87
|
+
|
88
|
+
def initialize_batch!
|
89
|
+
batches << {:entries => [], :addendas => {}}
|
90
|
+
end
|
91
|
+
private :initialize_batch!
|
92
|
+
|
93
|
+
def current_batch
|
94
|
+
batches.last
|
95
|
+
end
|
96
|
+
private :current_batch
|
97
|
+
|
98
|
+
def current_entry
|
99
|
+
current_batch[:entries].last
|
100
|
+
end
|
101
|
+
private :current_entry
|
102
|
+
end
|
103
|
+
end
|