nacha 0.1.4 → 0.1.6
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 +4 -4
- data/CHANGELOG.md +81 -0
- data/exe/nacha +6 -9
- data/lib/nacha/aba_number.rb +18 -6
- data/lib/nacha/ach_date.rb +1 -1
- data/lib/nacha/field.rb +45 -6
- data/lib/nacha/numeric.rb +5 -7
- data/lib/nacha/parser_context.rb +1 -1
- data/lib/nacha/record/adv_file_control.rb +3 -3
- data/lib/nacha/record/base.rb +24 -16
- data/lib/nacha/record/batch_control.rb +1 -1
- data/lib/nacha/record/batch_header.rb +1 -1
- data/lib/nacha/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 782ae8de3e488c9404aab641bf27e71da0b9bc6d391d235dbec10fc8d750c27a
|
4
|
+
data.tar.gz: 76e485087bcdd967c437912b1318bd79f21e50c1e9ed6eb3dbd73ea3233b86ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 799751bb17453a1f28e7e0b362e7a06eb017445d5928c06e7130c15c6d7b6a6a427e2b96ecb05567fafc60107400c82e39e0c81799158f2d942af6839ba470b7
|
7
|
+
data.tar.gz: adf86d5aa5302590c53c0333e3ea7a32c5e0e3ed255da5bf8812d0d2e853173e31b8cf12a7ba1602b9c0420f1aa52eedd9c54f7698f90ba11f0a399bd787bb42
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.1.6] - 2025-06-20
|
11
|
+
|
12
|
+
- Better code coverage
|
13
|
+
|
14
|
+
- fixed an issue with jruby running tests
|
15
|
+
|
16
|
+
- Bump version to 0.1.6
|
17
|
+
|
18
|
+
|
19
|
+
## [0.1.5] - 2025-06-18
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
- Actual reporting of field errors now in the output html.
|
23
|
+
- Change Nacha::Record::BatchControl#reserved to optional
|
24
|
+
- Change Nacha::Record::BatchHeader#settlement_date_julian to optional
|
25
|
+
- bump version to 0.1.5
|
26
|
+
|
27
|
+
## [0.1.4] - 2025-06-14
|
28
|
+
|
29
|
+
### Added
|
30
|
+
- Store the original input line on parsed records for easier debugging.
|
31
|
+
|
32
|
+
### Changed
|
33
|
+
- Group metadata into a nested `metadata` object in `to_h` output for better organization.
|
34
|
+
|
35
|
+
### Fixed
|
36
|
+
- Resolved a module loading issue.
|
37
|
+
|
38
|
+
## [0.1.3] - 2025-06-14
|
39
|
+
|
40
|
+
### Added
|
41
|
+
- Introduced SimpleCov for tracking code coverage.
|
42
|
+
- Added comprehensive tests for `Nacha::Record::Filler`.
|
43
|
+
|
44
|
+
### Changed
|
45
|
+
- Improved parsing with stricter matching for numeric fields and better handling of records not 94 bytes long.
|
46
|
+
- Enhanced HTML output with better class handling.
|
47
|
+
- Refactored `Nacha::Numeric` for better clarity and added more tests.
|
48
|
+
|
49
|
+
### Fixed
|
50
|
+
- Improved error parsing and reporting.
|
51
|
+
- Corrected memoization for `Nacha::Record.matcher`.
|
52
|
+
|
53
|
+
## [0.1.2] - 2025-06-04
|
54
|
+
|
55
|
+
### Added
|
56
|
+
- Introduced a command-line interface (CLI) using Thor.
|
57
|
+
- The CLI includes a `parse` command to process ACH files and output a summary to the console or generate an HTML representation.
|
58
|
+
|
59
|
+
## [0.1.1] - 2025-05-31
|
60
|
+
|
61
|
+
### Fixed
|
62
|
+
- Corrected an issue with `Nacha::AchDate` parsing on JRuby.
|
63
|
+
- Fixed gem file loading in Rails environments.
|
64
|
+
- Replaced deprecated `BigDecimal.new` with `BigDecimal()`.
|
65
|
+
|
66
|
+
### Removed
|
67
|
+
- Removed `pry` as a dependency.
|
68
|
+
|
69
|
+
## [0.1.0] - 2017-03-06
|
70
|
+
|
71
|
+
### Added
|
72
|
+
- Initial release of the Nacha gem.
|
73
|
+
- Core functionality to define NACHA record types and their fields.
|
74
|
+
- Parser for processing NACHA files from strings.
|
75
|
+
- Ability to generate NACHA-formatted strings from record objects (`to_ach`).
|
76
|
+
- Ability to convert records to JSON (`to_json`) and Hashes (`to_h`).
|
77
|
+
- Initial implementation of field and record validations.
|
78
|
+
- Extensive test suite using RSpec and later FactoryBot.
|
79
|
+
|
80
|
+
### Changed
|
81
|
+
- Major architectural refactoring to define records in pure Ruby classes (`nacha_field`) instead of external YAML files.
|
data/exe/nacha
CHANGED
@@ -36,10 +36,12 @@ module Nacha
|
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
|
-
def output_html(file_path,
|
39
|
+
def output_html(file_path, ach_records)
|
40
40
|
puts html_preamble
|
41
41
|
puts "<h1>Successfully parsed #{file_path}</h1>\n"
|
42
|
-
|
42
|
+
ach_records.each do |record|
|
43
|
+
display_child(0, record)
|
44
|
+
end
|
43
45
|
puts html_postamble
|
44
46
|
end
|
45
47
|
|
@@ -55,17 +57,12 @@ module Nacha
|
|
55
57
|
# Attempt to call a summary or to_s method if it exists,
|
56
58
|
# otherwise inspect the record.
|
57
59
|
return unless record
|
58
|
-
level_indent = ' ' * level.to_i
|
59
|
-
puts "<html>"
|
60
60
|
puts record.to_html
|
61
61
|
if record.respond_to?(:children) && record.children.any?
|
62
|
-
|
63
|
-
|
64
|
-
display_child(level + 1, child_record)
|
65
|
-
end
|
62
|
+
record.children.each do |child_record|
|
63
|
+
display_child(level + 1, child_record)
|
66
64
|
end
|
67
65
|
end
|
68
|
-
puts "</html>"
|
69
66
|
end
|
70
67
|
end
|
71
68
|
end
|
data/lib/nacha/aba_number.rb
CHANGED
@@ -9,6 +9,7 @@ class Nacha::AbaNumber
|
|
9
9
|
include HasErrors
|
10
10
|
|
11
11
|
def initialize(routing_number)
|
12
|
+
@errors = []
|
12
13
|
self.routing_number = routing_number
|
13
14
|
end
|
14
15
|
|
@@ -23,7 +24,8 @@ class Nacha::AbaNumber
|
|
23
24
|
|
24
25
|
def routing_number=(val)
|
25
26
|
@valid = nil
|
26
|
-
@
|
27
|
+
@errors&.clear
|
28
|
+
@routing_number = val.to_s.strip
|
27
29
|
end
|
28
30
|
|
29
31
|
def add_check_digit
|
@@ -35,15 +37,25 @@ class Nacha::AbaNumber
|
|
35
37
|
end
|
36
38
|
|
37
39
|
def valid?
|
38
|
-
@valid ||= valid_routing_number_length?
|
40
|
+
@valid ||= if valid_routing_number_length?
|
41
|
+
if @routing_number.length == 9
|
42
|
+
valid_check_digit?
|
43
|
+
else # 8 digits is valid
|
44
|
+
true
|
45
|
+
end
|
46
|
+
else
|
47
|
+
false
|
48
|
+
end
|
39
49
|
end
|
40
50
|
|
41
51
|
def valid_routing_number_length?
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
else
|
52
|
+
actual_length = @routing_number.length
|
53
|
+
|
54
|
+
if [9, 10].include?(actual_length)
|
46
55
|
true
|
56
|
+
else
|
57
|
+
add_error("Routing number must be 8 or 9 digits long, but was #{actual_length} digits long.")
|
58
|
+
false
|
47
59
|
end
|
48
60
|
end
|
49
61
|
|
data/lib/nacha/ach_date.rb
CHANGED
@@ -36,7 +36,7 @@ class Nacha::AchDate < Date
|
|
36
36
|
# This works because `Date.new` is designed to be effectively `allocate.initialize`.
|
37
37
|
super(year, month, day)
|
38
38
|
|
39
|
-
rescue ArgumentError => e
|
39
|
+
rescue TypeError, ArgumentError => e
|
40
40
|
# Catch errors that might arise from strptime or invalid date components
|
41
41
|
raise ArgumentError, "Invalid date format for Nacha::AchDate: #{args.inspect}. Original error: #{e.message}"
|
42
42
|
end
|
data/lib/nacha/field.rb
CHANGED
@@ -25,6 +25,8 @@ class Nacha::Field
|
|
25
25
|
@fill_character = ' '
|
26
26
|
@json_output = [:to_s]
|
27
27
|
@output_conversion = [:to_s]
|
28
|
+
@validated = false
|
29
|
+
@data_assigned = false
|
28
30
|
opts.each do |k, v|
|
29
31
|
setter = "#{k}="
|
30
32
|
if respond_to?(setter)
|
@@ -38,8 +40,6 @@ class Nacha::Field
|
|
38
40
|
def contents=(val)
|
39
41
|
@contents = val
|
40
42
|
case @contents
|
41
|
-
when /\AC(.*)\z/ # Constant
|
42
|
-
@data = Regexp.last_match(1)
|
43
43
|
when /\$.*¢*/
|
44
44
|
@data_type = Nacha::Numeric
|
45
45
|
@justification = :rjust
|
@@ -71,10 +71,15 @@ class Nacha::Field
|
|
71
71
|
@justification = :ljust
|
72
72
|
@output_conversion = [:to_s]
|
73
73
|
@fill_character = ' '
|
74
|
+
when /\AC(.*)\z/ # Constant
|
75
|
+
@data = Regexp.last_match(1)
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
77
79
|
def data=(val)
|
80
|
+
@validated = false
|
81
|
+
@data_assigned = true
|
82
|
+
@errors = []
|
78
83
|
@data = @data_type.new(val)
|
79
84
|
@input_data = val
|
80
85
|
rescue StandardError => e
|
@@ -96,10 +101,44 @@ class Nacha::Field
|
|
96
101
|
@inclusion == 'O'
|
97
102
|
end
|
98
103
|
|
104
|
+
def validate
|
105
|
+
return if @validated
|
106
|
+
|
107
|
+
add_error("'inclusion' must be present for a field definition.") unless @inclusion
|
108
|
+
add_error("'position' must be present for a field definition.") unless @position
|
109
|
+
add_error("'contents' must be present for a field definition.") unless @contents
|
110
|
+
|
111
|
+
if @data_assigned && (mandatory? || required?) && (@input_data.nil? || @input_data.to_s.strip.empty?)
|
112
|
+
add_error("'#{human_name}' is a required field and cannot be blank.")
|
113
|
+
end
|
114
|
+
|
115
|
+
# Type-specific validations
|
116
|
+
if @data_type == Nacha::Numeric && @input_data.to_s.strip.match(/\D/)
|
117
|
+
add_error("Invalid characters in numeric field '#{human_name}'. Got '#{@input_data}'.")
|
118
|
+
end
|
119
|
+
|
120
|
+
# If data object has its own validation, run it.
|
121
|
+
if @validator && @data.is_a?(@data_type)
|
122
|
+
# The call to the validator might populate errors on the data object.
|
123
|
+
is_valid = @data.send(@validator)
|
124
|
+
|
125
|
+
# Collect any errors from the data object.
|
126
|
+
if @data.respond_to?(:errors) && @data.errors && @data.errors.any?
|
127
|
+
@data.errors.each { |e| add_error(e) }
|
128
|
+
end
|
129
|
+
|
130
|
+
# If it's not valid and we haven't collected any specific errors, add a generic one.
|
131
|
+
if !is_valid && errors.empty?
|
132
|
+
add_error("'#{human_name}' is invalid. Got '#{@input_data}'.")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
@validated = true
|
137
|
+
end
|
138
|
+
|
99
139
|
def valid?
|
100
|
-
|
101
|
-
|
102
|
-
@valid
|
140
|
+
validate
|
141
|
+
errors.empty?
|
103
142
|
end
|
104
143
|
|
105
144
|
def add_error(err_string)
|
@@ -138,7 +177,7 @@ class Nacha::Field
|
|
138
177
|
end
|
139
178
|
|
140
179
|
def to_html
|
141
|
-
tooltip_text = "<span class=\"tooltiptext\" >#{human_name}</span>"
|
180
|
+
tooltip_text = "<span class=\"tooltiptext\" >#{human_name} #{errors.join(' ')}</span>"
|
142
181
|
field_classes = ["nacha-field tooltip"]
|
143
182
|
field_classes += ['mandatory'] if mandatory?
|
144
183
|
field_classes += ['required'] if required?
|
data/lib/nacha/numeric.rb
CHANGED
@@ -5,14 +5,12 @@ class Nacha::Numeric
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def to_i
|
8
|
-
if @value
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@value.to_i
|
13
|
-
end
|
8
|
+
return 0 if @value.nil?
|
9
|
+
|
10
|
+
if @value.is_a?(String) && @value.match(/\A *\z/)
|
11
|
+
@value # blank strings should return as blank
|
14
12
|
else
|
15
|
-
|
13
|
+
@value.to_i
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
data/lib/nacha/parser_context.rb
CHANGED
@@ -11,7 +11,7 @@ class Nacha::ParserContext
|
|
11
11
|
attr_accessor :previous_record
|
12
12
|
attr_reader :validated
|
13
13
|
|
14
|
-
def initialize
|
14
|
+
def initialize(opts = {})
|
15
15
|
@file_name = opts[:file_name] || ''
|
16
16
|
@line_number = opts[:line_number] || 0
|
17
17
|
@line_length = opts[:line_length] || 0
|
@@ -16,9 +16,9 @@ module Nacha
|
|
16
16
|
nacha_field :total_credit_entry_dollar_amount_in_file, inclusion: 'M', contents: '$$$$$$$$$$¢¢', position: 52..71
|
17
17
|
nacha_field :reserved, inclusion: 'M', contents: 'C', position: 72..94
|
18
18
|
|
19
|
-
def initialize
|
20
|
-
create_fields_from_definition
|
21
|
-
end
|
19
|
+
# def initialize
|
20
|
+
# create_fields_from_definition
|
21
|
+
# end
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
data/lib/nacha/record/base.rb
CHANGED
@@ -10,12 +10,13 @@ module Nacha
|
|
10
10
|
include Validations::FieldValidations
|
11
11
|
|
12
12
|
attr_accessor :children, :parent, :fields
|
13
|
-
attr_reader :name, :validations, :errors
|
13
|
+
attr_reader :name, :validations, :errors, :original_input_line
|
14
14
|
attr_accessor :line_number
|
15
15
|
|
16
16
|
def initialize(opts = {})
|
17
17
|
@children = []
|
18
18
|
@errors = []
|
19
|
+
@original_input_line = nil
|
19
20
|
create_fields_from_definition
|
20
21
|
opts.each do |k, v|
|
21
22
|
setter = "#{k}="
|
@@ -113,8 +114,14 @@ module Nacha
|
|
113
114
|
ach_str.unpack(unpack_str).zip(rec.fields.values) do |input_data, field|
|
114
115
|
field.data = input_data
|
115
116
|
end
|
117
|
+
rec.original_input_line = ach_str
|
118
|
+
rec.validate
|
116
119
|
rec
|
117
120
|
end
|
121
|
+
end # end class methods
|
122
|
+
|
123
|
+
def original_input_line=(line)
|
124
|
+
@original_input_line = line.dup if line.is_a?(String)
|
118
125
|
end
|
119
126
|
|
120
127
|
def create_fields_from_definition
|
@@ -133,7 +140,13 @@ module Nacha
|
|
133
140
|
end
|
134
141
|
|
135
142
|
def to_h
|
136
|
-
{ nacha_record_type: record_type
|
143
|
+
{ nacha_record_type: record_type,
|
144
|
+
metadata: {
|
145
|
+
errors: errors,
|
146
|
+
line_number: @line_number,
|
147
|
+
original_input_line: original_input_line
|
148
|
+
}
|
149
|
+
}.merge(
|
137
150
|
@fields.keys.map do |key|
|
138
151
|
[key, @fields[key].to_json_output]
|
139
152
|
end.to_h)
|
@@ -172,8 +185,12 @@ module Nacha
|
|
172
185
|
end
|
173
186
|
|
174
187
|
def validate
|
175
|
-
|
176
|
-
|
188
|
+
# Run field-level validations first
|
189
|
+
@fields.values.each(&:validate)
|
190
|
+
|
191
|
+
# Then run record-level validations that might depend on multiple fields
|
192
|
+
self.class.definition.keys.each do |field|
|
193
|
+
next unless self.class.validations[field]
|
177
194
|
|
178
195
|
# rubocop:disable GitlabSecurity/PublicSend
|
179
196
|
field_data = send(field)
|
@@ -182,22 +199,13 @@ module Nacha
|
|
182
199
|
self.class.send(validation_method, field_data)
|
183
200
|
end
|
184
201
|
# rubocop:enable GitlabSecurity/PublicSend
|
185
|
-
end
|
202
|
+
end
|
186
203
|
end
|
187
204
|
|
188
205
|
# look for invalid fields, if none, then return true
|
189
206
|
def valid?
|
190
|
-
|
191
|
-
|
192
|
-
# field = send(field_sym)
|
193
|
-
# # rubocop:enable GitlabSecurity/PublicSend
|
194
|
-
# next true unless field.mandatory?
|
195
|
-
|
196
|
-
# ## TODO: levels of validity with 'R' and 'O' fields
|
197
|
-
# field.valid?
|
198
|
-
# end
|
199
|
-
statuses = validate
|
200
|
-
(statuses.detect { |valid| valid == false } != false)
|
207
|
+
validate
|
208
|
+
errors.empty?
|
201
209
|
end
|
202
210
|
|
203
211
|
# Checks if the current transaction code represents a debit transaction.
|
@@ -16,7 +16,7 @@ module Nacha
|
|
16
16
|
nacha_field :total_credit_entry_dollar_amount, inclusion: 'M', contents: '$$$$$$$$$$¢¢', position: 33..44
|
17
17
|
nacha_field :company_identification, inclusion: 'R', contents: 'Alphameric', position: 45..54
|
18
18
|
nacha_field :message_authentication_code, inclusion: 'O', contents: 'Alphameric', position: 55..73
|
19
|
-
nacha_field :reserved, inclusion: '
|
19
|
+
nacha_field :reserved, inclusion: 'O', contents: 'C ', position: 74..79
|
20
20
|
nacha_field :originating_dfi_identification, inclusion: 'M', contents: 'TTTTAAAA', position: 80..87
|
21
21
|
nacha_field :batch_number, inclusion: 'M', contents: 'Numeric', position: 88..94
|
22
22
|
end
|
@@ -17,7 +17,7 @@ module Nacha
|
|
17
17
|
nacha_field :company_entry_description, inclusion: 'M', contents: 'Alphameric', position: 54..63
|
18
18
|
nacha_field :company_descriptive_date, inclusion: 'O', contents: 'Alphameric', position: 64..69
|
19
19
|
nacha_field :effective_entry_date, inclusion: 'R', contents: 'YYMMDD', position: 70..75
|
20
|
-
nacha_field :settlement_date_julian, inclusion: '
|
20
|
+
nacha_field :settlement_date_julian, inclusion: 'O', contents: 'Numeric', position: 76..78
|
21
21
|
nacha_field :originator_status_code, inclusion: 'M', contents: 'Alphameric', position: 79..79
|
22
22
|
nacha_field :originating_dfi_identification, inclusion: 'M', contents: 'TTTTAAAA', position: 80..87
|
23
23
|
nacha_field :batch_number, inclusion: 'M', contents: 'Numeric', position: 88..94
|
data/lib/nacha/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nacha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David H. Wilkins
|
@@ -220,6 +220,7 @@ files:
|
|
220
220
|
- ".ruby-gemset"
|
221
221
|
- ".ruby-version"
|
222
222
|
- ".travis.yml"
|
223
|
+
- CHANGELOG.md
|
223
224
|
- Gemfile
|
224
225
|
- Guardfile
|
225
226
|
- LICENSE.txt
|