ach 0.4.7 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MTg0YjlhNzMwY2RkODJlY2VlYjY3MjU2NmRmZmQ0ZDMyNTFiOGEzNA==
4
+ NzUzNDkyMzc0ZDk4ODk2MzhjZTY0MGE5Y2ZlMmFkNmE0NjZiNjVjNw==
5
5
  data.tar.gz: !binary |-
6
- OTg5OTQ1MjYzNDM4YjJjNjhkMTYzZGVkZmI2NGNjNGJhMzNhNWI5YQ==
6
+ YzQ4ZGQ1MTAwNGYwOTAxZTFlZDk1OGFkMjJhNDgzNGNiNzBjNWJiYQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YTM0ZDNmMWM2ZjBkMDdiNmUxNWVmOWJhMGE2OGYyNmM1ZmRjOTRiOWJiM2Ez
10
- NDMwNzA4YjEwYzI1MTg3MWYwZmY2OWU2YmY4MGQwMDcwZmYyMmI1ZmZmNjg5
11
- ZDVmYTE4NTRmMDU2YmJmOTcwNTY4ZWM2YjU4ZTAyMGExOGYxODI=
9
+ MjUwMTUwM2U4NmFkZDI3ZGVjMTgzMDRhZWZkNWMzY2IwYTJiNjU4MjA3YjU2
10
+ MzYwYjVhYzdkMDVmZDVlMzEyZDEwNWM0YmY4YTU3ODhiMjhmZTgxMWYwOGQ5
11
+ NmE0NjQxN2RjNTNhODIyYTg4ZWJjYTE5N2NlZTJiNmRhYjYyOGI=
12
12
  data.tar.gz: !binary |-
13
- ZDAyNmExZjE2NjFkY2Q1ZjJlZmUwZGI0NjE5OWYwNzJkNWQxNjI1N2MxZTVm
14
- NzJhYTgzN2VjOGViMDFlMmM0MDgxZGM1ODk5YmFhNmI0MDM2NzBkY2JiNjQy
15
- ZWIzOGM3ZDQ4OTkxZjZjMTY1NjFiYmRjNWY5ODIzMGE1YjkxMWM=
13
+ ODFmY2MzYmM3NTlkMWMwNWU2OTAzYmU1ZTRlZDgyMGMzNzVlZmE5YjE0YzBj
14
+ NmZjNjhlN2VlNTI0MjNiYzU5NDZhZDU3YWNmYWU1MDFlODBmMGQzMmE4MGQ0
15
+ ZWQxZjI0ZWQxNjkwNTJlOWUyMjEwZDg5MmQ2OTViYzJiNDEzODY=
data/README.md CHANGED
@@ -70,6 +70,8 @@ ach.batches.first.entries.first.addenda.first.payment_data
70
70
  => "C05992222220280489 1211403932 1211"
