ach 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ require 'autotest'
2
+
3
+ Autotest.add_hook :initialize do |at|
4
+ at.clear_mappings
5
+ at.failed_results_re = /^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m
6
+ at.completed_re = /\n(?:\e\[\d*m)?\d* examples?/m
7
+
8
+ at.add_mapping(%r%^examples/.*_example.rb$%) { |filename, _|
9
+ filename
10
+ }
11
+
12
+ at.add_mapping(%r%^lib/(.*)\.rb$%) { |filename, m|
13
+ ["examples/lib/#{m[1]}_example.rb"]
14
+ }
15
+
16
+ at.add_mapping(%r%^examples/(example_helper|shared/.*)\.rb$%) {
17
+ at.files_matching %r%^examples/.*_example\.rb$%
18
+ }
19
+ end
20
+
21
+ class Autotest
22
+ remove_method :consolidate_failures if instance_methods(false).include?("consolidate_failures")
23
+ remove_method :make_test_cmd if instance_methods(false).include?("make_test_cmd")
24
+
25
+ def consolidate_failures(failed)
26
+ filters = new_hash_of_arrays
27
+ failed.each do |spec, trace|
28
+ if trace =~ /\n(\.\/)?(.*example\.rb):[\d]+:\Z?/
29
+ filters[$2] << spec
30
+ end
31
+ end
32
+ return filters
33
+ end
34
+
35
+ def make_test_cmd(files_to_test)
36
+ puts 'command'
37
+ return '' if files_to_test.empty?
38
+
39
+ examples = files_to_test.keys.flatten
40
+
41
+ examples.map! {|f| %Q(require "#{f}")}
42
+
43
+ return "#{ruby} -Ilib -Iexamples -e '#{examples.join("; ")}'"
44
+ end
45
+
46
+ end
@@ -0,0 +1,10 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .project
7
+ .loadpath
8
+ *qt_temp*
9
+ *~
10
+ *.gem
File without changes
data/README.md CHANGED
@@ -30,18 +30,20 @@ on individual fields. You can probably obtain a copy from your bank.
30
30
  batch = ACH::Batch.new
31
31
  bh = batch.header
32
32
  bh.company_name = "Company Name"
33
- bh.company_identification = "Company ID"
33
+ bh.company_identification = "123456789"
34
+ bh.standard_entry_class_code = 'PPD'
34
35
  bh.company_entry_description = "DESCRIPTION"
35
36
  bh.company_descriptive_date = Date.today
36
37
  bh.effective_entry_date = (Date.today + 1)
37
38
  bh.originating_dfi_identification = "00000000"
39
+ ach.batches << batch
38
40
 
39
41
  # Detail Entry
40
42
  ed = ACH::EntryDetail.new
41
43
  ed.transaction_code = ACH::CHECKING_CREDIT
42
44
  ed.routing_number = "000000000"
43
45
  ed.account_number = "00000000000"
44
- ed.amount = "100" # In cents
46
+ ed.amount = 100 # In cents
45
47
  ed.individual_id_number = "Employee Name"
46
48
  ed.individual_name = "Employee Name"
47
49
  ed.originating_dfi_identification = '00000000'
@@ -51,8 +53,9 @@ on individual fields. You can probably obtain a copy from your bank.
51
53
  # Insert trace numbers
52
54
  batch.entries.each{ |entry| entry.trace_number = (trace_number += 1) }
53
55
 
56
+
54
57
  output = ach.to_s
55
- File.open("/path/to/ach.txt", 'w') do |f|
58
+ File.open("ach.txt", 'w') do |f|
56
59
  f.write output
57
60
  end
58
61
 
data/Rakefile CHANGED
File without changes
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ach}
8
- s.version = "0.2.0"
8
+ s.version = "0.3.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"]
12
- s.date = %q{2009-10-11}
12
+ s.date = %q{2009-11-29}
13
13
  s.description = %q{ach is a Ruby helper for builder ACH files. In particular, it helps with field
14
14
  order and alignment, and adds padding lines to end of file.
15
15
  }
@@ -18,14 +18,20 @@ order and alignment, and adds padding lines to end of file.
18
18
  "README.md"
19
19
  ]
