nacha 0.1.8 → 0.1.12
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/.rubocop.yml +4 -3
- data/.ruby-version +1 -1
- data/CHANGELOG.md +41 -0
- data/README.md +1 -1
- data/exe/nacha +50 -20
- data/lib/nacha/aba_number.rb +17 -14
- data/lib/nacha/ach_date.rb +15 -8
- data/lib/nacha/field.rb +69 -58
- data/lib/nacha/has_errors.rb +12 -8
- data/lib/nacha/numeric.rb +13 -10
- data/lib/nacha/parser.rb +33 -16
- data/lib/nacha/parser_context.rb +4 -9
- data/lib/nacha/record/ack_entry_detail.rb +15 -8
- data/lib/nacha/record/adv_batch_control.rb +13 -6
- data/lib/nacha/record/adv_entry_detail.rb +3 -2
- data/lib/nacha/record/adv_file_control.rb +10 -9
- data/lib/nacha/record/adv_file_header.rb +12 -7
- data/lib/nacha/record/arc_entry_detail.rb +2 -2
- data/lib/nacha/record/base.rb +128 -108
- data/lib/nacha/record/batch_control.rb +13 -7
- data/lib/nacha/record/batch_header.rb +20 -11
- data/lib/nacha/record/batch_header_record_type.rb +1 -1
- data/lib/nacha/record/boc_entry_detail.rb +3 -2
- data/lib/nacha/record/ccd_addenda.rb +2 -2
- data/lib/nacha/record/ccd_entry_detail.rb +3 -2
- data/lib/nacha/record/cie_addenda.rb +2 -2
- data/lib/nacha/record/cie_entry_detail.rb +5 -3
- data/lib/nacha/record/ctx_addenda.rb +2 -2
- data/lib/nacha/record/ctx_corporate_entry_detail.rb +2 -2
- data/lib/nacha/record/dne_addenda.rb +2 -2
- data/lib/nacha/record/dne_entry_detail.rb +6 -4
- data/lib/nacha/record/enr_addenda.rb +3 -2
- data/lib/nacha/record/enr_entry_detail.rb +4 -3
- data/lib/nacha/record/fifth_iat_addenda.rb +8 -4
- data/lib/nacha/record/file_control.rb +9 -5
- data/lib/nacha/record/file_control_record_type.rb +1 -1
- data/lib/nacha/record/file_header.rb +13 -7
- data/lib/nacha/record/file_header_record_type.rb +1 -1
- data/lib/nacha/record/filler.rb +3 -3
- data/lib/nacha/record/filler_record_type.rb +3 -1
- data/lib/nacha/record/first_iat_addenda.rb +3 -2
- data/lib/nacha/record/fourth_iat_addenda.rb +7 -4
- data/lib/nacha/record/iat_batch_header.rb +5 -3
- data/lib/nacha/record/iat_entry_detail.rb +7 -4
- data/lib/nacha/record/iat_foreign_coorespondent_bank_information_addenda.rb +10 -6
- data/lib/nacha/record/iat_remittance_information_addenda.rb +3 -2
- data/lib/nacha/record/mte_addenda.rb +4 -3
- data/lib/nacha/record/mte_entry_detail.rb +5 -3
- data/lib/nacha/record/pop_entry_detail.rb +3 -2
- data/lib/nacha/record/pos_addenda.rb +6 -3
- data/lib/nacha/record/pos_entry_detail.rb +5 -3
- data/lib/nacha/record/ppd_addenda.rb +3 -2
- data/lib/nacha/record/ppd_entry_detail.rb +5 -3
- data/lib/nacha/record/rck_entry_detail.rb +3 -2
- data/lib/nacha/record/second_iat_addenda.rb +3 -2
- data/lib/nacha/record/seventh_iat_addenda.rb +3 -2
- data/lib/nacha/record/shr_addenda.rb +5 -3
- data/lib/nacha/record/shr_entry_detail.rb +3 -2
- data/lib/nacha/record/sixth_iat_addenda.rb +5 -3
- data/lib/nacha/record/tel_entry_detail.rb +5 -3
- data/lib/nacha/record/third_iat_addenda.rb +3 -2
- data/lib/nacha/record/trc_entry_detail.rb +3 -2
- data/lib/nacha/record/trx_addenda.rb +3 -2
- data/lib/nacha/record/trx_entry_detail.rb +5 -3
- data/lib/nacha/record/validations/field_validations.rb +26 -14
- data/lib/nacha/record/validations/record_validations.rb +2 -1
- data/lib/nacha/record/web_addenda.rb +3 -2
- data/lib/nacha/record/web_entry_detail.rb +5 -3
- data/lib/nacha/record/xck_entry_detail.rb +3 -2
- data/lib/nacha/version.rb +4 -1
- data/lib/nacha.rb +84 -20
- data/nacha.gemspec +13 -10
- metadata +38 -21
data/lib/nacha/parser.rb
CHANGED
@@ -1,33 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'byebug'
|
3
4
|
require 'nacha'
|
4
5
|
require 'nacha/parser_context'
|
5
6
|
|
7
|
+
# Nacha Parser - deal with figuring out what record type a line is
|
6
8
|
class Nacha::Parser
|
7
|
-
DEFAULT_RECORD_TYPES = [
|
8
|
-
|
9
|
+
DEFAULT_RECORD_TYPES = [
|
10
|
+
'Nacha::Record::FileHeader',
|
11
|
+
'Nacha::Record::AdvFileHeader',
|
12
|
+
'Nacha::Record::Filler'
|
13
|
+
].freeze
|
9
14
|
|
10
15
|
attr_reader :context
|
11
16
|
|
12
17
|
def initialize
|
13
18
|
@context = Nacha::ParserContext.new
|
14
|
-
reset!
|
15
19
|
end
|
16
20
|
|
17
|
-
def reset!; end
|
18
|
-
|
19
21
|
def parse_file(file)
|
20
|
-
@context.parser_started_at = Time.now
|
22
|
+
@context.parser_started_at = Time.now.utc
|
21
23
|
@context.file_name = file
|
22
24
|
parse_string(file.read)
|
23
25
|
end
|
24
26
|
|
27
|
+
def detect_possible_record_types(line)
|
28
|
+
Nacha.ach_record_types.filter_map do |record_type|
|
29
|
+
record_type if record_type.matcher.match?(line)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
25
33
|
def parse_string(str)
|
26
34
|
line_num = -1
|
27
35
|
records = []
|
28
|
-
@context.parser_started_at ||= Time.now
|
29
|
-
str.scan(/(.{94}
|
30
|
-
line = line.first.strip
|
36
|
+
@context.parser_started_at ||= Time.now.utc
|
37
|
+
str.scan(/(.{0,94})[\r\n]*/).each do |line|
|
38
|
+
line = line.compact.first.strip
|
39
|
+
next if line.empty? || line.start_with?('#') # Skip empty lines and comments
|
40
|
+
|
31
41
|
line_num += 1
|
32
42
|
@context.line_number = line_num
|
33
43
|
@context.line_length = line.length
|
@@ -39,12 +49,13 @@ class Nacha::Parser
|
|
39
49
|
def process(line, line_num, previous = nil)
|
40
50
|
@context.line_errors = []
|
41
51
|
parent = previous
|
42
|
-
record = nil
|
43
52
|
|
44
53
|
record_types = valid_record_types(parent)
|
54
|
+
|
45
55
|
while record_types
|
46
|
-
record =
|
56
|
+
record = parse_first_by_types(line, record_types)
|
47
57
|
break if record || !parent
|
58
|
+
|
48
59
|
record.validate if record
|
49
60
|
parent = parent.parent
|
50
61
|
record_types = valid_record_types(parent)
|
@@ -67,11 +78,17 @@ class Nacha::Parser
|
|
67
78
|
child.parent = parent
|
68
79
|
end
|
69
80
|
|
70
|
-
def
|
71
|
-
record_types.
|
72
|
-
record_type = Object.const_get(rt)
|
73
|
-
|
74
|
-
|
81
|
+
def parse_first_by_types(line, record_types)
|
82
|
+
record_types.lazy.filter_map do |rt|
|
83
|
+
record_type = rt.is_a?(Class) ? rt : Object.const_get(rt)
|
84
|
+
record_type.parse(line) if record_type.matcher.match?(line)
|
85
|
+
end.first
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_all_by_types(line, record_types)
|
89
|
+
record_types.filter_map do |rt|
|
90
|
+
record_type = rt.is_a?(Class) ? rt : Object.const_get(rt)
|
91
|
+
record_type.parse(line) if record_type.matcher.match?(line)
|
75
92
|
end
|
76
93
|
end
|
77
94
|
end
|
data/lib/nacha/parser_context.rb
CHANGED
@@ -2,13 +2,9 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
class Nacha::ParserContext
|
5
|
-
attr_accessor :file_name
|
6
|
-
|
7
|
-
|
8
|
-
attr_accessor :line_errors
|
9
|
-
attr_accessor :parser_started_at
|
10
|
-
attr_accessor :parser_ended_at # nil 'till the end of parsing
|
11
|
-
attr_accessor :previous_record
|
5
|
+
attr_accessor :file_name, :line_number, :line_length, :line_errors,
|
6
|
+
:parser_started_at, :previous_record,
|
7
|
+
:parser_ended_at # nil 'till the end of parsing
|
12
8
|
attr_reader :validated
|
13
9
|
|
14
10
|
def initialize(opts = {})
|
@@ -16,7 +12,7 @@ class Nacha::ParserContext
|
|
16
12
|
@line_number = opts[:line_number] || 0
|
17
13
|
@line_length = opts[:line_length] || 0
|
18
14
|
@current_line_errors = []
|
19
|
-
@parser_started_at = Time.now
|
15
|
+
@parser_started_at = Time.now.utc
|
20
16
|
@parser_ended_at = nil
|
21
17
|
@previous_record = nil
|
22
18
|
@validated = false
|
@@ -29,5 +25,4 @@ class Nacha::ParserContext
|
|
29
25
|
def valid?
|
30
26
|
@valid ||= errors.empty?
|
31
27
|
end
|
32
|
-
|
33
28
|
end
|
@@ -1,22 +1,29 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'nacha/record/base
|
5
|
-
require 'nacha/record/detail_record_type
|
4
|
+
require 'nacha/record/base'
|
5
|
+
require 'nacha/record/detail_record_type'
|
6
6
|
|
7
7
|
module Nacha
|
8
8
|
module Record
|
9
9
|
class AckEntryDetail < Nacha::Record::Base
|
10
10
|
include DetailRecordType
|
11
|
+
|
11
12
|
nacha_field :record_type_code, inclusion: 'M', contents: 'C6', position: 1..1
|
12
13
|
nacha_field :transaction_code, inclusion: 'M', contents: 'Numeric', position: 2..3
|
13
|
-
nacha_field :receiving_dfi_identification,
|
14
|
-
|
14
|
+
nacha_field :receiving_dfi_identification,
|
15
|
+
inclusion: 'M', contents: 'TTTTAAAAC', position: 4..12
|
16
|
+
nacha_field :dfi_account_number,
|
17
|
+
inclusion: 'R', contents: 'Alphameric', position: 13..29
|
15
18
|
nacha_field :amount, inclusion: 'M', contents: '$$$$$$$$¢¢', position: 30..39
|
16
|
-
nacha_field :originl_entry_trace_number,
|
17
|
-
|
18
|
-
nacha_field :
|
19
|
-
|
19
|
+
nacha_field :originl_entry_trace_number,
|
20
|
+
inclusion: 'M', contents: 'Numeric', position: 40..54
|
21
|
+
nacha_field :receiving_company_name,
|
22
|
+
inclusion: 'R', contents: 'Alphameric', position: 55..76
|
23
|
+
nacha_field :discretionary_data,
|
24
|
+
inclusion: 'O', contents: 'Alphameric', position: 77..78
|
25
|
+
nacha_field :addenda_record_indicator,
|
26
|
+
inclusion: 'M', contents: 'Numeric', position: 79..79
|
20
27
|
nacha_field :trace_number, inclusion: 'M', contents: 'Numeric', position: 80..94
|
21
28
|
end
|
22
29
|
end
|
@@ -1,20 +1,27 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'nacha/record/base
|
5
|
-
require 'nacha/record/batch_control_record_type
|
4
|
+
require 'nacha/record/base'
|
5
|
+
require 'nacha/record/batch_control_record_type'
|
6
6
|
|
7
7
|
module Nacha
|
8
8
|
module Record
|
9
|
+
# The AdvBatchControl record is used to control the batch of adv entry detail records.
|
9
10
|
class AdvBatchControl < Nacha::Record::Base
|
11
|
+
include BatchControlRecordType
|
12
|
+
|
10
13
|
nacha_field :record_type_code, inclusion: 'M', contents: 'C8', position: 1..1
|
11
14
|
nacha_field :service_class_code, inclusion: 'M', contents: 'Numeric', position: 2..4
|
12
15
|
nacha_field :entry_addenda_count, inclusion: 'M', contents: 'Numeric', position: 5..10
|
13
16
|
nacha_field :entry_hash, inclusion: 'M', contents: 'Numeric', position: 11..20
|
14
|
-
nacha_field :total_debit_entry_dollar_amount,
|
15
|
-
|
16
|
-
nacha_field :
|
17
|
-
|
17
|
+
nacha_field :total_debit_entry_dollar_amount,
|
18
|
+
inclusion: 'M', contents: '$$$$$$$$$$$$$$$$$$¢¢', position: 21..40
|
19
|
+
nacha_field :total_credit_entry_dollar_amount,
|
20
|
+
inclusion: 'M', contents: '$$$$$$$$$$$$$$$$$$¢¢', position: 41..60
|
21
|
+
nacha_field :ach_operator_data,
|
22
|
+
inclusion: 'O', contents: 'Alphameric', position: 61..79
|
23
|
+
nacha_field :originating_dfi_identification,
|
24
|
+
inclusion: 'M', contents: 'TTTAAAA', position: 80..87
|
18
25
|
nacha_field :batch_number, inclusion: 'M', contents: 'Numeric', position: 88..94
|
19
26
|
end
|
20
27
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'nacha/record/base
|
5
|
-
require 'nacha/record/detail_record_type
|
4
|
+
require 'nacha/record/base'
|
5
|
+
require 'nacha/record/detail_record_type'
|
6
6
|
|
7
7
|
module Nacha
|
8
8
|
module Record
|
9
9
|
class AdvEntryDetail < Nacha::Record::Base
|
10
10
|
include DetailRecordType
|
11
|
+
|
11
12
|
nacha_field :record_type_code, inclusion: 'M', contents: 'C6', position: 1..1
|
12
13
|
nacha_field :transaction_code, inclusion: 'M', contents: 'Numeric', position: 2..3
|
13
14
|
nacha_field :receiving_dfi_identification, inclusion: 'M', contents: 'TTTTAAAAC', position: 4..12
|
@@ -1,24 +1,25 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'nacha/record/base
|
5
|
-
require 'nacha/record/file_control_record_type
|
4
|
+
require 'nacha/record/base'
|
5
|
+
require 'nacha/record/file_control_record_type'
|
6
6
|
|
7
7
|
module Nacha
|
8
8
|
module Record
|
9
9
|
class AdvFileControl < Nacha::Record::Base
|
10
|
+
include FileControlRecordType
|
11
|
+
|
10
12
|
nacha_field :record_type_code, inclusion: 'M', contents: 'C9', position: 1..1
|
11
13
|
nacha_field :batch_count, inclusion: 'M', contents: 'Numeric', position: 2..7
|
12
14
|
nacha_field :block_count, inclusion: 'M', contents: 'Numeric', position: 8..13
|
13
|
-
nacha_field :entry_addenda_count,
|
15
|
+
nacha_field :entry_addenda_count,
|
16
|
+
inclusion: 'M', contents: 'Numeric', position: 14..21
|
14
17
|
nacha_field :entry_hash, inclusion: 'M', contents: 'Numeric', position: 22..31
|
15
|
-
nacha_field :total_debit_entry_dollar_amount_in_file,
|
16
|
-
|
18
|
+
nacha_field :total_debit_entry_dollar_amount_in_file,
|
19
|
+
inclusion: 'M', contents: '$$$$$$$$$$¢¢', position: 32..51
|
20
|
+
nacha_field :total_credit_entry_dollar_amount_in_file,
|
21
|
+
inclusion: 'M', contents: '$$$$$$$$$$¢¢', position: 52..71
|
17
22
|
nacha_field :reserved, inclusion: 'M', contents: 'C', position: 72..94
|
18
|
-
|
19
|
-
# def initialize
|
20
|
-
# create_fields_from_definition
|
21
|
-
# end
|
22
23
|
end
|
23
24
|
end
|
24
25
|
end
|
@@ -1,26 +1,31 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'nacha/record/base
|
5
|
-
require 'nacha/record/file_header_record_type
|
4
|
+
require 'nacha/record/base'
|
5
|
+
require 'nacha/record/file_header_record_type'
|
6
6
|
|
7
7
|
module Nacha
|
8
8
|
module Record
|
9
9
|
class AdvFileHeader < Nacha::Record::Base
|
10
10
|
include FileHeaderRecordType
|
11
|
+
|
11
12
|
nacha_field :record_type_code, inclusion: 'M', contents: 'C1', position: 1..1
|
12
13
|
nacha_field :priority_code, inclusion: 'R', contents: 'Numeric', position: 2..3
|
13
|
-
nacha_field :immediate_destination, inclusion: 'M',
|
14
|
-
|
14
|
+
nacha_field :immediate_destination, inclusion: 'M',
|
15
|
+
contents: 'bTTTTAAAAC', position: 4..13
|
16
|
+
nacha_field :immediate_origin, inclusion: 'M',
|
17
|
+
contents: 'bTTTTAAAAC', position: 14..23
|
15
18
|
nacha_field :file_creation_date, inclusion: 'M', contents: 'YYMMDD', position: 24..29
|
16
19
|
nacha_field :file_creation_time, inclusion: 'M', contents: 'HHMM', position: 30..33
|
17
20
|
nacha_field :file_id_modifier, inclusion: 'M', contents: 'A-Z0-9', position: 34..34
|
18
21
|
nacha_field :record_size, inclusion: 'M', contents: 'C094', position: 35..37
|
19
22
|
nacha_field :blocking_factor, inclusion: 'M', contents: 'C10', position: 38..39
|
20
23
|
nacha_field :format_code, inclusion: 'M', contents: 'C1', position: 40..40
|
21
|
-
nacha_field :immediate_destination_name, inclusion: 'M',
|
22
|
-
|
23
|
-
nacha_field :
|
24
|
+
nacha_field :immediate_destination_name, inclusion: 'M',
|
25
|
+
contents: 'Alphameric', position: 41..63
|
26
|
+
nacha_field :immediate_origin_name, inclusion: 'M',
|
27
|
+
contents: 'Alphameric', position: 64..86
|
28
|
+
nacha_field :reference_code, inclusion: 'M', contents: 'CADV FILE', position: 87..94
|
24
29
|
end
|
25
30
|
end
|
26
31
|
end
|
data/lib/nacha/record/base.rb
CHANGED
@@ -4,42 +4,47 @@ require 'json'
|
|
4
4
|
require 'nacha/field'
|
5
5
|
require 'nacha/record/validations/field_validations'
|
6
6
|
|
7
|
+
# :reek:TooManyInstanceVariables, :reek:TooManyMethods
|
7
8
|
module Nacha
|
8
9
|
module Record
|
10
|
+
# Base class for all Nacha records.
|
9
11
|
class Base
|
10
12
|
include Validations::FieldValidations
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
attr_accessor :line_number
|
14
|
+
attr_reader :children, :name, :validations, :original_input_line, :fields
|
15
|
+
# :reek:Attribute
|
16
|
+
attr_accessor :parent, :line_number
|
15
17
|
|
18
|
+
# :reek:ManualDispatch
|
16
19
|
def initialize(opts = {})
|
17
20
|
@children = []
|
21
|
+
@parent = nil
|
18
22
|
@errors = []
|
19
23
|
@original_input_line = nil
|
24
|
+
@line_number = nil
|
25
|
+
@dirty = false
|
26
|
+
@fields = {}
|
20
27
|
create_fields_from_definition
|
21
|
-
opts.each do |
|
22
|
-
setter = "#{
|
23
|
-
next unless respond_to? setter
|
24
|
-
|
28
|
+
opts.each do |key, value|
|
29
|
+
setter = "#{key}="
|
25
30
|
# rubocop:disable GitlabSecurity/PublicSend
|
26
|
-
|
31
|
+
public_send(setter, value) if value && respond_to?(setter)
|
27
32
|
# rubocop:enable GitlabSecurity/PublicSend
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
31
36
|
class << self
|
37
|
+
# :reek:LongParameterList, :reek:ManualDispatch
|
32
38
|
def nacha_field(name, inclusion:, contents:, position:)
|
39
|
+
Nacha.add_ach_record_type(self)
|
33
40
|
definition[name] = { inclusion: inclusion,
|
34
41
|
contents: contents,
|
35
42
|
position: position,
|
36
|
-
name: name}
|
37
|
-
validation_method = "valid_#{name}"
|
43
|
+
name: name }
|
44
|
+
validation_method = :"valid_#{name}"
|
38
45
|
return unless respond_to?(validation_method)
|
39
46
|
|
40
|
-
validations[name] ||= []
|
41
|
-
validations[name] << validation_method
|
42
|
-
Nacha.add_ach_record_type(self)
|
47
|
+
(validations[name] ||= []) << validation_method
|
43
48
|
end
|
44
49
|
|
45
50
|
def definition
|
@@ -51,90 +56,94 @@ module Nacha
|
|
51
56
|
end
|
52
57
|
|
53
58
|
def unpack_str
|
54
|
-
@unpack_str ||= definition.values.collect do |
|
55
|
-
Nacha::Field.unpack_str(
|
59
|
+
@unpack_str ||= definition.values.collect do |field_def|
|
60
|
+
Nacha::Field.unpack_str(field_def)
|
56
61
|
end.join.freeze
|
57
62
|
end
|
58
63
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
if d[:contents] =~ /\AC(.+)\z/ || d['contents'] =~ /\AC(.+)\z/
|
63
|
-
Regexp.last_match(1)
|
64
|
-
else
|
65
|
-
'.' * (d[:position] || d['position']).size
|
66
|
-
end
|
67
|
-
end.join + '\z')
|
68
|
-
end
|
69
|
-
|
70
|
-
# A more strict matcher that accounts for numeric and date fields
|
71
|
-
# and allows for spaces in those fields, but not alphabetic characters
|
72
|
-
# Also matches strings that might not be long enough.
|
73
|
-
#
|
74
|
-
# Processes the definition in reverse order to allow later fields to be
|
75
|
-
# skipped if they are not present in the input string.
|
64
|
+
# :reek:TooManyStatements
|
65
|
+
# rubocop:disable Layout/BlockAlignment,
|
66
|
+
# rubocop:disable Style/StringConcatenation:
|
76
67
|
def matcher
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
68
|
+
@matcher ||= begin
|
69
|
+
output_started = false
|
70
|
+
skipped_output = false
|
71
|
+
Regexp.new('\A' + definition.values.reverse.collect do |field|
|
72
|
+
contents = field[:contents]
|
73
|
+
position = field[:position]
|
74
|
+
size = position.size
|
75
|
+
if contents =~ /\AC(.+)\z/
|
84
76
|
last_match = Regexp.last_match(1)
|
85
|
-
if last_match
|
77
|
+
if last_match.match?(/ */) && !output_started
|
86
78
|
skipped_output = true
|
87
79
|
''
|
88
80
|
else
|
89
81
|
output_started = true
|
90
82
|
last_match
|
91
83
|
end
|
92
|
-
elsif
|
84
|
+
elsif contents.match?(/\ANumeric\z/) || contents.match?(/\AYYMMDD\z/)
|
93
85
|
output_started = true
|
94
|
-
'[0-9 ]' + "{#{
|
95
|
-
elsif
|
96
|
-
|
97
|
-
'[0-9 ]' + "{#{(d[:position] || d['position']).size}}"
|
98
|
-
else
|
99
|
-
skipped_output = true
|
100
|
-
''
|
101
|
-
end
|
86
|
+
'[0-9 ]' + "{#{size}}"
|
87
|
+
elsif output_started
|
88
|
+
'.' + "{#{size}}"
|
102
89
|
else
|
103
|
-
|
104
|
-
|
105
|
-
else
|
106
|
-
skipped_output = true
|
107
|
-
''
|
108
|
-
end
|
90
|
+
skipped_output = true
|
91
|
+
''
|
109
92
|
end
|
110
93
|
end.reverse.join + (skipped_output ? '.*' : '') + '\z')
|
94
|
+
end
|
111
95
|
end
|
112
|
-
|
96
|
+
# rubocop:enable Layout/BlockAlignment,
|
97
|
+
# rubocop:enable Style/StringConcatenation:
|
113
98
|
|
114
99
|
def parse(ach_str)
|
115
|
-
rec = new
|
100
|
+
rec = new(original_input_line: ach_str)
|
116
101
|
ach_str.unpack(unpack_str).zip(rec.fields.values) do |input_data, field|
|
117
102
|
field.data = input_data
|
118
103
|
end
|
119
|
-
rec.original_input_line = ach_str
|
120
104
|
rec.validate
|
121
105
|
rec
|
122
106
|
end
|
123
|
-
end # end class methods
|
124
107
|
|
108
|
+
def record_type
|
109
|
+
Nacha.record_name(self)
|
110
|
+
end
|
111
|
+
|
112
|
+
# :reek:ManualDispatch, :reek:TooManyStatements
|
113
|
+
def to_h
|
114
|
+
fields = definition.transform_values do |field_def|
|
115
|
+
{
|
116
|
+
inclusion: field_def[:inclusion],
|
117
|
+
contents: field_def[:contents],
|
118
|
+
position: field_def[:position].to_s
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
fields[:child_record_types] = child_record_types.to_a
|
123
|
+
fields[:child_record_types] ||= []
|
124
|
+
fields[:klass] = name.to_s
|
125
|
+
|
126
|
+
{ record_type.to_sym => fields }
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_json(*_args)
|
130
|
+
JSON.pretty_generate(to_h)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# :reek:FeatureEnvy
|
125
135
|
def original_input_line=(line)
|
126
136
|
@original_input_line = line.dup if line.is_a?(String)
|
127
137
|
end
|
128
138
|
|
129
139
|
def create_fields_from_definition
|
130
|
-
@fields ||= {}
|
131
140
|
definition.each_pair do |field_name, field_def|
|
132
141
|
@fields[field_name.to_sym] = Nacha::Field.new(field_def)
|
133
142
|
end
|
134
143
|
end
|
135
144
|
|
136
145
|
def record_type
|
137
|
-
|
146
|
+
self.class.record_type
|
138
147
|
end
|
139
148
|
|
140
149
|
def human_name
|
@@ -144,14 +153,14 @@ module Nacha
|
|
144
153
|
def to_h
|
145
154
|
{ nacha_record_type: record_type,
|
146
155
|
metadata: {
|
156
|
+
klass: self.class.name,
|
147
157
|
errors: errors,
|
148
158
|
line_number: @line_number,
|
149
159
|
original_input_line: original_input_line
|
150
|
-
}
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end.to_h)
|
160
|
+
} }.merge(
|
161
|
+
@fields.keys.to_h do |key|
|
162
|
+
[key, @fields[key].to_json_output]
|
163
|
+
end)
|
155
164
|
end
|
156
165
|
|
157
166
|
def to_json(*_args)
|
@@ -164,17 +173,18 @@ module Nacha
|
|
164
173
|
end.join
|
165
174
|
end
|
166
175
|
|
167
|
-
def to_html(
|
176
|
+
def to_html(_opts = {})
|
168
177
|
record_error_class = nil
|
169
178
|
|
170
|
-
field_html = @fields.
|
171
|
-
record_error_class ||= 'error' if
|
172
|
-
|
179
|
+
field_html = @fields.values.collect do |field|
|
180
|
+
record_error_class ||= 'error' if field.errors.any?
|
181
|
+
field.to_html
|
173
182
|
end.join
|
174
|
-
"<div class=\"nacha-record tooltip #{record_type} #{record_error_class}\">"
|
175
|
-
"<span class=\"nacha-field\" data-name=\"record-number\">#{
|
176
|
-
|
177
|
-
"
|
183
|
+
"<div class=\"nacha-record tooltip #{record_type} #{record_error_class}\">" \
|
184
|
+
"<span class=\"nacha-field\" data-name=\"record-number\">#{format('%05d',
|
185
|
+
line_number)} | </span>" \
|
186
|
+
"#{field_html}" \
|
187
|
+
"<span class=\"record-type\" data-name=\"record-type\">#{human_name}</span>" \
|
178
188
|
"</div>"
|
179
189
|
end
|
180
190
|
|
@@ -188,19 +198,10 @@ module Nacha
|
|
188
198
|
|
189
199
|
def validate
|
190
200
|
# Run field-level validations first
|
191
|
-
@fields.
|
192
|
-
|
201
|
+
@fields.each_value(&:validate)
|
193
202
|
# Then run record-level validations that might depend on multiple fields
|
194
|
-
self.class.definition.
|
195
|
-
|
196
|
-
|
197
|
-
# rubocop:disable GitlabSecurity/PublicSend
|
198
|
-
field_data = send(field)
|
199
|
-
|
200
|
-
self.class.validations[field].map do |validation_method|
|
201
|
-
self.class.send(validation_method, field_data)
|
202
|
-
end
|
203
|
-
# rubocop:enable GitlabSecurity/PublicSend
|
203
|
+
self.class.definition.each_key do |field|
|
204
|
+
run_record_level_validations_for(field)
|
204
205
|
end
|
205
206
|
end
|
206
207
|
|
@@ -224,7 +225,8 @@ module Nacha
|
|
224
225
|
# # Assuming transaction_code is "201" and DEBIT_TRANSACTION_CODES includes "201"
|
225
226
|
# debit? #=> true
|
226
227
|
#
|
227
|
-
# # Assuming transaction_code is "100" and DEBIT_TRANSACTION_CODES
|
228
|
+
# # Assuming transaction_code is "100" and DEBIT_TRANSACTION_CODES
|
229
|
+
# # does not include "100"
|
228
230
|
# debit? #=> false
|
229
231
|
#
|
230
232
|
# # Assuming transaction_code is nil
|
@@ -263,7 +265,8 @@ module Nacha
|
|
263
265
|
# # Assuming transaction_code is "101" and CREDIT_TRANSACTION_CODES includes "101"
|
264
266
|
# credit? #=> true
|
265
267
|
#
|
266
|
-
# # Assuming transaction_code is "200" and CREDIT_TRANSACTION_CODES
|
268
|
+
# # Assuming transaction_code is "200" and CREDIT_TRANSACTION_CODES
|
269
|
+
# # does not include "200"
|
267
270
|
# credit? #=> false
|
268
271
|
#
|
269
272
|
# # Assuming transaction_code is nil
|
@@ -289,37 +292,54 @@ module Nacha
|
|
289
292
|
end
|
290
293
|
|
291
294
|
def errors
|
292
|
-
(@errors + @fields.values.map
|
295
|
+
(@errors + @fields.values.map(&:errors)).flatten
|
293
296
|
end
|
294
297
|
|
295
|
-
|
296
298
|
def add_error(err_string)
|
297
299
|
@errors << err_string
|
298
300
|
end
|
299
301
|
|
300
|
-
|
301
|
-
|
302
|
+
# :reek:TooManyStatements
|
303
|
+
def method_missing(method_name, *args, &block)
|
304
|
+
method = method_name.to_s
|
305
|
+
field_name = method.gsub(/=$/, '').to_sym
|
306
|
+
field = @fields[field_name]
|
307
|
+
return super unless field
|
302
308
|
|
303
|
-
|
309
|
+
if method.end_with?('=')
|
310
|
+
assign_field_data(field, args)
|
311
|
+
else
|
312
|
+
field
|
313
|
+
end
|
304
314
|
end
|
305
315
|
|
306
|
-
def
|
316
|
+
def respond_to_missing?(method_name, *)
|
307
317
|
field_name = method_name.to_s.gsub(/=$/, '').to_sym
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
318
|
+
definition[field_name] || super
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
|
323
|
+
def assign_field_data(field, args)
|
324
|
+
# rubocop:disable GitlabSecurity/PublicSend
|
325
|
+
field.public_send(:data=, *args)
|
326
|
+
# rubocop:enable GitlabSecurity/PublicSend
|
327
|
+
@dirty = true
|
328
|
+
end
|
329
|
+
|
330
|
+
# :reek:TooManyStatements
|
331
|
+
def run_record_level_validations_for(field)
|
332
|
+
klass = self.class
|
333
|
+
validations = klass.validations[field]
|
334
|
+
return unless validations
|
335
|
+
|
336
|
+
# rubocop:disable GitlabSecurity/PublicSend
|
337
|
+
field_data = send(field)
|
338
|
+
|
339
|
+
validations.each do |validation_method|
|
340
|
+
klass.send(validation_method, field_data)
|
322
341
|
end
|
342
|
+
# rubocop:enable GitlabSecurity/PublicSend
|
323
343
|
end
|
324
344
|
end
|
325
345
|
end
|