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 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.0
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.0"
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-11-07"
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 12114039 121140390280738
8
+ 799R07992222220280393 12114039INVALID 121140390280738
9
9
  820000000200121140390000000025360000000000001412345678 992222220000002
10
- 5200COMPANY INC DISC DATA 1412345678PPDDESCRIPT 1210151210150001992222220000003
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..0]
78
- if type == '1'
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
- elsif type == '5'
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
- elsif type == '6'
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
- elsif type == '7'
109
- ad = ACH::Addendum.new
110
- ad.type_code = line[1..2]
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
- elsif type == '8'
124
+ when '8'
116
125
  # skip
117
- elsif type == '9'
126
+ when '9'
118
127
  # skip
119
128
  else
120
129
  raise "Didn't recognize type code #{type} for this line:\n#{line}"
@@ -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, lambda {|f| f}, '05', /\A\d{2}\Z/
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| '1' + f}, nil, /\A\d{9}\Z/,
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| '1' + f}, nil, /\A\d{9}\Z/,
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.0
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-11-07 00:00:00.000000000 Z
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