20
20
  s.files = [
21
- "MIT-LICENSE",
21
+ ".autotest",
22
+ ".gitignore",
23
+ "MIT-LICENSE",
22
24
  "README.md",
23
25
  "Rakefile",
24
26
  "VERSION",
25
27
  "ach.gemspec",
26
28
  "examples/ach/ach_file_example.rb",
29
+ "examples/ach/batch_example.rb",
30
+ "examples/ach/field_identifiers_example.rb",
27
31
  "examples/ach/records/batch_control_example.rb",
32
+ "examples/ach/records/batch_header_example.rb",
28
33
  "examples/ach/records/nines_example.rb",
34
+ "examples/ach/records/shared/batch_summaries.rb",
29
35
  "examples/example_helper.rb",
30
36
  "lib/ach.rb",
31
37
  "lib/ach/ach_file.rb",
@@ -46,9 +52,13 @@ order and alignment, and adds padding lines to end of file.
46
52
  s.rubygems_version = %q{1.3.5}
47
53
  s.summary = %q{Helper for building ACH files in Ruby}
48
54
  s.test_files = [
49
- "examples/ach/ach_file_example.rb",
50
- "examples/ach/records/batch_control_example.rb",
55
+ "examples/ach/field_identifiers_example.rb",
56
+ "examples/ach/ach_file_example.rb",
51
57
  "examples/ach/records/nines_example.rb",
58
+ "examples/ach/records/shared/batch_summaries.rb",
59
+ "examples/ach/records/batch_control_example.rb",
60
+ "examples/ach/records/batch_header_example.rb",
61
+ "examples/ach/batch_example.rb",
52
62
  "examples/example_helper.rb"
53
63
  ]
54
64
 
@@ -62,3 +72,4 @@ order and alignment, and adds padding lines to end of file.
62
72
  else
63
73
  end
64
74
  end
75
+
File without changes
@@ -0,0 +1,98 @@
1
+ require 'example_helper'
2
+
3
+ describe ACH::Batch do
4
+ before(:each) do
5
+ @batch = ACH::Batch.new
6
+
7
+ @credit = ACH::EntryDetail.new
8
+ @credit.transaction_code = ACH::CHECKING_CREDIT
9
+ @credit.routing_number = "000000000"
10
+ @credit.account_number = "00000000000"
11
+ @credit.amount = 100 # In cents
12
+ @credit.individual_id_number = "Employee Name"
13
+ @credit.individual_name = "Employee Name"
14
+ @credit.originating_dfi_identification = '00000000'
15
+
16
+ @debit = @credit.dup
17
+ @debit.transaction_code = ACH::CHECKING_DEBIT
18
+ end
19
+
20
+ def new_batch
21
+ batch = ACH::Batch.new
22
+ bh = batch.header
23
+ bh.company_name = "Company Name"
24
+ bh.company_identification = "123456789"
25
+ bh.standard_entry_class_code = 'PPD'
26
+ bh.company_entry_description = "DESCRIPTION"
27
+ bh.company_descriptive_date = Date.today
28
+ bh.effective_entry_date = (Date.today + 1)
29
+ bh.originating_dfi_identification = "00000000"
30
+ return batch
31
+ end
32
+
33
+ describe '#to_s' do
34
+ it 'should determine BatchHeader#service_class_code if not set' do
35
+ debits = new_batch
36
+ debits.entries << @debit << @debit
37
+ debits.header.service_class_code.should be_nil
38
+ debits.to_ach
39
+ debits.header.service_class_code.should == 225
40
+
41
+ credits = new_batch
42
+ credits.entries << @credit << @credit
43
+ credits.header.service_class_code.should be_nil
44
+ credits.to_ach
45
+ credits.header.service_class_code.should == 220
46
+
47
+ both = new_batch
48
+ both.entries << @credit << @debit
49
+ both.header.service_class_code.should be_nil
50
+ both.to_ach
51
+ both.header.service_class_code.should == 200
52
+ end
53
+
54
+ it 'should not override BatchHeader#service_class_code if already set' do
55
+ debits = new_batch
56
+ debits.header.service_class_code = 200
57
+ debits.entries << @debit << @debit
58
+ debits.to_ach
59
+ debits.header.service_class_code.should == 200
60
+
61
+ debits = new_batch
62
+ debits.header.service_class_code = '220'
63
+ debits.entries << @credit << @credit
64
+ debits.to_ach
65
+ debits.header.service_class_code.should == '220'
66
+ end
67
+
68
+ it 'should set BatchControl#service_class_code from BatchHeader if not set' do
69
+ batch = new_batch
70
+ batch.header.service_class_code = 200
71
+ batch.control.service_class_code.should be_nil
72
+ batch.to_ach
73
+ batch.control.service_class_code.should == 200
74
+
75
+ batch = new_batch
76
+ batch.header.service_class_code = '225'
77
+ batch.control.service_class_code.should be_nil
78
+ batch.to_ach
79
+ batch.control.service_class_code.should == '225'
80
+
81
+ debits = new_batch
82
+ debits.entries << @debit << @debit
83
+ debits.header.service_class_code.should be_nil
84
+ debits.control.service_class_code.should be_nil
85
+ debits.to_ach
86
+ debits.header.service_class_code.should == 225
87
+ end
88
+
89
+ it 'should not override BatchHeader#service_class_code if already set' do
90
+ # Granted that I can't imagine this every being used...
91
+ batch = new_batch
92
+ batch.header.service_class_code = 220
93
+ batch.control.service_class_code = 200
94
+ batch.to_ach
95
+ batch.control.service_class_code.should == 200
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,47 @@
1
+ require 'example_helper'
2
+
3
+ describe ACH::FieldIdentifiers do
4
+ describe 'setter' do
5
+ before(:each) do
6
+ @klass = Class.new(ACH::Records::Record)
7
+ @klass.instance_variable_set(:@fields, [])
8
+ end
9
+
10
+ it 'should validate against a Regexp' do
11
+ @klass.field(:sample, String, nil, nil, /\A\w{5}\Z/)
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)
15
+ record.sample.should be_nil
16
+ lambda { record.sample = 'abcdef' }.should raise_error(RuntimeError)
17
+ record.sample.should be_nil
18
+ lambda { record.sample = 'abcde' }.should_not raise_error
19
+ record.sample.should == 'abcde'
20
+ end
21
+
22
+ it 'should validate against a Proc' do
23
+ block = Proc.new do |val|
24
+ [1, 2, 500].include?(val)
25
+ end
26
+
27
+ @klass.field(:sample, String, nil, nil, block)
28
+ record = @klass.new
29
+ msg = "5 does not pass validation Proc"
30
+ lambda { record.sample = 5 }.should raise_error(RuntimeError, msg)
31
+ record.sample.should be_nil
32
+ lambda { record.sample = 501 }.should raise_error(RuntimeError)
33
+ record.sample.should be_nil
34
+ lambda { record.sample = 1 }.should_not raise_error
35
+ record.sample.should == 1
36
+ lambda { record.sample = 500 }.should_not raise_error
37
+ record.sample.should == 500
38
+ end
39
+
40
+ it 'should set instance variable' do
41
+ @klass.field(:sample, String)
42
+ record = @klass.new
43
+ record.sample = 'abcde'
44
+ record.instance_variable_get(:@sample).should == 'abcde'
45
+ end
46
+ end
47
+ end
@@ -1,4 +1,5 @@
1
1
  require 'example_helper'
