ach 0.4.7 → 0.4.8

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