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,58 @@
|
|
1
|
+
module ACH
|
2
|
+
module Record
|
3
|
+
# Descendants of +ACH::Record::Dynamic+ class are the only
|
4
|
+
# records allowed to have previously undefined fields. However,
|
5
|
+
# each new field definition requires a rule string to be passed.
|
6
|
+
# +UndefinedFormatError+ will be raised otherwise. If the field
|
7
|
+
# has already been defined, and a new rule string is passed,
|
8
|
+
# a +DuplicateFormatError+ is raised.
|
9
|
+
#
|
10
|
+
# == Example
|
11
|
+
#
|
12
|
+
# class CustomHeader < ACH::Record::Dynamic
|
13
|
+
# request_type '<-9-' => '$$ADD ID='
|
14
|
+
# remote_id '<-8-'
|
15
|
+
# file_type '<-6-', 'NWFACH'
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# This example declares a +CustomHeader+ record type with following
|
19
|
+
# fields:
|
20
|
+
# * +request_type+, defined by rule '<-9-' with a default
|
21
|
+
# value of '$$ADD ID='
|
22
|
+
# * +remote_id+ with no default value
|
23
|
+
# * +file_type+ with default value of 'NWFACH'
|
24
|
+
#
|
25
|
+
# Note: passing two arguments to method call is equivalent to
|
26
|
+
# passing a hash with a single key-value pair.
|
27
|
+
class Dynamic < Base
|
28
|
+
class DuplicateFormatError < ArgumentError
|
29
|
+
def initialize(field_name)
|
30
|
+
super "Rule #{field_name} has already been defined"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class UndefinedFormatError < ArgumentError
|
35
|
+
def initialize(field_name)
|
36
|
+
super "Unknown field #{field_name} should be supplied by format"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.method_missing(field, *args)
|
41
|
+
format, default = args.first.is_a?(Hash) ? args.first.first : args
|
42
|
+
unless format =~ Formatter::Rule::RULE_PARSER_REGEX
|
43
|
+
default, format = format, nil
|
44
|
+
end
|
45
|
+
|
46
|
+
unless Formatter.defined? field
|
47
|
+
raise UndefinedFormatError.new(field) if format.nil?
|
48
|
+
Formatter.define field, format
|
49
|
+
else
|
50
|
+
raise DuplicateFormatError.new(field) if format
|
51
|
+
end
|
52
|
+
define_field_methods(field)
|
53
|
+
(@fields ||= []) << field
|
54
|
+
(@defaults ||= {})[field] = default if default
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ACH
|
2
|
+
module Record
|
3
|
+
# A subclass of ACH::Record::Base, an Entry appears in an ACH::Batch
|
4
|
+
# component. It is the main record for representing a particular
|
5
|
+
# transaction.
|
6
|
+
#
|
7
|
+
# == Fields
|
8
|
+
#
|
9
|
+
# * record_type
|
10
|
+
# * transaction_code
|
11
|
+
# * routing_number
|
12
|
+
# * bank_account
|
13
|
+
# * amount
|
14
|
+
# * customer_acct
|
15
|
+
# * customer_name
|
16
|
+
# * transaction_type
|
17
|
+
# * addenda
|
18
|
+
# * bank_15
|
19
|
+
class Entry < Base
|
20
|
+
CREDIT_TRANSACTION_CODE_ENDING_DIGITS = ('0'..'4').to_a.freeze
|
21
|
+
|
22
|
+
fields :record_type,
|
23
|
+
:transaction_code,
|
24
|
+
:routing_number,
|
25
|
+
:bank_account,
|
26
|
+
:amount,
|
27
|
+
:customer_acct,
|
28
|
+
:customer_name,
|
29
|
+
:transaction_type,
|
30
|
+
:addenda,
|
31
|
+
:bank_15
|
32
|
+
|
33
|
+
defaults :record_type => BATCH_ENTRY_RECORD_TYPE,
|
34
|
+
:transaction_code => 27,
|
35
|
+
:transaction_type => 'S',
|
36
|
+
:customer_acct => '',
|
37
|
+
:addenda => 0,
|
38
|
+
:bank_15 => ''
|
39
|
+
|
40
|
+
def debit?
|
41
|
+
!credit?
|
42
|
+
end
|
43
|
+
|
44
|
+
def credit?
|
45
|
+
CREDIT_TRANSACTION_CODE_ENDING_DIGITS.include? transaction_code.to_s[1..1]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ACH
|
2
|
+
module Record
|
3
|
+
# Since a number of records in ACH file must be multiple of 10, tail records
|
4
|
+
# are used to populate empty records at the end of the file with "9" characters.
|
5
|
+
class Tail < Base
|
6
|
+
fields :nines
|
7
|
+
defaults :nines => '9' * RECORD_SIZE
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/ach/validations.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module ACH
|
2
|
+
# This module hosts the most basic validations for both +components+ and
|
3
|
+
# +records+. The only validation being performed is presence validation.
|
2
4
|
module Validations
|
3
5
|
def valid?
|
4
6
|
reset_errors!
|
@@ -7,11 +9,11 @@ module ACH
|
|
7
9
|
end
|
8
10
|
|
9
11
|
def valid_component?
|
10
|
-
counts =
|
12
|
+
counts = Hash.new(0)
|
11
13
|
to_ach.each do |record|
|
12
|
-
counts[record.class] ||= 0
|
13
14
|
unless record.valid?
|
14
|
-
|
15
|
+
klass = record.class
|
16
|
+
errors["#{klass}##{counts[klass] += 1}"] = record.errors
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
data/lib/ach/version.rb
CHANGED
data/lib/ach_builder.rb
CHANGED
@@ -1,41 +1,28 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/dependencies/autoload'
|
1
3
|
require 'active_support/inflector'
|
2
4
|
require 'active_support/ordered_hash'
|
5
|
+
require 'active_support/core_ext/module/delegation'
|
6
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
7
|
+
require 'active_support/core_ext/class/attribute'
|
3
8
|
|
4
9
|
require "ach/version"
|
5
10
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
require 'ach/component'
|
10
|
-
require 'ach/record'
|
11
|
-
require 'ach/entry'
|
12
|
-
require 'ach/tail'
|
13
|
-
require 'ach/batch'
|
14
|
-
require 'ach/batch/header'
|
15
|
-
require 'ach/batch/control'
|
16
|
-
require 'ach/file'
|
17
|
-
require 'ach/file/header'
|
18
|
-
require 'ach/file/control'
|
19
|
-
|
11
|
+
# Support for building the files necessary for the bulk exchange of debits and
|
12
|
+
# credits with financial institutions via the Automated Clearing House system,
|
13
|
+
# governed by NACHA ( http://www.nacha.org/ ).
|
20
14
|
module ACH
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
:customer_acct => '61242882282',
|
34
|
-
:amount => '2501',
|
35
|
-
:routing_number => '010010101',
|
36
|
-
:bank_account => '103030030'
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
15
|
+
extend ActiveSupport::Autoload
|
16
|
+
|
17
|
+
autoload :Constants
|
18
|
+
autoload :Formatter
|
19
|
+
autoload :Validations
|
20
|
+
autoload :Component
|
21
|
+
autoload :Record
|
22
|
+
autoload :Batch
|
23
|
+
autoload :File
|
24
|
+
|
25
|
+
def self.to_const(name)
|
26
|
+
[self, self::Record].detect{ |mod| mod.const_defined?(name) }.const_get(name)
|
40
27
|
end
|
41
28
|
end
|
data/spec/batch_spec.rb
CHANGED
@@ -3,22 +3,36 @@ require 'spec_helper'
|
|
3
3
|
describe ACH::Batch do
|
4
4
|
before(:each) do
|
5
5
|
@batch = ACH::Batch.new
|
6
|
-
@file = ACH.sample_file
|
6
|
+
@file = ACH::FileFactory.sample_file
|
7
7
|
end
|
8
8
|
|
9
|
-
it "should create entry with attributes" do
|
9
|
+
it "should create entry with attributes in hash form" do
|
10
10
|
entry = @batch.entry :amount => 100
|
11
|
-
entry.should be_instance_of(ACH::Entry)
|
11
|
+
entry.should be_instance_of(ACH::Record::Entry)
|
12
12
|
entry.amount.should == 100
|
13
13
|
end
|
14
14
|
|
15
|
-
it "should create entry with attributes" do
|
15
|
+
it "should create entry with attributes in block form" do
|
16
16
|
entry = @batch.entry do
|
17
17
|
amount 100
|
18
18
|
end
|
19
19
|
entry.amount.should == 100
|
20
20
|
end
|
21
21
|
|
22
|
+
it "should raise error when adding addenda records without any entry" do
|
23
|
+
batch = ACH::Batch.new
|
24
|
+
expect{ batch.addenda(:payment_related_info =>'foo bar') }.to raise_error(ACH::Component::HasManyAssociation::NoLinkError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should append addenda records after entry records" do
|
28
|
+
batch = ACH::Batch.new
|
29
|
+
3.times do |i|
|
30
|
+
batch.entry(:amount => 100)
|
31
|
+
i.times{ batch.addenda(:payment_related_info => 'foo bar') }
|
32
|
+
end
|
33
|
+
batch.to_ach.map(&:class)[1...-1].should == [ACH::Record::Entry, ACH::Record::Entry, ACH::Record::Addenda, ACH::Record::Entry, ACH::Record::Addenda, ACH::Record::Addenda]
|
34
|
+
end
|
35
|
+
|
22
36
|
it "should return false for has_credit? and has_debit? for empty entries" do
|
23
37
|
@batch.has_credit?.should be_false
|
24
38
|
@batch.has_debit?.should be_false
|
@@ -50,12 +64,10 @@ describe ACH::Batch do
|
|
50
64
|
@batch.header.service_class_code.should == 200
|
51
65
|
end
|
52
66
|
|
53
|
-
it "should have header record with length of 94" do
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
it "should have control record with length of 94" do
|
58
|
-
@file.batch(0).send(:before_header) # to fill service_class_code value
|
59
|
-
@file.batch(0).control.to_s!.length.should == ACH::Constants::RECORD_SIZE
|
67
|
+
it "should have header and control record with length of 94" do
|
68
|
+
[:header, :control].each do |record|
|
69
|
+
@file.batches[0].send(record).to_s!.length.should == ACH::Constants::RECORD_SIZE
|
70
|
+
end
|
60
71
|
end
|
72
|
+
|
61
73
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ACH::Component::HasManyAssociation do
|
4
|
+
before(:all) do
|
5
|
+
ACH::Element = Class.new(ACH::Component)
|
6
|
+
ACH::Record::Item = Class.new(ACH::Record::Dynamic) do
|
7
|
+
item_id '<-3' => 'foo'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:all) do
|
12
|
+
ACH.remove_const(:Element)
|
13
|
+
ACH::Record.remove_const(:Item)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "properties" do
|
17
|
+
before(:each) do
|
18
|
+
@association = ACH::Component::HasManyAssociation.new(:elements)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have proper klass" do
|
22
|
+
@association.send(:klass).should == ACH::Element
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should have proper singular name" do
|
26
|
+
@association.send(:singular_name).should == 'element'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should have proper set of delegation methods" do
|
30
|
+
@association.delegation_methods.to_set.should == %w{elements element build_element}.to_set
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "owner assignment" do
|
34
|
+
before(:each) do
|
35
|
+
@assigned = @association.for(ACH::Element.new)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should have @owner assigned" do
|
39
|
+
@assigned.instance_variable_get('@owner').should_not be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should not share containers" do
|
43
|
+
@other = @association.for(ACH::Element.new)
|
44
|
+
@assigned.container.object_id.should_not == @other.container.object_id
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should have method aliases for owner to delegate" do
|
48
|
+
@association.delegation_methods.each do |method_name|
|
49
|
+
@assigned.should respond_to method_name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "reassignment" do
|
55
|
+
it "should raise error" do
|
56
|
+
assigned = @association.for(Object.new)
|
57
|
+
expect{ assigned.for(Object.new) }.to raise_error(ACH::Component::HasManyAssociation::DoubleAssignmentError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "without :linked_to option" do
|
62
|
+
it "should have Array as container" do
|
63
|
+
@association.container.should be_an Array
|
64
|
+
end
|
65
|
+
|
66
|
+
it "container_for_associated should be the same as container" do
|
67
|
+
@association.container_for_associated.object_id.should == @association.container.object_id
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "with :linked_to option" do
|
72
|
+
before(:each) do
|
73
|
+
@linked_association = ACH::Component::HasManyAssociation.new(:items, :linked_to => :elements)
|
74
|
+
@assigned = @association.for(ACH::Element.new)
|
75
|
+
@linked_assigned = @linked_association.for(@assigned)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should have proper klass" do
|
79
|
+
@linked_association.send(:klass).should == ACH::Record::Item
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should have Hash as container" do
|
83
|
+
@linked_association.container.should be_a Hash
|
84
|
+
end
|
85
|
+
|
86
|
+
it "container_for_associated should raise NoLinkError if there is no parent record created" do
|
87
|
+
expect {
|
88
|
+
@linked_assigned.container_for_associated
|
89
|
+
}.to raise_error(ACH::Component::HasManyAssociation::NoLinkError)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "container_for_associated should be array if there is parent record" do
|
93
|
+
@assigned.create
|
94
|
+
@linked_assigned.container_for_associated.should be_an Array
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "with :proc_defaults option" do
|
99
|
+
before(:each) do
|
100
|
+
@proc_association = ACH::Component::HasManyAssociation.new(
|
101
|
+
:items, :proc_defaults => lambda{ {:item_id => 'bar'} }
|
102
|
+
).for(ACH::Element.new)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "created item should have proper value" do
|
106
|
+
item = @proc_association.create
|
107
|
+
item.item_id.should == 'bar'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/spec/file_spec.rb
CHANGED
@@ -1,90 +1,182 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ACH::File do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
it "should correctly assign attributes" do
|
21
|
-
@file.company_id.should == '11-11111'
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should be modified by calling attribute methods in block" do
|
25
|
-
file = ACH::File.new(@attributes) do
|
26
|
-
company_name "MINE COMPANY"
|
4
|
+
|
5
|
+
context "building the ACH file" do
|
6
|
+
before(:each) do
|
7
|
+
@attributes = {
|
8
|
+
:company_id => '11-11111',
|
9
|
+
:company_name => 'MY COMPANY',
|
10
|
+
:immediate_dest => '123123123',
|
11
|
+
:immediate_dest_name => 'COMMERCE BANK',
|
12
|
+
:immediate_origin => '123123123',
|
13
|
+
:immediate_origin_name => 'MYCOMPANY' }
|
14
|
+
@invalid_attributes = {:foo => 'bar'}
|
15
|
+
@file = ACH::File.new(@attributes)
|
16
|
+
@file_with_batch = ACH::File.new(@attributes) do
|
17
|
+
batch :entry_class_code => 'WEB'
|
18
|
+
end
|
19
|
+
@sample_file = ACH::FileFactory.sample_file
|
27
20
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
it "should be able to modify header info in block form" do
|
38
|
-
file = ACH::File.new(@attributes) do
|
39
|
-
header(:immediate_dest => '321321321') do
|
40
|
-
immediate_dest_name 'BANK COMMERCE'
|
21
|
+
|
22
|
+
it "should correctly assign attributes" do
|
23
|
+
@file.company_id.should == '11-11111'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be modified by calling attribute methods in block" do
|
27
|
+
file = ACH::File.new(@attributes) do
|
28
|
+
company_name "MINE COMPANY"
|
41
29
|
end
|
30
|
+
file.company_name.should == "MINE COMPANY"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should fetch and return header" do
|
34
|
+
head = @file.header
|
35
|
+
head.should be_instance_of(ACH::File::Header)
|
36
|
+
head.immediate_dest.should == '123123123'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be able to modify header info in block form" do
|
40
|
+
file = ACH::File.new(@attributes) do
|
41
|
+
header(:immediate_dest => '321321321') do
|
42
|
+
immediate_dest_name 'BANK COMMERCE'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
head = file.header
|
46
|
+
head.immediate_dest.should == '321321321'
|
47
|
+
head.immediate_dest_name.should == 'BANK COMMERCE'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should raise exception on unknown attribute assignement" do
|
51
|
+
lambda {
|
52
|
+
ACH::File.new(@invalid_attributes)
|
53
|
+
}.should raise_error(ACH::Component::UnknownAttributeError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should be able to create a batch" do
|
57
|
+
@file_with_batch.batches.should_not be_empty
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return a batch when index is passed" do
|
61
|
+
@file_with_batch.batches[0].should be_instance_of(ACH::Batch)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should assign a batch_number to a batch" do
|
65
|
+
batch = @file_with_batch.batches[0]
|
66
|
+
batch.batch_number.should == 1
|
67
|
+
batch = @file_with_batch.batch(:entry_class_code => 'WEB')
|
68
|
+
batch.batch_number.should == 2
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should assign attributes to a batch" do
|
72
|
+
batch = @file_with_batch.batches[0]
|
73
|
+
batch.attributes.should include(@file_with_batch.attributes)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should have correct record count" do
|
77
|
+
@sample_file.record_count.should == 10
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should have header and control record with length of 94" do
|
81
|
+
[:header, :control].each do |record|
|
82
|
+
@sample_file.send(record).to_s!.length.should == ACH::Constants::RECORD_SIZE
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should have length devisible by 94 (record size)" do
|
87
|
+
(@sample_file.to_s!.gsub(ACH::Constants::ROWS_DELIMITER, '').length % ACH::Constants::RECORD_SIZE).should be_zero
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
describe 'transmission header' do
|
92
|
+
before(:all) do
|
93
|
+
@with_transmission_header
|
94
|
+
attrs = {:remote_id => 'ZYXWVUTS', :application_id => '98765432'}
|
95
|
+
ach_file = ACH::FileFactory.with_transmission_header(attrs)
|
96
|
+
@transmission_header = ach_file.to_s!.split(ACH::Constants::ROWS_DELIMITER).first
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should raise error when defining empty transmission header" do
|
100
|
+
expect do
|
101
|
+
Class.new(ACH::File) do
|
102
|
+
transmission_header
|
103
|
+
end
|
104
|
+
end.to raise_error(ACH::File::EmptyTransmissionHeaderError)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "have_transmission_header? method should return proper value" do
|
108
|
+
without_header = Class.new(ACH::File)
|
109
|
+
with_header = Class.new(ACH::File) do
|
110
|
+
transmission_header do
|
111
|
+
application_id
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
without_header.have_transmission_header?.should be_false
|
116
|
+
with_header.have_transmission_header?.should be_true
|
117
|
+
end
|
118
|
+
|
119
|
+
it "has length of 38" do
|
120
|
+
@transmission_header.length.should == 38
|
121
|
+
end
|
122
|
+
|
123
|
+
it "has specified remote_id" do
|
124
|
+
@transmission_header[9..16].should == 'ZYXWVUTS'
|
125
|
+
end
|
126
|
+
|
127
|
+
it "has specified application_id" do
|
128
|
+
@transmission_header[29..36].should == '98765432'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'number of records is multiple of 10 (transmission header is ignored)' do
|
133
|
+
records = ACH::FileFactory.sample_file.to_s!.split(ACH::Constants::ROWS_DELIMITER)
|
134
|
+
(records.size % 10).should == 0
|
135
|
+
|
136
|
+
records = ACH::FileFactory.with_transmission_header.to_s!.split(ACH::Constants::ROWS_DELIMITER)
|
137
|
+
((records.size - 1) % 10).should == 0
|
138
|
+
end
|
139
|
+
|
140
|
+
describe 'inherited class' do
|
141
|
+
before(:all) do
|
142
|
+
@custom_file_class = Class.new(ACH::File) do
|
143
|
+
immediate_dest_name 'CUSTOM VALUE'
|
144
|
+
customer_name "PETER PARKER"
|
145
|
+
end
|
146
|
+
|
147
|
+
@custom_file = @custom_file_class.new do
|
148
|
+
immediate_dest '123123123'
|
149
|
+
immediate_origin '123123123'
|
150
|
+
immediate_origin_name 'MYCOMPANY'
|
151
|
+
batch(:entry_class_code => "WEB", :company_entry_descr => 'TV-TELCOM') do
|
152
|
+
effective_date Time.now.strftime('%y%m%d')
|
153
|
+
desc_date Time.now.strftime('%b %d').upcase
|
154
|
+
origin_dfi_id "00000000"
|
155
|
+
entry :customer_acct => '61242882282',
|
156
|
+
:amount => '2501',
|
157
|
+
:routing_number => '010010101',
|
158
|
+
:bank_account => '103030030'
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should use default values defined in inherited class' do
|
164
|
+
header = @custom_file.header
|
165
|
+
header.immediate_dest_name.should == "CUSTOM VALUE"
|
166
|
+
entry = @custom_file.batches.first.entries.first
|
167
|
+
entry.customer_name.should == "PETER PARKER"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "file control" do
|
172
|
+
subject { @file.control }
|
173
|
+
it { should be_an ACH::File::Control }
|
42
174
|
end
|
43
|
-
head = file.header
|
44
|
-
head.immediate_dest.should == '321321321'
|
45
|
-
head.immediate_dest_name.should == 'BANK COMMERCE'
|
46
|
-
end
|
47
|
-
|
48
|
-
it "should raise exception on unknown attribute assignement" do
|
49
|
-
lambda {
|
50
|
-
ACH::File.new(@invalid_attributes)
|
51
|
-
}.should raise_error(ACH::Component::UnknownAttribute)
|
52
|
-
end
|
53
|
-
|
54
|
-
it "should be able to create a batch" do
|
55
|
-
@file_with_batch.batches.should_not be_empty
|
56
|
-
end
|
57
|
-
|
58
|
-
it "should return a batch when index is passed" do
|
59
|
-
@file_with_batch.batch(0).should be_instance_of(ACH::Batch)
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should assign a batch_number to a batch" do
|
63
|
-
batch = @file_with_batch.batch(0)
|
64
|
-
batch.batch_number.should == 1
|
65
|
-
batch = @file_with_batch.batch(:entry_class_code => 'WEB')
|
66
|
-
batch.batch_number.should == 2
|
67
|
-
end
|
68
|
-
|
69
|
-
it "should assign attributes to a batch" do
|
70
|
-
batch = @file_with_batch.batch(0)
|
71
|
-
batch.attributes.should include(@file_with_batch.attributes)
|
72
|
-
end
|
73
|
-
|
74
|
-
it "should have correct record count" do
|
75
|
-
@sample_file.record_count.should == 8
|
76
|
-
end
|
77
|
-
|
78
|
-
it "should have header record with length of 94" do
|
79
|
-
@sample_file.header.to_s!.length.should == ACH::Constants::RECORD_SIZE
|
80
|
-
end
|
81
|
-
|
82
|
-
it "should have control record with length of 94" do
|
83
|
-
@sample_file.control.to_s!.length.should == ACH::Constants::RECORD_SIZE
|
84
175
|
end
|
85
|
-
|
86
|
-
|
87
|
-
(@
|
176
|
+
|
177
|
+
context "reading the ACH file" do
|
178
|
+
before(:each) { @result = ACH::File.read well_fargo_empty_filename }
|
179
|
+
subject { @result }
|
180
|
+
it { should be_an ACH::File }
|
88
181
|
end
|
89
182
|
end
|
90
|
-
|