2
+ require 'ach/records/shared/batch_summaries'
2
3
 
3
4
  describe ACH::Records::BatchControl do
4
5
  before(:each) do
@@ -13,6 +14,8 @@ describe ACH::Records::BatchControl do
13
14
  @record.batch_number = 5
14
15
  end
15
16
 
17
+ self.instance_eval(&SharedExamples.batch_summaries)
18
+
16
19
  describe '#to_ach' do
17
20
  it 'should generate record string' do
18
21
  exp = [
@@ -0,0 +1,29 @@
1
+ require 'example_helper'
2
+ require 'ach/records/shared/batch_summaries'
3
+
4
+ describe ACH::Records::BatchHeader do
5
+ before(:each) do
6
+ @record = ACH::Records::BatchHeader.new
7
+ end
8
+
9
+ self.instance_eval(&SharedExamples.batch_summaries)
10
+
11
+ describe '#standard_entry_class_code' do
12
+ it 'should default to PPD' do
13
+ @record.standard_entry_class_code_to_ach.should == 'PPD'
14
+ end
15
+
16
+ it 'should be capitalized' do
17
+ @record.standard_entry_class_code = 'ccd'
18
+ @record.standard_entry_class_code_to_ach.should == 'CCD'
19
+ end
20
+
21
+ it 'should be exactly three characters' do
22
+ lambda { @record.standard_entry_class_code = 'CCDA' }.should raise_error(RuntimeError)
23
+ lambda { @record.standard_entry_class_code = 'CC' }.should raise_error(RuntimeError)
24
+ lambda { @record.standard_entry_class_code = 'CCD' }.should_not raise_error(RuntimeError)
25
+ end
26
+
27
+ it 'should be limited to real codes'
28
+ end
29
+ end
File without changes
@@ -0,0 +1,43 @@
1
+ # Include with:
2
+ #
3
+ # self.instance_eval(&shared_examples_for_batch_summaries)
4
+
5
+ module SharedExamples
6
+ def self.batch_summaries
7
+ Proc.new do
8
+ describe '#service_class_code' do
9
+ it 'should accept an Integer' do
10
+ @record.service_class_code = 200
11
+ @record.service_class_code.should == 200
12
+ @record.service_class_code_to_ach.should == '200'
13
+ end
14
+
15
+ it 'should accept a String' do
16
+ @record.service_class_code = '220'
17
+ @record.service_class_code.should == '220'
18
+ @record.service_class_code_to_ach.should == '220'
19
+ end
20
+
21
+ it 'must be a 200, 220, 225 or 280' do
22
+ lambda { @record.service_class_code = '201' }.should raise_error(RuntimeError)
23
+ lambda { @record.service_class_code = 201 }.should raise_error(RuntimeError)
24
+ lambda { @record.service_class_code = '2020' }.should raise_error(RuntimeError)
25
+ lambda { @record.service_class_code = '20' }.should raise_error(RuntimeError)
26
+ end
27
+
28
+ describe '#service_class_code_to_ach' do
29
+ it 'should use a given value' do
30
+ @record.service_class_code = '220'
31
+ @record.service_class_code_to_ach.should == '220'
32
+ @record.service_class_code = '225'
33
+ @record.service_class_code_to_ach.should == '225'
34
+ end
35
+
36
+ it 'should default to 200 when entries unavailable' do
37
+ @record.service_class_code_to_ach.should == '200'
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
File without changes
data/lib/ach.rb CHANGED
@@ -9,6 +9,14 @@ module ACH
9
9
  SAVING_DEBIT = '37'
10
10
  SAVING_CREDIT_PRENOTE = '33'
11
11
  SAVING_DEBIT_PRENOT = '38'
12
+
13
+ # Valid service class codes
14
+ SERVICE_CLASS_CODES = [
15
+ 200, # ACH Entries Mixed Debits and Credits
16
+ 220, # ACH Credits Only
17
+ 225, # ACH Debits Only
18
+ 280 # ACH Automated Accounting Advices
19
+ ]
12
20
  end
13
21
 
14
22
  require 'time'
@@ -25,6 +33,6 @@ end
25
33
 
26
34
  # Include Records module to simplify accessing Records classes.
27
35
  module ACH
28
- VERSION = '0.2.0'
36
+ VERSION = '0.3.0'
29
37
  include Records
30
38
  end
@@ -37,7 +37,7 @@ module ACH
37
37
  @control.entry_hash += batch.control.entry_hash
38
38
  end
39
39
 
40
- records.collect { |r| r.to_ach }.join("\n") + "\n"
40
+ records.collect { |r| r.to_ach }.join("\r\n") + "\r\n"
41
41
  end
42
42
 
43
43
  def report
@@ -50,7 +50,7 @@ module ACH
50
50
  sprintf("% 7d.%02d", entry.amount / 100, entry.amount % 100)
51
51
  end
52
52
  end
53
- lines.join("\n")
53
+ lines.join("\r\n")
54
54
  end
55
55
  end
56
56
  end
@@ -15,17 +15,36 @@ module ACH
15
15
  @control.debit_total = 0
16
16
  @control.credit_total = 0
17
17
  @control.entry_hash = 0
18
+ has_debits = false
19
+ has_credits = false
18
20
 
19
21
  @entries.each do |e|
20
22
  if e.debit?
21
23
  @control.debit_total += e.amount
24
+ has_debits = true
22
25
  else
23
26
  @control.credit_total += e.amount
27
+ has_credits = true
24
28
  end
25
29
  @control.entry_hash +=
26
30
  (e.routing_number.to_i / 10) # Last digit is not part of Receiving DFI
27
31
  end
28
32
 
33
+ # Set service class codes if needed
34
+ if @header.service_class_code.nil?
35
+ if has_debits && has_credits
36
+ @header.service_class_code = 200
37
+ elsif has_debits
38
+ @header.service_class_code = 225
39
+ else
40
+ @header.service_class_code = 220
41
+ end
42
+ end
43
+
44
+ if @control.service_class_code.nil?
45
+ @control.service_class_code = @header.service_class_code
46
+ end
47
+
29
48
  @control.company_identification = @header.company_identification
30
49
  @control.originating_dfi_identification = @header.originating_dfi_identification
31
50
  @control.batch_number = @header.batch_number
@@ -14,7 +14,12 @@ module ACH
14
14
  unless val =~ validate
15
15
  raise RuntimeError, "#{val} does not match Regexp #{validate}"
16
16
  end
17
+ elsif validate.respond_to?(:call) # Proc with value as argument
18
+ unless validate.call(val)
19
+ raise RuntimeError, "#{val} does not pass validation Proc"
20
+ end
17
21
  end
22
+
18
23
  instance_variable_set( "@#{name}", val )
19
24
  end
20
25
 
File without changes
@@ -4,9 +4,10 @@ module ACH::Records
4
4
 
5
5
  const_field :record_type, '8'
6
6
 
7
- # TODO: This needs to be changed to reflect whether credits, debits or both.
8
- const_field :service_class_code, '200'
9
7
  # Many of the fields are calculated in Batch.to_ach
8
+ field :service_class_code, String,
9
+ lambda { |f| f.to_s }, '200',
10
+ lambda { |f| ACH::SERVICE_CLASS_CODES.include?(f.to_i) }
10
11
  field :entry_count, Integer, lambda { |f| sprintf('%06d', f)}
11
12
  field :entry_hash, Integer, lambda { |f| sprintf('%010d', f % (10 ** 10))}
12
13
  field :debit_total, Integer, lambda { |f| sprintf('%012d', f)}
@@ -5,14 +5,20 @@ module ACH::Records
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
- const_field :service_class_code, '200'
8
+ field :service_class_code, String,
9
+ lambda { |f| f.to_s }, '200',
10
+ lambda { |f| ACH::SERVICE_CLASS_CODES.include?(f.to_i) }
9
11
  field :company_name, String, lambda { |f| left_justify(f, 16)}
10
12
  field :company_discretionary_data, String,
11
13
  lambda { |f| left_justify(f, 20)}, ''
12
14
  field :company_identification, String,
13
15
  lambda {|f| '1' + f}, nil, /\A\d{9}\Z/,
14
16
  'Company Tax ID'
15
- const_field :standard_entry_class_code, 'PPD'
17
+ # TODO This should be used to determine whether other records are valid for
18
+ # for this code. Should there be a Class for each code?
19
+ # The default of PPD is purely for my benefit (Jared Morgan)
20
+ field :standard_entry_class_code, String,
21
+ lambda { |f| f.upcase }, 'PPD', /\A\w{3}\Z/
16
22
  field :company_entry_description, String,
17
23
  lambda { |f| left_justify(f, 10)}
18
24
  field :company_descriptive_date, Time,
@@ -27,7 +27,10 @@ module ACH::Records
27
27
  def debit?
28
28
  !credit?
29
29
  end
30
+
31
+ def amount_value
32
+ return self.amount
33
+ end
34
+
30
35
  end
31
36
  end
32
-
33
- # Uses '\r\n' (I think)
File without changes
File without changes
File without changes
File without changes
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jared Morgan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-11 00:00:00 -05:00
12
+ date: 2009-11-29 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,14 +25,20 @@ extensions: []
25
25
  extra_rdoc_files:
26
26
  - README.md
27
27
  files:
28
+ - .autotest
29
+ - .gitignore
28
30
  - MIT-LICENSE
29
31
  - README.md
30
32
  - Rakefile
31
33
  - VERSION
32
34
  - ach.gemspec
33
35
  - examples/ach/ach_file_example.rb
36
+ - examples/ach/batch_example.rb
37
+ - examples/ach/field_identifiers_example.rb
34
38
  - examples/ach/records/batch_control_example.rb
39
+ - examples/ach/records/batch_header_example.rb
35
40
  - examples/ach/records/nines_example.rb
41
+ - examples/ach/records/shared/batch_summaries.rb
36
42
  - examples/example_helper.rb
37
43
  - lib/ach.rb
38
44
  - lib/ach/ach_file.rb
@@ -75,7 +81,11 @@ signing_key:
75
81
  specification_version: 3
76
82
  summary: Helper for building ACH files in Ruby
77
83
  test_files:
84
+ - examples/ach/field_identifiers_example.rb
78
85
  - examples/ach/ach_file_example.rb
79
- - examples/ach/records/batch_control_example.rb
80
86
  - examples/ach/records/nines_example.rb
87
+ - examples/ach/records/shared/batch_summaries.rb
88
+ - examples/ach/records/batch_control_example.rb
89
+ - examples/ach/records/batch_header_example.rb
90
+ - examples/ach/batch_example.rb
81
91
  - examples/example_helper.rb