ach 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -15,51 +15,60 @@ for a similar Perl library
15
15
  You should consult a copy of the [ACH Rules](http://www.nacha.org) for details
16
16
  on individual fields. You can probably obtain a copy from your bank.
17
17
 
18
- # Create ACH file
19
- ach = ACH::ACHFile.new
20
- trace_number = 0
21
-
22
- # File Header
23
- fh = ach.header
24
- fh.immediate_destination = "000000000"
25
- fh.immediate_destination_name = "BANK NAME"
26
- fh.immediate_origin = "000000000"
27
- fh.immediate_origin_name = "BANK NAME"
28
-
29
- # Batch
30
- batch = ACH::Batch.new
31
- bh = batch.header
32
- bh.company_name = "Company Name"
33
- bh.company_identification = "123456789"
34
- bh.standard_entry_class_code = 'PPD'
35
- bh.company_entry_description = "DESCRIPTION"
36
- bh.company_descriptive_date = Date.today
37
- bh.effective_entry_date = (Date.today + 1)
38
- bh.originating_dfi_identification = "00000000"
39
- ach.batches << batch
40
-
41
- # Detail Entry
42
- ed = ACH::EntryDetail.new
43
- ed.transaction_code = ACH::CHECKING_CREDIT
44
- ed.routing_number = "000000000"
45
- ed.account_number = "00000000000"
46
- ed.amount = 100 # In cents
47
- ed.individual_id_number = "Employee Name"
48
- ed.individual_name = "Employee Name"
49
- ed.originating_dfi_identification = '00000000'
50
- batch.entries << ed
51
- # ... Additional detail entries, possibly including *offsetting entry*, if needed.
52
-
53
- # Insert trace numbers
54
- batch.entries.each{ |entry| entry.trace_number = (trace_number += 1) }
55
-
56
-
57
- output = ach.to_s
58
- File.open("ach.txt", 'w') do |f|
59
- f.write output
60
- end
61
-
62
- p ach.report
18
+ ```ruby
19
+ # Create ACH file
20
+ ach = ACH::ACHFile.new
21
+ trace_number = 0
22
+
23
+ # File Header
24
+ fh = ach.header
25
+ fh.immediate_destination = "000000000"
26
+ fh.immediate_destination_name = "BANK NAME"
27
+ fh.immediate_origin = "000000000"
28
+ fh.immediate_origin_name = "BANK NAME"
29
+
30
+ # Batch
31
+ batch = ACH::Batch.new
32
+ bh = batch.header
33
+ bh.company_name = "Company Name"
34
+ bh.company_identification = "123456789"
35
+ bh.standard_entry_class_code = 'PPD'
36
+ bh.company_entry_description = "DESCRIPTION"
37
+ bh.company_descriptive_date = Date.today
38
+ bh.effective_entry_date = (Date.today + 1)
39
+ bh.originating_dfi_identification = "00000000"
40
+ ach.batches << batch
41
+
42
+ # Detail Entry
43
+ ed = ACH::EntryDetail.new
44
+ ed.transaction_code = ACH::CHECKING_CREDIT
45
+ ed.routing_number = "000000000"
46
+ ed.account_number = "00000000000"
47
+ ed.amount = 100 # In cents
48
+ ed.individual_id_number = "Employee Name"
49
+ ed.individual_name = "Employee Name"
50
+ ed.originating_dfi_identification = '00000000'
51
+ batch.entries << ed
52
+ # ... Additional detail entries, possibly including *offsetting entry*, if needed.
53
+
54
+ # Insert trace numbers
55
+ batch.entries.each{ |entry| entry.trace_number = (trace_number += 1) }
56
+
57
+
58
+ output = ach.to_s
59
+ File.open("ach.txt", 'w') do |f|
60
+ f.write output
61
+ end
62
+
63
+ p ach.report
64
+ ```
65
+
66
+ ```ruby
67
+ # Parse an ACH file
68
+ ach = ACH::ACHFile.new(File.read('examples/ach/fixtures/return_noc.txt'))
69
+ ach.batches.first.entries.first.addenda.first.payment_data
70
+ => "C05992222220280489 1211403932 1211"
71
+ ```
63
72
 
64
73
  ##Copyright
65
74
 
data/Rakefile CHANGED
@@ -22,7 +22,7 @@ end
22
22
 
23
23
  require 'micronaut/rake_task'
24
24
  Micronaut::RakeTask.new(:examples) do |examples|
25
- examples.pattern = 'examples/**/*_example.rb'
25
+ examples.pattern = './examples/**/*_example.rb'
26
26
  examples.ruby_opts << '-Ilib -Iexamples'
27
27
  end
28
28
 
@@ -34,7 +34,7 @@ end
34
34
 
35
35
  task :default => :examples
36
36
 
37
- require 'rake/rdoctask'
37
+ require 'rdoc/task'
38
38
  Rake::RDocTask.new do |rdoc|
39
39
  version = File.exist?('VERSION') ? File.read('VERSION') : ''
40
40
  rdoc.rdoc_dir = 'rdoc'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.4.0
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.3.2"
8
+ s.version = "0.4.0"
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-02"
12
+ s.date = "2012-11-07"
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 = [
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
25
25
  "examples/ach/ach_file_example.rb",
26
26
  "examples/ach/batch_example.rb",
27
27
  "examples/ach/field_identifiers_example.rb",
28
+ "examples/ach/fixtures/return_noc.txt",
29
+ "examples/ach/parse_example.rb",
28
30
  "examples/ach/records/batch_control_example.rb",
29
31
  "examples/ach/records/batch_header_example.rb",
30
32
  "examples/ach/records/ctx_entry_detail_example.rb",
@@ -47,7 +49,7 @@ Gem::Specification.new do |s|
47
49
  ]
48
50
  s.homepage = "http://github.com/jm81/ach"
49
51
  s.require_paths = ["lib"]
50
- s.rubygems_version = "1.8.11"
52
+ s.rubygems_version = "1.8.10"
51
53
  s.summary = "Helper for building ACH files in Ruby"
52
54
 
53
55
  if s.respond_to? :specification_version then
@@ -10,8 +10,7 @@ describe ACH::FieldIdentifiers do
10
10
  it 'should validate against a Regexp' do
11
11
  @klass.field(:sample, String, nil, nil, /\A\w{5}\Z/)
12
12
  record = @klass.new
13
- msg = "abcd does not match Regexp #{/\A\w{5}\Z/}"
14
- lambda { record.sample = 'abcd' }.should raise_error(RuntimeError, msg)
13
+ lambda { record.sample = 'abcd' }.should raise_error(RuntimeError)
15
14
  record.sample.should be_nil
16
15
  lambda { record.sample = 'abcdef' }.should raise_error(RuntimeError)
17
16
  record.sample.should be_nil
@@ -26,8 +25,7 @@ describe ACH::FieldIdentifiers do
26
25
 
27
26
  @klass.field(:sample, String, nil, nil, block)
28
27
  record = @klass.new
29
- msg = "5 does not pass validation Proc"
30
- lambda { record.sample = 5 }.should raise_error(RuntimeError, msg)
28
+ lambda { record.sample = 5 }.should raise_error(RuntimeError)
31
29
  record.sample.should be_nil
32
30
  lambda { record.sample = 501 }.should raise_error(RuntimeError)
33
31
  record.sample.should be_nil
@@ -0,0 +1,20 @@
1
+ 101 191001234 9922222261210151518A094101Certification Bank-SiliCERTIFICATION BANK-SILIRETURNS
2
+ 5200COMPANY INC DISC DATA 1412345678CORDESCRIPT 1210151210150001992222220000001
3
+ 6211211403993300911569 0000000000A38LTNY2 NAME ONE 1121140390280747
4
+ 798C05992222220280489 1211403932 121140390280747
5
+ 820000000200121140390000000000000000000000001412345678 992222220000001
6
+ 5200COMPANY INC DISC DATA 1412345678PPDDESCRIPT 1210151210150001992222220000002
7
+ 6261211403993300911569 0000002536GKGQT9VK NAME TWO 1121140390280738
8
+ 799R07992222220280393 12114039 121140390280738
9
+ 820000000200121140390000000025360000000000001412345678 992222220000002
10
+ 5200COMPANY INC DISC DATA 1412345678PPDDESCRIPT 1210151210150001992222220000003
11
+ 6261211403993300911569 0000002417KYU341VP NAME THREE 1121140390280729
12
+ 799R03992222220280389 12114039 121140390280729
13
+ 820000000200121140390000000024170000000000001412345678 992222220000003
14
+ 9000003000002000000060036342117000000004953000000000000
15
+ 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
16
+ 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
17
+ 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
18
+ 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
19
+ 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
20
+ 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
@@ -0,0 +1,49 @@
1
+ require 'example_helper'
2
+ require 'date'
3
+
4
+ describe "Parse" do
5
+ describe 'Returns and NOC' do
6
+ before(:each) do
7
+ @data = File.read('examples/ach/fixtures/return_noc.txt')
8
+ end
9
+
10
+ it "should parse return/notification of change file" do
11
+ ach = ACH::ACHFile.new(@data)
12
+ fh = ach.header
13
+ fh.immediate_destination.should == "191001234"
14
+ fh.immediate_origin.should == "992222226"
15
+ fh.transmission_datetime.should == Time.utc(2012, 10, 15, 15, 18)
16
+ fh.immediate_destination_name.should == "Certification Bank-Sili"
17
+ fh.immediate_origin_name.should == "CERTIFICATION BANK-SILI"
18
+
19
+ ach.batches.size.should == 3
20
+
21
+ batch = ach.batches[0]
22
+ batch.entries.size.should == 1
23
+ bh = batch.header
24
+ bh.company_name.should == "COMPANY INC"
25
+ bh.company_identification.should == "412345678"
26
+ bh.standard_entry_class_code.should == 'COR'
27
+ bh.company_entry_description.should == "DESCRIPT"
28
+ bh.company_descriptive_date.should == Date.parse('121015')
29
+ bh.effective_entry_date.should == Date.parse('121015')
30
+ bh.originating_dfi_identification.should == "99222222"
31
+
32
+ ed = batch.entries[0]
33
+ ed.transaction_code.should == "21"
34
+ ed.routing_number.should == "121140399"
35
+ ed.account_number.should == "3300911569"
36
+ ed.amount.should == 0 # In cents
37
+ ed.individual_id_number.should == "A38LTNY2"
38
+ ed.individual_name.should == "NAME ONE"
39
+
40
+ batch = ach.batches[1]
41
+ batch.entries.size.should == 1
42
+ bh = batch.header
43
+ bh.standard_entry_class_code.should == 'PPD'
44
+ ed = batch.entries[0]
45
+ ed.amount.should == 2536
46
+ end
47
+
48
+ end
49
+ end
data/lib/ach/ach_file.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'date'
2
+
1
3
  module ACH
2
4
  class ACHFile
3
5
  include FieldIdentifiers
@@ -6,10 +8,14 @@ module ACH
6
8
  attr_reader :header
7
9
  attr_reader :control
8
10
 
9
- def initialize
11
+ def initialize data=nil
10
12
  @batches = []
11
13
  @header = Records::FileHeader.new
12
14
  @control = Records::FileControl.new
15
+
16
+ if data
17
+ parse(data)
18
+ end
13
19
  end
14
20
 
15
21
  def to_s
@@ -37,6 +43,7 @@ module ACH
37
43
  @control.entry_hash += batch.control.entry_hash
38
44
  end
39
45
 
46
+
40
47
  records.collect { |r| r.to_ach }.join("\r\n") + "\r\n"
41
48
  end
42
49
 
@@ -58,5 +65,66 @@ module ACH
58
65
 
59
66
  lines.join("\r\n")
60
67
  end
68
+
69
+ def parse data
70
+ trace_number = 0
71
+ fh = self.header
72
+ batch = nil
73
+ bh = nil
74
+ ed = nil
75
+
76
+ data.strip.split(/\n|\r\n/).each do |line|
77
+ type = line[0..0]
78
+ if type == '1'
79
+ fh.immediate_destination = line[03..12].strip
80
+ fh.immediate_origin = line[13..22].strip
81
+ fh.transmission_datetime = Time.utc('20'+line[23..24], line[25..26], line[27..28], line[29..30], line[31..32])
82
+ fh.file_id_modifier = line[33..33]
83
+ fh.immediate_destination_name = line[40..62].strip
84
+ fh.immediate_origin_name = line[63..85].strip
85
+ fh.reference_code = line[86..93].strip
86
+ elsif type == '5'
87
+ self.batches << batch unless batch.nil?
88
+ batch = ACH::Batch.new
89
+ bh = batch.header
90
+ bh.company_name = line[4..19].strip
91
+ bh.company_identification = line[41..49].strip
92
+ bh.standard_entry_class_code = line[50..52].strip
93
+ bh.company_entry_description = line[53..62].strip
94
+ bh.company_descriptive_date = Date.parse(line[63..68])
95
+ bh.effective_entry_date = Date.parse(line[69..74])
96
+ bh.originating_dfi_identification = line[79..86].strip
97
+ elsif type == '6'
98
+ ed = ACH::CtxEntryDetail.new
99
+ ed.transaction_code = line[1..2]
100
+ ed.routing_number = line[3..11]
101
+ ed.account_number = line[12..28].strip
102
+ ed.amount = line[29..38].to_i # cents
103
+ ed.individual_id_number = line[39..53].strip
104
+ ed.individual_name = line[54..75].strip
105
+ ed.originating_dfi_identification = line[79..86]
106
+ ed.trace_number = line[87..93].to_i
107
+ batch.entries << ed
108
+ elsif type == '7'
109
+ ad = ACH::Addendum.new
110
+ ad.type_code = line[1..2]
111
+ ad.payment_data = line[3..82].strip
112
+ ad.sequence_number = line[83..86].strip.to_i
113
+ ad.entry_detail_sequence_number = line[87..93].to_i
114
+ ed.addenda << ad
115
+ elsif type == '8'
116
+ # skip
117
+ elsif type == '9'
118
+ # skip
119
+ else
120
+ raise "Didn't recognize type code #{type} for this line:\n#{line}"
121
+ end
122
+ end
123
+
124
+ batch.entries << ed unless ed.nil?
125
+ self.batches << batch unless batch.nil?
126
+ batch.entries.each{ |entry| entry.trace_number = (trace_number += 1) }
127
+ to_s
128
+ end
61
129
  end
62
130
  end
data/lib/ach/batch.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  module ACH
2
2
  class Batch
3
3
  attr_reader :entries
4
+ attr_reader :addendas
4
5
  attr_reader :header
5
6
  attr_reader :control
6
7
 
7
8
  def initialize
8
9
  @entries = []
10
+ @addendas = []
9
11
  @header = Records::BatchHeader.new
10
12
  @control = Records::BatchControl.new
11
13
  end
@@ -49,7 +51,7 @@ module ACH
49
51
  @control.originating_dfi_identification = @header.originating_dfi_identification
50
52
  @control.batch_number = @header.batch_number
51
53
 
52
- [@header] + @entries + [@control]
54
+ [@header] + @entries + @addendas + [@control]
53
55
  end
54
56
  end
55
57
  end
@@ -12,11 +12,11 @@ module ACH
12
12
  define_method "#{name}=" do | val |
13
13
  if validate.kind_of?(Regexp)
14
14
  unless val =~ validate
15
- raise RuntimeError, "#{val} does not match Regexp #{validate}"
15
+ raise RuntimeError, "#{val} does not match Regexp #{validate} for field #{name}"
16
16
  end
17
17
  elsif validate.respond_to?(:call) # Proc with value as argument
18
18
  unless validate.call(val)
19
- raise RuntimeError, "#{val} does not pass validation Proc"
19
+ raise RuntimeError, "#{val} does not pass validation Proc for field #{name}"
20
20
  end
21
21
  end
22
22
 
@@ -33,7 +33,7 @@ module ACH
33
33
  elsif default
34
34
  val = default
35
35
  else
36
- raise RuntimeError, "val is nil"
36
+ raise RuntimeError, "val of #{name} is nil"
37
37
  end
38
38
  end
39
39
 
@@ -4,7 +4,7 @@ module ACH::Records
4
4
  @fields = []
5
5
 
6
6
  const_field :record_type, '7'
7
- const_field :type_code, '05'
7
+ field :type_code, String, lambda {|f| f}, '05', /\A\d{2}\Z/
8
8
  field :payment_data, String, lambda { |f| left_justify(f, 80)}
9
9
  field :sequence_number, Integer, lambda { |f| sprintf('%04d', f)}
10
10
  field :entry_detail_sequence_number, Integer, lambda { |f| sprintf('%07d', f)}
@@ -68,7 +68,7 @@ module ACH::Records
68
68
 
69
69
  self.addenda.each {|a|
70
70
  a.entry_detail_sequence_number = self.trace_number
71
- ach_string << "\r\n" + a.to_ach
71
+ ach_string << "\r\n" + a.to_ach
72
72
  }
73
73
  return ach_string
74
74
  end
@@ -4,8 +4,8 @@ module ACH::Records
4
4
 
5
5
  const_field :record_type, '1'
6
6
  const_field :priority_code, '01'
7
- routing_field :immediate_destination
8
- routing_field :immediate_origin
7
+ field :immediate_destination, String, nil, /\A\s?\d{9}\Z/
8
+ field :immediate_origin, String, nil, /\A\d{9,10}\Z/
9
9
  field :transmission_datetime, Time,
10
10
  lambda { |f| f.strftime('%y%m%d%H%M')},
11
11
  lambda { Time.now }
data/lib/ach.rb CHANGED
@@ -36,6 +36,6 @@ end
36
36
 
37
37
  # Include Records module to simplify accessing Records classes.
38
38
  module ACH
39
- VERSION = '0.3.2'
39
+ VERSION = '0.4.0'
40
40
  include Records
41
41
  end
metadata CHANGED
@@ -1,36 +1,29 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: ach
3
- version: !ruby/object:Gem::Version
4
- hash: 23
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 3
9
- - 2
10
- version: 0.3.2
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Jared Morgan
14
9
  - Josh Puetz
15
10
  autorequire:
16
11
  bindir: bin
17
12
  cert_chain: []
18
-
19
- date: 2012-11-02 00:00:00 Z
13
+ date: 2012-11-07 00:00:00.000000000 Z
20
14
  dependencies: []
15
+ description: ! 'ach is a Ruby helper for builder ACH files. In particular, it helps
16
+ with field
21
17
 
22
- description: |
23
- ach is a Ruby helper for builder ACH files. In particular, it helps with field
24
18
  order and alignment, and adds padding lines to end of file.
25
19
 
20
+ '
26
21
  email: jmorgan@morgancreative.net
27
22
  executables: []
28
-
29
23
  extensions: []
30
-
31
- extra_rdoc_files:
24
+ extra_rdoc_files:
32
25
  - README.md
33
- files:
26
+ files:
34
27
  - .autotest
35
28
  - MIT-LICENSE
36
29
  - README.md
@@ -40,6 +33,8 @@ files:
40
33
  - examples/ach/ach_file_example.rb
41
34
  - examples/ach/batch_example.rb
42
35
  - examples/ach/field_identifiers_example.rb
36
+ - examples/ach/fixtures/return_noc.txt
37
+ - examples/ach/parse_example.rb
43
38
  - examples/ach/records/batch_control_example.rb
44
39
  - examples/ach/records/batch_header_example.rb
45
40
  - examples/ach/records/ctx_entry_detail_example.rb
@@ -61,36 +56,26 @@ files:
61
56
  - lib/ach/records/record.rb
62
57
  homepage: http://github.com/jm81/ach
63
58
  licenses: []
64
-
65
59
  post_install_message:
66
60
  rdoc_options: []
67
-
68
- require_paths:
61
+ require_paths:
69
62
  - lib
70
- required_ruby_version: !ruby/object:Gem::Requirement
63
+ required_ruby_version: !ruby/object:Gem::Requirement
71
64
  none: false
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- hash: 3
76
- segments:
77
- - 0
78
- version: "0"
79
- required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
70
  none: false
81
- requirements:
82
- - - ">="
83
- - !ruby/object:Gem::Version
84
- hash: 3
85
- segments:
86
- - 0
87
- version: "0"
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
88
75
  requirements: []
89
-
90
76
  rubyforge_project:
91
- rubygems_version: 1.8.11
77
+ rubygems_version: 1.8.10
92
78
  signing_key:
93
79
  specification_version: 3
94
80
  summary: Helper for building ACH files in Ruby
95
81
  test_files: []
96
-