ach 0.2.0 → 0.3.0

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.
@@ -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