ach_builder 0.0.2 → 0.2.1
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 +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
|