71
71
  ```
72
72
 
73
+ **Note:** When adding an amount to your ach file, it needs to be in cents. So you'll want to multiply any dollar amounts by 100
74
+
73
75
  ##Copyright
74
76
 
75
77
  Copyright (c) 2008-2009 Jared E Morgan, released under the MIT license
@@ -14,7 +14,11 @@ module ACH
14
14
  @control = Records::FileControl.new
15
15
 
16
16
  if data
17
- parse(data)
17
+ if (data.encode(Encoding.find('ASCII'),ENCODING_OPTIONS) =~ /\n|\r\n/).nil?
18
+ parse_fixed(data)
19
+ else
20
+ parse(data)
21
+ end
18
22
  end
19
23
  end
20
24
 
@@ -37,13 +41,12 @@ module ACH
37
41
  @control.entry_hash = 0
38
42
 
39
43
  @batches.each do | batch |
40
- @control.entry_count += batch.entries.length
44
+ @control.entry_count += batch.entries.inject(0) { |total, entry| total + entry.records_count }
41
45
  @control.debit_total += batch.control.debit_total
42
46
  @control.credit_total += batch.control.credit_total
43
47
  @control.entry_hash += batch.control.entry_hash
44
48
  end
45
49
 
46
-
47
50
  records.collect { |r| r.to_ach }.join("\r\n") + "\r\n"
48
51
  end
49
52
 
@@ -66,6 +69,12 @@ module ACH
66
69
  lines.join("\r\n")
67
70
  end
68
71
 
72
+ def parse_fixed data
73
+ # replace with a space to preserve the record-lengths
74
+ encoded_data = data.encode(Encoding.find('ASCII'),{:invalid => :replace, :undef => :replace, :replace => ' '})
75
+ parse encoded_data.scan(/.{94}/).join("\n")
76
+ end
77
+
69
78
  def parse data
70
79
  trace_number = 0
71
80
  fh = self.header
@@ -89,7 +98,7 @@ module ACH
89
98
  batch = ACH::Batch.new
90
99
  bh = batch.header
91
100
  bh.company_name = line[4..19].strip
92
- bh.company_identification = line[40..49].strip.gsub(/\A1/, '')
101
+ bh.company_identification = line[40..49].gsub(/\A1/, '')
93
102
  bh.standard_entry_class_code = line[50..52].strip
94
103
  bh.company_entry_description = line[53..62].strip
95
104
  bh.company_descriptive_date = Date.parse(line[63..68]) rescue nil # this can be various formats
@@ -0,0 +1,150 @@
1
+ require 'date'
2
+
3
+ module ACH
4
+ class ACHFile
5
+ include FieldIdentifiers
6
+
7
+ attr_reader :batches
8
+ attr_reader :header
9
+ attr_reader :control
10
+
11
+ def initialize data=nil
12
+ @batches = []
13
+ @header = Records::FileHeader.new
14
+ @control = Records::FileControl.new
15
+
16
+ if data
17
+ if (data =~ /\n|\r\n/).nil?
18
+ parse_fixed(data)
19
+ else
20
+ parse(data)
21
+ end
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ records = []
27
+ records << @header
28
+ @batches.each { |b| records += b.to_ach }
29
+ records << @control
30
+
31
+ nines_needed = 10 - (records.length % 10)
32
+ nines_needed = nines_needed % 10
33
+ nines_needed.times { records << Records::Nines.new() }
34
+
35
+ @control.batch_count = @batches.length
36
+ @control.block_count = (records.length / 10).ceil
37
+
38
+ @control.entry_count = 0
39
+ @control.debit_total = 0
40
+ @control.credit_total = 0
41
+ @control.entry_hash = 0
42
+
43
+ @batches.each do | batch |
44
+ @control.entry_count += batch.entries.length
45
+ @control.debit_total += batch.control.debit_total
46
+ @control.credit_total += batch.control.credit_total
47
+ @control.entry_hash += batch.control.entry_hash
48
+ end
49
+
50
+
51
+ records.collect { |r| r.to_ach }.join("\r\n") + "\r\n"
52
+ end
53
+
54
+ def report
55
+ to_s # To ensure correct records
56
+ lines = []
57
+
58
+ @batches.each do | batch |
59
+ batch.entries.each do | entry |
60
+ lines << left_justify(entry.individual_name + ": ", 25) +
61
+ sprintf("% 7d.%02d", entry.amount / 100, entry.amount % 100)
62
+ end
63
+ end
64
+ lines << ""
65
+ lines << left_justify("Debit Total: ", 25) +
66
+ sprintf("% 7d.%02d", @control.debit_total / 100, @control.debit_total % 100)
67
+ lines << left_justify("Credit Total: ", 25) +
68
+ sprintf("% 7d.%02d", @control.credit_total / 100, @control.credit_total % 100)
69
+
70
+ lines.join("\r\n")
71
+ end
72
+
73
+ def parse_fixed data
74
+ parse data.scan(/.{94}/).join("\n")
75
+ end
76
+
77
+ def parse data
78
+ trace_number = 0
79
+ fh = self.header
80
+ batch = nil
81
+ bh = nil
82
+ ed = nil
83
+
84
+ data.strip.split(/\n|\r\n/).each do |line|
85
+ type = line[0].chr
86
+ case type
87
+ when '1'
88
+ fh.immediate_destination = line[03..12].strip
89
+ fh.immediate_origin = line[13..22].strip
90
+ fh.transmission_datetime = Time.utc('20'+line[23..24], line[25..26], line[27..28], line[29..30], line[31..32])
91
+ fh.file_id_modifier = line[33..33]
92
+ fh.immediate_destination_name = line[40..62].strip
93
+ fh.immediate_origin_name = line[63..85].strip
94
+ fh.reference_code = line[86..93].strip
95
+ when '5'
96
+ self.batches << batch unless batch.nil?
97
+ batch = ACH::Batch.new
98
+ bh = batch.header
99
+ bh.company_name = line[4..19].strip
100
+ <<<<<<< HEAD
101
+ bh.company_identification = line[40..49].strip.gsub(/\A1/, '')
102
+ =======
103
+ bh.company_identification = line[41..49]# .strip - do not strip need to preserve trailing spaces
104
+ >>>>>>> 2dd27ff1c45679b6544a088c4a21f2689123ddf8
105
+ bh.standard_entry_class_code = line[50..52].strip
106
+ bh.company_entry_description = line[53..62].strip
107
+ bh.company_descriptive_date = Date.parse(line[63..68]) rescue nil # this can be various formats
108
+ bh.effective_entry_date = Date.parse(line[69..74])
109
+ bh.originating_dfi_identification = line[79..86].strip
110
+ when '6'
111
+ ed = ACH::CtxEntryDetail.new
112
+ ed.transaction_code = line[1..2]
113
+ ed.routing_number = line[3..11]
114
+ ed.account_number = line[12..28].strip
115
+ ed.amount = line[29..38].to_i # cents
116
+ ed.individual_id_number = line[39..53].strip
117
+ ed.individual_name = line[54..75].strip
118
+ ed.originating_dfi_identification = line[79..86]
119
+ ed.trace_number = line[87..93].to_i
120
+ batch.entries << ed
121
+ when '7'
122
+ type_code = line[1..2]
123
+ ad = case type_code
124
+ when '98'
125
+ ACH::Addendum::NotificationOfChange.new
126
+ when '99'
127
+ ACH::Addendum::Return.new
128
+ else
129
+ ACH::Addendum.new
130
+ end
131
+ ad.type_code = type_code
132
+ ad.payment_data = line[3..82].strip
133
+ ad.sequence_number = line[83..86].strip.to_i
134
+ ad.entry_detail_sequence_number = line[87..93].to_i
135
+ ed.addenda << ad
136
+ when '8'
137
+ # skip
138
+ when '9'
139
+ # skip
140
+ else
141
+ raise "Didn't recognize type code #{type} for this line:\n#{line}"
142
+ end
143
+ end
144
+
145
+ self.batches << batch unless batch.nil?
146
+ batch.entries.each{ |entry| entry.trace_number = (trace_number += 1) }
147
+ to_s
148
+ end
149
+ end
150
+ end
@@ -13,7 +13,7 @@ module ACH
13
13
  end
14
14
 
15
15
  def to_ach
16
- @control.entry_count = @entries.length
16
+ @control.entry_count = @entries.inject(0) { |total, entry| total + entry.records_count }
17
17
  @control.debit_total = 0
18
18
  @control.credit_total = 0
19
19
  @control.entry_hash = 0
@@ -14,8 +14,7 @@ module ACH::Records
14
14
  field :debit_total, Integer, lambda { |f| sprintf('%012d', f)}
15
15
  field :credit_total, Integer, lambda { |f| sprintf('%012d', f)}
16
16
  field :company_identification, String,
17
- lambda { |f| f.length == 10 ? f : "1#{f}" }, nil, /\A\d{9,10}\z/
18
-
17
+ lambda { |f| f.length == 10 ? f : "1#{f}" }, nil, /\A.{9,10}\z/
19
18
  field :message_authentication_code, String,
20
19
  lambda { |f| left_justify(f, 19)}, ''
21
20
 
@@ -12,7 +12,7 @@ module ACH::Records
12
12
  field :company_discretionary_data, String,
13
13
  lambda { |f| left_justify(f, 20)}, ''
14
14
  field :company_identification, String,
15
- lambda { |f| f.length == 10 ? f : "1#{f}" }, nil, /\A[a-zA-Z0-9 ]{9,10}\z/
15
+ lambda { |f| f.length == 10 ? f : "1#{f}" }, nil, /\A.{9,10}\z/
16
16
  # TODO This should be used to determine whether other records are valid for
17
17
  # this code. Should there be a Class for each code?
18
18
  # The default of PPD is purely for my benefit (Jared Morgan)
@@ -23,6 +23,12 @@ module ACH::Records
23
23
  field :trace_number, Integer, lambda { |f| sprintf('%07d', f)}, nil,
24
24
  lambda { |n| n.to_s.length <= 7 }
25
25
 
26
+ attr_reader :addenda
27
+
28
+ def initialize
29
+ @addenda = []
30
+ end
31
+
26
32
  def credit?
27
33
  CREDIT_RECORD_TRANSACTION_CODE_ENDING_DIGITS.include?(@transaction_code[1..1])
28
34
  end
@@ -35,35 +41,13 @@ module ACH::Records
35
41
  return self.amount
36
42
  end
37
43
 
38
- end
39
-
40
- class CtxEntryDetail < EntryDetail
41
-
42
- @fields = EntryDetail.fields.slice(0, 6)
43
- field :number_of_addenda_records, Integer, lambda { |f| sprintf('%04d', f)}, 0
44
- field :individual_name, String, lambda { |f| left_justify(f, 16)}
45
- const_field :reserved, ' '
46
- field :discretionary_data, String, lambda { |f| left_justify(f, 2)}, ' '
47
- field :addenda_record_indicator, Integer,
48
- lambda { |f| sprintf('%01d', f)}
49
- field :originating_dfi_identification, String,
50
- nil, nil, /\A\d{8}\z/
51
- field :trace_number, Integer, lambda { |f| sprintf('%07d', f)}
52
-
53
-
54
- attr_reader :addenda
55
-
56
- def initialize
57
- @addenda = []
58
- end
59
-
60
44
  def addenda_records?
61
45
  return !self.addenda.empty?
62
46
  end
63
47
 
64
48
  def to_ach
65
- self.addenda_record_indicator = (self.addenda.empty? ? 0 : 1)
66
- self.number_of_addenda_records = self.addenda.length
49
+ self.addenda_record_indicator = (self.addenda.empty? ? 0 : 1) if self.respond_to?(:addenda_record_indicator)
50
+ self.number_of_addenda_records = self.addenda.length if self.respond_to?(:number_of_addenda_records)
67
51
 
68
52
  ach_string = super
69
53
 
@@ -74,5 +58,22 @@ module ACH::Records
74
58
  return ach_string
75
59
  end
76
60
 
61
+ # @return [Integer] Length of addenda plus 1, used by Batch#entry_count
62
+ def records_count
63
+ 1 + self.addenda.length
64
+ end
65
+ end
66
+
67
+ class CtxEntryDetail < EntryDetail
68
+ @fields = EntryDetail.fields.slice(0, 6)
69
+ field :number_of_addenda_records, Integer, lambda { |f| sprintf('%04d', f)}, 0
70
+ field :individual_name, String, lambda { |f| left_justify(f, 16)}
71
+ const_field :reserved, ' '
72
+ field :discretionary_data, String, lambda { |f| left_justify(f, 2)}, ' '
73
+ field :addenda_record_indicator, Integer,
74
+ lambda { |f| sprintf('%01d', f)}
75
+ field :originating_dfi_identification, String,
76
+ nil, nil, /\A\d{8}\z/
77
+ field :trace_number, Integer, lambda { |f| sprintf('%07d', f)}
77
78
  end
78
79
  end
@@ -1,3 +1,3 @@
1
1
  module ACH
2
- VERSION = '0.4.7'.freeze
2
+ VERSION = '0.4.8'.freeze
3
3
  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.7
4
+ version: 0.4.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jared Morgan
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-14 00:00:00.000000000 Z
12
+ date: 2014-04-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -47,21 +47,22 @@ extensions: []
47
47
  extra_rdoc_files:
48
48
  - README.md
49
49
  files:
50
+ - MIT-LICENSE
51
+ - README.md
50
52
  - lib/ach.rb
51
- - lib/ach/field_identifiers.rb
53
+ - lib/ach/ach_file.rb
54
+ - lib/ach/ach_file.rb.orig
52
55
  - lib/ach/batch.rb
53
- - lib/ach/version.rb
54
- - lib/ach/records/file_header.rb
55
- - lib/ach/records/file_control.rb
56
+ - lib/ach/field_identifiers.rb
57
+ - lib/ach/records/addendum.rb
56
58
  - lib/ach/records/batch_control.rb
59
+ - lib/ach/records/batch_header.rb
57
60
  - lib/ach/records/entry_detail.rb
61
+ - lib/ach/records/file_control.rb
62
+ - lib/ach/records/file_header.rb
58
63
  - lib/ach/records/nines.rb
59
- - lib/ach/records/addendum.rb
60
64
  - lib/ach/records/record.rb
61
- - lib/ach/records/batch_header.rb
62
- - lib/ach/ach_file.rb
63
- - MIT-LICENSE
64
- - README.md
65
+ - lib/ach/version.rb
65
66
  homepage: https://github.com/jm81/ach
66
67
  licenses: []
67
68
  metadata: {}
@@ -81,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  version: '0'
82
83
  requirements: []
83
84
  rubyforge_project:
84
- rubygems_version: 2.1.10
85
+ rubygems_version: 2.2.0
85
86
  signing_key:
86
87
  specification_version: 4
87
88
  summary: Helper for building ACH files