ach 0.4.0 → 0.4.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.
- data/README.md +2 -1
- data/VERSION +1 -1
- data/ach.gemspec +2 -2
- data/examples/ach/batch_example.rb +11 -11
- data/examples/ach/fixtures/return_noc.txt +2 -2
- data/examples/ach/parse_example.rb +15 -0
- data/examples/ach/records/batch_control_example.rb +2 -2
- data/lib/ach/ach_file.rb +32 -23
- data/lib/ach/records/addendum.rb +47 -3
- data/lib/ach/records/batch_control.rb +9 -8
- data/lib/ach/records/batch_header.rb +8 -6
- metadata +2 -2
data/README.md
CHANGED
@@ -4,7 +4,7 @@ ach is a Ruby helper for builder ACH files. In particular, it helps with field
|
|
4
4
|
order and alignment, and adds padding lines to end of file.
|
5
5
|
|
6
6
|
**This library has only been used in one production application and for very
|
7
|
-
limited purposes. Please test thoroughly before using in a production
|
7
|
+
limited purposes. Please test thoroughly before using in a production
|
8
8
|
environment.**
|
9
9
|
|
10
10
|
See [ACH::Builder](http://search.cpan.org/~tkeefer/ACH-Builder-0.03/lib/ACH/Builder.pm)
|
@@ -31,6 +31,7 @@ fh.immediate_origin_name = "BANK NAME"
|
|
31
31
|
batch = ACH::Batch.new
|
32
32
|
bh = batch.header
|
33
33
|
bh.company_name = "Company Name"
|
34
|
+
bh.company_identification_code_designator = "1"
|
34
35
|
bh.company_identification = "123456789"
|
35
36
|
bh.standard_entry_class_code = 'PPD'
|
36
37
|
bh.company_entry_description = "DESCRIPTION"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.1
|
data/ach.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "ach"
|
8
|
-
s.version = "0.4.
|
8
|
+
s.version = "0.4.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jared Morgan", "Josh Puetz"]
|
12
|
-
s.date = "2012-
|
12
|
+
s.date = "2012-12-10"
|
13
13
|
s.description = "ach is a Ruby helper for builder ACH files. In particular, it helps with field\norder and alignment, and adds padding lines to end of file.\n"
|
14
14
|
s.email = "jmorgan@morgancreative.net"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -3,7 +3,7 @@ require 'example_helper'
|
|
3
3
|
describe ACH::Batch do
|
4
4
|
before(:each) do
|
5
5
|
@batch = ACH::Batch.new
|
6
|
-
|
6
|
+
|
7
7
|
@credit = ACH::EntryDetail.new
|
8
8
|
@credit.transaction_code = ACH::CHECKING_CREDIT
|
9
9
|
@credit.routing_number = "000000000"
|
@@ -12,11 +12,11 @@ describe ACH::Batch do
|
|
12
12
|
@credit.individual_id_number = "Employee Name"
|
13
13
|
@credit.individual_name = "Employee Name"
|
14
14
|
@credit.originating_dfi_identification = '00000000'
|
15
|
-
|
15
|
+
|
16
16
|
@debit = @credit.dup
|
17
17
|
@debit.transaction_code = ACH::CHECKING_DEBIT
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def new_batch
|
21
21
|
batch = ACH::Batch.new
|
22
22
|
bh = batch.header
|
@@ -37,47 +37,47 @@ describe ACH::Batch do
|
|
37
37
|
debits.header.service_class_code.should be_nil
|
38
38
|
debits.to_ach
|
39
39
|
debits.header.service_class_code.should == 225
|
40
|
-
|
40
|
+
|
41
41
|
credits = new_batch
|
42
42
|
credits.entries << @credit << @credit
|
43
43
|
credits.header.service_class_code.should be_nil
|
44
44
|
credits.to_ach
|
45
45
|
credits.header.service_class_code.should == 220
|
46
|
-
|
46
|
+
|
47
47
|
both = new_batch
|
48
48
|
both.entries << @credit << @debit
|
49
49
|
both.header.service_class_code.should be_nil
|
50
50
|
both.to_ach
|
51
51
|
both.header.service_class_code.should == 200
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
it 'should not override BatchHeader#service_class_code if already set' do
|
55
55
|
debits = new_batch
|
56
56
|
debits.header.service_class_code = 200
|
57
57
|
debits.entries << @debit << @debit
|
58
58
|
debits.to_ach
|
59
59
|
debits.header.service_class_code.should == 200
|
60
|
-
|
60
|
+
|
61
61
|
debits = new_batch
|
62
62
|
debits.header.service_class_code = '220'
|
63
63
|
debits.entries << @credit << @credit
|
64
64
|
debits.to_ach
|
65
65
|
debits.header.service_class_code.should == '220'
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
it 'should set BatchControl#service_class_code from BatchHeader if not set' do
|
69
69
|
batch = new_batch
|
70
70
|
batch.header.service_class_code = 200
|
71
71
|
batch.control.service_class_code.should be_nil
|
72
72
|
batch.to_ach
|
73
73
|
batch.control.service_class_code.should == 200
|
74
|
-
|
74
|
+
|
75
75
|
batch = new_batch
|
76
76
|
batch.header.service_class_code = '225'
|
77
77
|
batch.control.service_class_code.should be_nil
|
78
78
|
batch.to_ach
|
79
79
|
batch.control.service_class_code.should == '225'
|
80
|
-
|
80
|
+
|
81
81
|
debits = new_batch
|
82
82
|
debits.entries << @debit << @debit
|
83
83
|
debits.header.service_class_code.should be_nil
|
@@ -85,7 +85,7 @@ describe ACH::Batch do
|
|
85
85
|
debits.to_ach
|
86
86
|
debits.header.service_class_code.should == 225
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
it 'should not override BatchHeader#service_class_code if already set' do
|
90
90
|
# Granted that I can't imagine this every being used...
|
91
91
|
batch = new_batch
|
@@ -5,9 +5,9 @@
|
|
5
5
|
820000000200121140390000000000000000000000001412345678 992222220000001
|
6
6
|
5200COMPANY INC DISC DATA 1412345678PPDDESCRIPT 1210151210150001992222220000002
|
7
7
|
6261211403993300911569 0000002536GKGQT9VK NAME TWO 1121140390280738
|
8
|
-
799R07992222220280393
|
8
|
+
799R07992222220280393 12114039INVALID 121140390280738
|
9
9
|
820000000200121140390000000025360000000000001412345678 992222220000002
|
10
|
-
5200COMPANY INC DISC DATA 1412345678PPDDESCRIPT
|
10
|
+
5200COMPANY INC DISC DATA 1412345678PPDDESCRIPT 1210150001992222220000003
|
11
11
|
6261211403993300911569 0000002417KYU341VP NAME THREE 1121140390280729
|
12
12
|
799R03992222220280389 12114039 121140390280729
|
13
13
|
820000000200121140390000000024170000000000001412345678 992222220000003
|
@@ -37,12 +37,27 @@ describe "Parse" do
|
|
37
37
|
ed.individual_id_number.should == "A38LTNY2"
|
38
38
|
ed.individual_name.should == "NAME ONE"
|
39
39
|
|
40
|
+
ed.addenda.size.should == 1
|
41
|
+
ad = ed.addenda[0]
|
42
|
+
ad.type_code.should == '98'
|
43
|
+
ad.reason_code.should == 'C05'
|
44
|
+
ad.original_entry_trace_number.should == '992222220280489'
|
45
|
+
ad.corrected_data.should == '32'
|
46
|
+
ad.sequence_number.should == 4039
|
47
|
+
|
40
48
|
batch = ach.batches[1]
|
41
49
|
batch.entries.size.should == 1
|
42
50
|
bh = batch.header
|
43
51
|
bh.standard_entry_class_code.should == 'PPD'
|
44
52
|
ed = batch.entries[0]
|
45
53
|
ed.amount.should == 2536
|
54
|
+
|
55
|
+
ed.addenda.size.should == 1
|
56
|
+
ad = ed.addenda[0]
|
57
|
+
ad.type_code.should == '99'
|
58
|
+
ad.reason_code.should == 'R07'
|
59
|
+
ad.original_entry_trace_number.should == '992222220280393'
|
60
|
+
ad.addenda_information.should == 'INVALID'
|
46
61
|
end
|
47
62
|
|
48
63
|
end
|
@@ -13,9 +13,9 @@ describe ACH::Records::BatchControl do
|
|
13
13
|
@record.originating_dfi_identification = '32345678'
|
14
14
|
@record.batch_number = 5
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
self.instance_eval(&SharedExamples.batch_summaries)
|
18
|
-
|
18
|
+
|
19
19
|
describe '#to_ach' do
|
20
20
|
it 'should generate record string' do
|
21
21
|
exp = [
|
data/lib/ach/ach_file.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
|
-
module ACH
|
3
|
+
module ACH
|
4
4
|
class ACHFile
|
5
5
|
include FieldIdentifiers
|
6
|
-
|
6
|
+
|
7
7
|
attr_reader :batches
|
8
8
|
attr_reader :header
|
9
9
|
attr_reader :control
|
10
|
-
|
10
|
+
|
11
11
|
def initialize data=nil
|
12
12
|
@batches = []
|
13
13
|
@header = Records::FileHeader.new
|
@@ -17,52 +17,52 @@ module ACH
|
|
17
17
|
parse(data)
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def to_s
|
22
22
|
records = []
|
23
23
|
records << @header
|
24
24
|
@batches.each { |b| records += b.to_ach }
|
25
25
|
records << @control
|
26
|
-
|
26
|
+
|
27
27
|
nines_needed = 10 - (records.length % 10)
|
28
28
|
nines_needed = nines_needed % 10
|
29
29
|
nines_needed.times { records << Records::Nines.new() }
|
30
|
-
|
30
|
+
|
31
31
|
@control.batch_count = @batches.length
|
32
32
|
@control.block_count = (records.length / 10).ceil
|
33
|
-
|
33
|
+
|
34
34
|
@control.entry_count = 0
|
35
35
|
@control.debit_total = 0
|
36
36
|
@control.credit_total = 0
|
37
37
|
@control.entry_hash = 0
|
38
|
-
|
38
|
+
|
39
39
|
@batches.each do | batch |
|
40
40
|
@control.entry_count += batch.entries.length
|
41
41
|
@control.debit_total += batch.control.debit_total
|
42
42
|
@control.credit_total += batch.control.credit_total
|
43
43
|
@control.entry_hash += batch.control.entry_hash
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
|
47
47
|
records.collect { |r| r.to_ach }.join("\r\n") + "\r\n"
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def report
|
51
51
|
to_s # To ensure correct records
|
52
52
|
lines = []
|
53
|
-
|
53
|
+
|
54
54
|
@batches.each do | batch |
|
55
55
|
batch.entries.each do | entry |
|
56
56
|
lines << left_justify(entry.individual_name + ": ", 25) +
|
57
57
|
sprintf("% 7d.%02d", entry.amount / 100, entry.amount % 100)
|
58
|
-
end
|
58
|
+
end
|
59
59
|
end
|
60
60
|
lines << ""
|
61
61
|
lines << left_justify("Debit Total: ", 25) +
|
62
62
|
sprintf("% 7d.%02d", @control.debit_total / 100, @control.debit_total % 100)
|
63
63
|
lines << left_justify("Credit Total: ", 25) +
|
64
64
|
sprintf("% 7d.%02d", @control.credit_total / 100, @control.credit_total % 100)
|
65
|
-
|
65
|
+
|
66
66
|
lines.join("\r\n")
|
67
67
|
end
|
68
68
|
|
@@ -74,8 +74,9 @@ module ACH
|
|
74
74
|
ed = nil
|
75
75
|
|
76
76
|
data.strip.split(/\n|\r\n/).each do |line|
|
77
|
-
type = line[0
|
78
|
-
|
77
|
+
type = line[0]
|
78
|
+
case type
|
79
|
+
when '1'
|
79
80
|
fh.immediate_destination = line[03..12].strip
|
80
81
|
fh.immediate_origin = line[13..22].strip
|
81
82
|
fh.transmission_datetime = Time.utc('20'+line[23..24], line[25..26], line[27..28], line[29..30], line[31..32])
|
@@ -83,7 +84,7 @@ module ACH
|
|
83
84
|
fh.immediate_destination_name = line[40..62].strip
|
84
85
|
fh.immediate_origin_name = line[63..85].strip
|
85
86
|
fh.reference_code = line[86..93].strip
|
86
|
-
|
87
|
+
when '5'
|
87
88
|
self.batches << batch unless batch.nil?
|
88
89
|
batch = ACH::Batch.new
|
89
90
|
bh = batch.header
|
@@ -91,10 +92,10 @@ module ACH
|
|
91
92
|
bh.company_identification = line[41..49].strip
|
92
93
|
bh.standard_entry_class_code = line[50..52].strip
|
93
94
|
bh.company_entry_description = line[53..62].strip
|
94
|
-
bh.company_descriptive_date = Date.parse(line[63..68])
|
95
|
+
bh.company_descriptive_date = Date.parse(line[63..68]) rescue nil # this can be various formats
|
95
96
|
bh.effective_entry_date = Date.parse(line[69..74])
|
96
97
|
bh.originating_dfi_identification = line[79..86].strip
|
97
|
-
|
98
|
+
when '6'
|
98
99
|
ed = ACH::CtxEntryDetail.new
|
99
100
|
ed.transaction_code = line[1..2]
|
100
101
|
ed.routing_number = line[3..11]
|
@@ -105,16 +106,24 @@ module ACH
|
|
105
106
|
ed.originating_dfi_identification = line[79..86]
|
106
107
|
ed.trace_number = line[87..93].to_i
|
107
108
|
batch.entries << ed
|
108
|
-
|
109
|
-
|
110
|
-
ad
|
109
|
+
when '7'
|
110
|
+
type_code = line[1..2]
|
111
|
+
ad = case type_code
|
112
|
+
when '98'
|
113
|
+
ACH::Addendum::NotificationOfChange.new
|
114
|
+
when '99'
|
115
|
+
ACH::Addendum::Return.new
|
116
|
+
else
|
117
|
+
ACH::Addendum.new
|
118
|
+
end
|
119
|
+
ad.type_code = type_code
|
111
120
|
ad.payment_data = line[3..82].strip
|
112
121
|
ad.sequence_number = line[83..86].strip.to_i
|
113
122
|
ad.entry_detail_sequence_number = line[87..93].to_i
|
114
123
|
ed.addenda << ad
|
115
|
-
|
124
|
+
when '8'
|
116
125
|
# skip
|
117
|
-
|
126
|
+
when '9'
|
118
127
|
# skip
|
119
128
|
else
|
120
129
|
raise "Didn't recognize type code #{type} for this line:\n#{line}"
|
data/lib/ach/records/addendum.rb
CHANGED
@@ -1,13 +1,57 @@
|
|
1
1
|
module ACH::Records
|
2
2
|
class Addendum < Record
|
3
|
-
|
4
3
|
@fields = []
|
5
4
|
|
6
5
|
const_field :record_type, '7'
|
7
|
-
field :type_code, String,
|
6
|
+
field :type_code, String, nil, '05', /\A\d{2}\Z/
|
8
7
|
field :payment_data, String, lambda { |f| left_justify(f, 80)}
|
9
8
|
field :sequence_number, Integer, lambda { |f| sprintf('%04d', f)}
|
10
9
|
field :entry_detail_sequence_number, Integer, lambda { |f| sprintf('%07d', f)}
|
11
10
|
|
11
|
+
# NOTE: When the API can change, these subclasses should override the
|
12
|
+
# redundant fields in Addendum
|
13
|
+
class NotificationOfChange < Addendum
|
14
|
+
@fields = Addendum.fields
|
15
|
+
|
16
|
+
def reason_code
|
17
|
+
payment_data[0..2]
|
18
|
+
end
|
19
|
+
|
20
|
+
def original_entry_trace_number
|
21
|
+
payment_data[3..17]
|
22
|
+
end
|
23
|
+
|
24
|
+
def original_receiving_dfi_identification
|
25
|
+
payment_data[24..31]
|
26
|
+
end
|
27
|
+
|
28
|
+
def corrected_data
|
29
|
+
payment_data[32..60].strip
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Return < Addendum
|
34
|
+
@fields = Addendum.fields
|
35
|
+
|
36
|
+
def reason_code
|
37
|
+
payment_data[0..2]
|
38
|
+
end
|
39
|
+
|
40
|
+
def original_entry_trace_number
|
41
|
+
payment_data[3..17]
|
42
|
+
end
|
43
|
+
|
44
|
+
def date_of_death
|
45
|
+
Date.parse(payment_data[18..23])
|
46
|
+
end
|
47
|
+
|
48
|
+
def original_receiving_dfi_identification
|
49
|
+
payment_data[24..31]
|
50
|
+
end
|
51
|
+
|
52
|
+
def addenda_information
|
53
|
+
payment_data[32..75].strip
|
54
|
+
end
|
55
|
+
end
|
12
56
|
end
|
13
|
-
end
|
57
|
+
end
|
@@ -1,30 +1,31 @@
|
|
1
1
|
module ACH::Records
|
2
2
|
class BatchControl < Record
|
3
3
|
@fields = []
|
4
|
-
|
4
|
+
|
5
5
|
const_field :record_type, '8'
|
6
|
-
|
6
|
+
|
7
7
|
# Many of the fields are calculated in Batch.to_ach
|
8
8
|
field :service_class_code, String,
|
9
|
-
lambda { |f| f.to_s }, '200',
|
9
|
+
lambda { |f| f.to_s }, '200',
|
10
10
|
lambda { |f| ACH::SERVICE_CLASS_CODES.include?(f.to_i) }
|
11
11
|
field :entry_count, Integer, lambda { |f| sprintf('%06d', f)}
|
12
12
|
field :entry_hash, Integer, lambda { |f| sprintf('%010d', f % (10 ** 10))}
|
13
13
|
field :debit_total, Integer, lambda { |f| sprintf('%012d', f)}
|
14
14
|
field :credit_total, Integer, lambda { |f| sprintf('%012d', f)}
|
15
|
-
|
15
|
+
field :company_identification_code_designator, String, lambda {|f| f}, '1',
|
16
|
+
/\A(1|3){1}\Z/
|
16
17
|
field :company_identification, String,
|
17
|
-
lambda {|f|
|
18
|
+
lambda {|f| f}, nil, /\A\d{9}\Z/,
|
18
19
|
'Company Tax ID'
|
19
|
-
|
20
|
+
|
20
21
|
field :message_authentication_code, String,
|
21
22
|
lambda { |f| left_justify(f, 19)}, ''
|
22
23
|
|
23
24
|
const_field :reserved, (' ' * 6)
|
24
|
-
|
25
|
+
|
25
26
|
field :originating_dfi_identification, String,
|
26
27
|
lambda {|f| f}, nil, /\A\d{8}\Z/
|
27
|
-
|
28
|
+
|
28
29
|
field :batch_number, Integer, lambda { |f| sprintf('%07d', f)}, 1
|
29
30
|
end
|
30
31
|
end
|
@@ -1,23 +1,25 @@
|
|
1
1
|
module ACH::Records
|
2
2
|
class BatchHeader < Record
|
3
3
|
@fields = []
|
4
|
-
|
4
|
+
|
5
5
|
const_field :record_type, '5'
|
6
|
-
|
6
|
+
|
7
7
|
# TODO: This needs to be changed to reflect whether credits, debits or both.
|
8
8
|
field :service_class_code, String,
|
9
|
-
lambda { |f| f.to_s }, '200',
|
9
|
+
lambda { |f| f.to_s }, '200',
|
10
10
|
lambda { |f| ACH::SERVICE_CLASS_CODES.include?(f.to_i) }
|
11
11
|
field :company_name, String, lambda { |f| left_justify(f, 16)}
|
12
12
|
field :company_discretionary_data, String,
|
13
13
|
lambda { |f| left_justify(f, 20)}, ''
|
14
|
+
field :company_identification_code_designator, String, lambda {|f| f}, '1',
|
15
|
+
/\A(1|3){1}\Z/
|
14
16
|
field :company_identification, String,
|
15
|
-
lambda {|f|
|
17
|
+
lambda {|f| f}, nil, /\A\d{9}\Z/,
|
16
18
|
'Company Tax ID'
|
17
19
|
# TODO This should be used to determine whether other records are valid for
|
18
20
|
# for this code. Should there be a Class for each code?
|
19
21
|
# The default of PPD is purely for my benefit (Jared Morgan)
|
20
|
-
field :standard_entry_class_code, String,
|
22
|
+
field :standard_entry_class_code, String,
|
21
23
|
lambda { |f| f.upcase }, 'PPD', /\A\w{3}\Z/
|
22
24
|
field :company_entry_description, String,
|
23
25
|
lambda { |f| left_justify(f, 10)}
|
@@ -30,7 +32,7 @@ module ACH::Records
|
|
30
32
|
const_field :originator_status_code, '1'
|
31
33
|
field :originating_dfi_identification, String,
|
32
34
|
lambda {|f| f}, nil, /\A\d{8}\Z/
|
33
|
-
|
35
|
+
|
34
36
|
field :batch_number, Integer, lambda { |f| sprintf('%07d', f)}, 1
|
35
37
|
end
|
36
38
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ach
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-12-10 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: ! 'ach is a Ruby helper for builder ACH files. In particular, it helps
|
16
16
|
with field
|