Repository: moov-io/ach
Pull request #929 (2021-05-11 18:12:16)

fix(deps): update module github.com/moov-io/base to v0.19.0
[![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [github.com/moov-io/base](https://togithub.com/moov-io/base) | require | minor | `v0.18.3` -> `v0.19.0` |

---

### Release Notes

<details>
<summary>moov-io/base</summary>

### [`v0.19.0`](https://togithub.com/moov-io/base/releases/v0.19.0)

[Compare Source](https://togithub.com/moov-io/base/compare/v0.18.3...v0.19.0)

Add transaction type signatures to `database`

</details>

---

### Configuration

📅 **Schedule**: At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻️ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box.

---

This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/moov-io/ach).


Patch:
diff --git a/docs/Gemfile b/docs/Gemfile
index efde910f..7963b961 100644
--- a/docs/Gemfile
+++ b/docs/Gemfile
@@ -11,7 +11,7 @@ source "https://rubygems.org"
 gem "bulma-clean-theme"
 # If you want to use GitHub Pages, remove the "gem "jekyll"" above and
 # uncomment the line below. To upgrade, run `bundle update github-pages`.
-gem "github-pages", group: :jekyll_plugins
+gem "github-pages", ">= 214", group: :jekyll_plugins
 # If you have any plugins, put them here!
 group :jekyll_plugins do
   gem "jekyll-feed", "~> 0.12"
@@ -26,4 +26,3 @@ end
 
 # Performance-booster for watching directories on Windows
 gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
-
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index e94164af..2f5627c9 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -1,7 +1,7 @@
 GEM
   remote: https://rubygems.org/
   specs:
-    activesupport (6.0.3.4)
+    activesupport (6.0.3.7)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
@@ -32,17 +32,21 @@ GEM
     ethon (0.12.0)
       ffi (>= 1.3.0)
     eventmachine (1.2.7)
-    execjs (2.7.0)
-    faraday (1.3.0)
+    execjs (2.8.0)
+    faraday (1.4.1)
+      faraday-excon (~> 1.1)
       faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.1)
       multipart-post (>= 1.2, < 3)
-      ruby2_keywords
+      ruby2_keywords (>= 0.0.4)
+    faraday-excon (1.1.0)
     faraday-net_http (1.0.1)
+    faraday-net_http_persistent (1.1.0)
     ffi (1.14.2)
     forwardable-extended (2.6.0)
     gemoji (3.0.1)
-    github-pages (211)
-      github-pages-health-check (= 1.16.1)
+    github-pages (214)
+      github-pages-health-check (= 1.17.0)
       jekyll (= 3.9.0)
       jekyll-avatar (= 0.7.0)
       jekyll-coffeescript (= 1.1.1)
@@ -57,7 +61,7 @@ GEM
       jekyll-readme-index (= 0.3.0)
       jekyll-redirect-from (= 0.16.0)
       jekyll-relative-links (= 0.6.1)
-      jekyll-remote-theme (= 0.4.2)
+      jekyll-remote-theme (= 0.4.3)
       jekyll-sass-converter (= 1.5.2)
       jekyll-seo-tag (= 2.7.1)
       jekyll-sitemap (= 1.4.0)
@@ -77,7 +81,7 @@ GEM
       jekyll-theme-time-machine (= 0.1.1)
       jekyll-titles-from-headings (= 0.5.3)
       jemoji (= 0.12.0)
-      kramdown (= 2.3.0)
+      kramdown (= 2.3.1)
       kramdown-parser-gfm (= 1.1.0)
       liquid (= 4.0.3)
       mercenary (~> 0.3)
@@ -85,11 +89,11 @@ GEM
       nokogiri (>= 1.10.4, < 2.0)
       rouge (= 3.26.0)
       terminal-table (~> 1.4)
-    github-pages-health-check (1.16.1)
+    github-pages-health-check (1.17.0)
       addressable (~> 2.3)
       dnsruby (~> 1.60)
       octokit (~> 4.0)
-      public_suffix (~> 3.0)
+      public_suffix (>= 2.0.2, < 5.0)
       typhoeus (~> 1.3)
     html-pipeline (2.14.0)
       activesupport (>= 2)
@@ -143,7 +147,7 @@ GEM
       jekyll (>= 3.3, < 5.0)
     jekyll-relative-links (0.6.1)
       jekyll (>= 3.3, < 5.0)
-    jekyll-remote-theme (0.4.2)
+    jekyll-remote-theme (0.4.3)
       addressable (~> 2.0)
       jekyll (>= 3.5, < 5.0)
       jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
@@ -203,7 +207,7 @@ GEM
       gemoji (~> 3.0)
       html-pipeline (~> 2.2)
       jekyll (>= 3.0, < 5.0)
-    kramdown (2.3.0)
+    kramdown (2.3.1)
       rexml
     kramdown-parser-gfm (1.1.0)
       kramdown (~> 2.0)
@@ -216,15 +220,15 @@ GEM
       jekyll (>= 3.5, < 5.0)
       jekyll-feed (~> 0.9)
       jekyll-seo-tag (~> 2.1)
-    minitest (5.14.3)
+    minitest (5.14.4)
     multipart-post (2.1.1)
-    nokogiri (1.11.1-arm64-darwin)
+    nokogiri (1.11.3-arm64-darwin)
       racc (~> 1.4)
-    nokogiri (1.11.1-x86_64-darwin)
+    nokogiri (1.11.3-x86_64-darwin)
       racc (~> 1.4)
-    nokogiri (1.11.1-x86_64-linux)
+    nokogiri (1.11.3-x86_64-linux)
       racc (~> 1.4)
-    octokit (4.20.0)
+    octokit (4.21.0)
       faraday (>= 0.9)
       sawyer (~> 0.8.0, >= 0.5.3)
     pathutil (0.16.2)
@@ -236,7 +240,7 @@ GEM
       ffi (~> 1.0)
     rexml (3.2.5)
     rouge (3.26.0)
-    ruby-enum (0.8.0)
+    ruby-enum (0.9.0)
       i18n
     ruby2_keywords (0.0.4)
     rubyzip (2.3.0)
@@ -270,7 +274,7 @@ PLATFORMS
 
 DEPENDENCIES
   bulma-clean-theme
-  github-pages
+  github-pages (>= 214)
   jekyll-feed (~> 0.12)
   tzinfo (~> 1.2)
   tzinfo-data
diff --git a/entryDetail.go b/entryDetail.go
index 6b7d65d2..e33e08bf 100644
--- a/entryDetail.go
+++ b/entryDetail.go
@@ -612,3 +612,23 @@ func sortEntriesByTraceNumber(entries []*EntryDetail) []*EntryDetail {
 	})
 	return entries
 }
+
+type traceNumbers map[string]struct{}
+
+var traceNumberValue = struct{}{}
+
+func (nums traceNumbers) contains(num string) bool {
+	_, exists := nums[num]
+	return exists
+}
+
+func getTraceNumbers(f *File) traceNumbers {
+	out := make(traceNumbers)
+	for i := range f.Batches {
+		entries := f.Batches[i].GetEntries()
+		for j := range entries {
+			out[entries[j].TraceNumber] = traceNumberValue
+		}
+	}
+	return out
+}
diff --git a/go.mod b/go.mod
index ab869009..7d436267 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a
 	github.com/lunixbochs/vtclean v1.0.0 // indirect
 	github.com/mattn/go-isatty v0.0.12
-	github.com/moov-io/base v0.18.3
+	github.com/moov-io/base v0.19.0
 	github.com/prometheus/client_golang v1.10.0
 )
 
diff --git a/go.sum b/go.sum
index 2c87c071..5fd68df5 100644
--- a/go.sum
+++ b/go.sum
@@ -380,6 +380,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/moov-io/base v0.18.3 h1:PtQMFNxQHF/YGcciMvuLrhI0+8FAbqaplNtqOG0jNsk=
 github.com/moov-io/base v0.18.3/go.mod h1:lU4wZfFEjIDkiQ58o1sSKsKFLy9R4noJWAr3t/rxaek=
+github.com/moov-io/base v0.19.0 h1:h6CE23tP79MKV8oSpHY0ZvT8EbJAhZsfuitYNwHmEPk=
+github.com/moov-io/base v0.19.0/go.mod h1:lU4wZfFEjIDkiQ58o1sSKsKFLy9R4noJWAr3t/rxaek=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
diff --git a/merge.go b/merge.go
index dc50af5d..156d0214 100644
--- a/merge.go
+++ b/merge.go
@@ -26,7 +26,11 @@ const NACHAFileLineLimit = 10000
 
 // MergeFiles is a helper function for consolidating an array of ACH Files into as few files
 // as possible. This is useful for optimizing cost and network efficiency.
-// This operation will override batch numbers in each file to ensure they do not collide. The ascending batch numbers will start at 1.
+//
+// This operation will override batch numbers in each file to ensure they do not collide.
+// The ascending batch numbers will start at 1.
+//
+// Duplicate TraceNumbers will not be allowed in the same file. Multiple files will be created.
 //
 // Per NACHA rules files must remain under 10,000 lines (when rendered in their ASCII encoding)
 //
@@ -34,7 +38,10 @@ const NACHAFileLineLimit = 10000
 func MergeFiles(files []*File) ([]*File, error) {
 	fs := &mergableFiles{infiles: files}
 	for i := range fs.infiles {
-		outf := fs.lookupByHeader(fs.infiles[i])
+		if fs.infiles[i] == nil {
+			continue // skip nil Files
+		}
+		outf := fs.findOutfile(fs.infiles[i])
 		for j := range fs.infiles[i].Batches {
 			batchExistsInMerged := false
 			for k := range outf.Batches {
@@ -114,20 +121,41 @@ next:
 	return out
 }
 
-// lookupByHeader optionally returns a File from fs.files if the FileHeaders match.
+// findOutfile optionally returns a File from fs.files if the FileHeaders match.
 // This is done because we append batches into files to minimize the count of output files.
 //
-// lookupByHeader will return the existing file (stored in outfiles) if no matching file exists.
-func (fs *mergableFiles) lookupByHeader(f *File) *File {
-	for i := range fs.outfiles {
-		if fs.outfiles[i].Header.ImmediateDestination == f.Header.ImmediateDestination &&
-			fs.outfiles[i].Header.ImmediateOrigin == f.Header.ImmediateOrigin {
-			// found a matching file, so return it
-			return fs.outfiles[i]
+// findOutfile will return the existing file (stored in outfiles) if no matching file exists.
+func (fs *mergableFiles) findOutfile(f *File) *File {
+	inTraceNumbers := getTraceNumbers(f)
+
+	var lookup func(int) *File
+	lookup = func(start int) *File {
+		// To allow recursive lookups we need to memorize the current index so deeper calls
+		// will bypass files with conflicting trace numbers.
+		for i := start; i < len(fs.outfiles); i++ {
+			if fs.outfiles[i].Header.ImmediateDestination == f.Header.ImmediateDestination &&
+				fs.outfiles[i].Header.ImmediateOrigin == f.Header.ImmediateOrigin {
+
+				// found a matching file, so verify the TraceNumber isn't alreay inside
+				outTraceNumbers := getTraceNumbers(fs.outfiles[i])
+				for trace := range inTraceNumbers {
+					// If any of our incoming trace numbers match the existing merged file
+					// return the entire file as separate. This keeps partially overlapping
+					// batches self-contained.
+					if outTraceNumbers.contains(trace) {
+						return lookup(i + 1)
+					}
+				}
+
+				// No conflicting TraceNumber was found, so return current merge file
+				return fs.outfiles[i]
+			}
 		}
+		// Record a newly mergable file we can use in future merge attempts
+		fs.outfiles = append(fs.outfiles, f)
+		return f
 	}
-	fs.outfiles = append(fs.outfiles, f)
-	return f
+	return lookup(0)
 }
 
 func lineCount(f *File) (int, error) {
diff --git a/reader.go b/reader.go
index 7202993f..b4a0b6fb 100644
--- a/reader.go
+++ b/reader.go
@@ -100,8 +100,8 @@ func (r *Reader) SetValidation(opts *ValidateOpts) {
 	r.File.SetValidation(opts)
 }
 
-// ReadFile attempts to open an os.File at path and read the entire file
-// before closing it and returning the parse result.
+// ReadFile attempts to open a file at path and read the contents before closing
+// and returning the parsed ACH File.
 func ReadFile(path string) (*File, error) {
 	fd, err := os.Open(path)
 	if err != nil {
@@ -113,6 +113,22 @@ func ReadFile(path string) (*File, error) {
 	return &file, err
 }
 
+// ReadFiles attempts to open files at the given paths and read the contents
+// of each before closing and returning the parsed ACH Files.
+func ReadFiles(paths []string) ([]*File, error) {
+	var out []*File
+	for i := range paths {
+		file, err := ReadFile(paths[i])
+		if err != nil {
+			return nil, err
+		}
+		if file != nil {
+			out = append(out, file)
+		}
+	}
+	return out, nil
+}
+
 // NewReader returns a new ACH Reader that reads from r.
 func NewReader(r io.Reader) *Reader {
 	return &Reader{


Test patch:
diff --git a/reader_test.go b/reader_test.go
index 3517372e..9f677170 100644
--- a/reader_test.go
+++ b/reader_test.go
@@ -51,6 +51,20 @@ func TestReadFile(t *testing.T) {
 	}
 }
 
+func TestReadFiles(t *testing.T) {
+	paths := []string{
+		filepath.Join("test", "testdata", "return-WEB.ach"),
+		filepath.Join("test", "testdata", "web-debit.ach"),
+	}
+	files, err := ReadFiles(paths)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if n := len(files); n != 2 {
+		t.Fatalf("read %d files", n)
+	}
+}
+
 func TestReadPartial(t *testing.T) {
 	file, err := ReadFile(filepath.Join("test", "testdata", "bh-ed-ad-bh-ed-ad-ed-ad.ach"))
 	if err != nil {
diff --git a/test/issues/issue927_test.go b/test/issues/issue927_test.go
new file mode 100644
index 00000000..c83fb3e1
--- /dev/null
+++ b/test/issues/issue927_test.go
@@ -0,0 +1,47 @@
+// Licensed to The Moov Authors under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. The Moov Authors licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package issues
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/moov-io/ach"
+)
+
+func TestIssue927(t *testing.T) {
+	before, err := ach.ReadDir(filepath.Join("testdata", "issue927"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	after, err := ach.MergeFiles(before)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(after) != 2 {
+		t.Errorf("merged %d files into %d", len(before), len(after))
+	}
+
+	if n := len(after[0].Batches); n != 5 {
+		t.Errorf("found %d batches", n)
+	}
+	if n := len(after[1].Batches); n != 3 {
+		t.Errorf("found %d batches", n)
+	}
+}
diff --git a/test/issues/testdata/issue927/016461532850569c045ce6e1056e3d49b95727d1.ach b/test/issues/testdata/issue927/016461532850569c045ce6e1056e3d49b95727d1.ach
new file mode 100644
index 00000000..d4aef6da
--- /dev/null
+++ b/test/issues/testdata/issue927/016461532850569c045ce6e1056e3d49b95727d1.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051229A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYYY      0000200000               ISSUE927 LLC          Ap0273976367998099
+632273976369XXXXXXXXXXX      0000200000               MOOV FINANCIAL        OF0273976367998100
+82000000020034787912000000200000000000200000ISSUE927                           273976360000001
+9000001000001000000020034787912000000200000000000200000                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue927/044f121c5da6ff398d22b6ca8679239a13270594.ach b/test/issues/testdata/issue927/044f121c5da6ff398d22b6ca8679239a13270594.ach
new file mode 100644
index 00000000..c381e840
--- /dev/null
+++ b/test/issues/testdata/issue927/044f121c5da6ff398d22b6ca8679239a13270594.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051225A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYY       0000007900               ISSUE927 LLC          Ap0273976361975007
+632273976369XXXXXXXXXXX      0000007900               MOOV FINANCIAL        OF0273976361975008
+82000000020034787912000000007900000000007900ISSUE927                           273976360000001
+9000001000001000000020034787912000000007900000000007900                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue927/2b5a940e6b5bcc9f2753d31ea6309d1f49b21891.ach b/test/issues/testdata/issue927/2b5a940e6b5bcc9f2753d31ea6309d1f49b21891.ach
new file mode 100644
index 00000000..55ec123e
--- /dev/null
+++ b/test/issues/testdata/issue927/2b5a940e6b5bcc9f2753d31ea6309d1f49b21891.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051312A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYY       0000015800               ISSUE927 LLC          Ap0273976368549829
+632273976369XXXXXXXXXXX      0000015800               MOOV FINANCIAL        OF0273976368549830
+82000000020034787912000000015800000000015800ISSUE927                           273976360000001
+9000001000001000000020034787912000000015800000000015800                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue927/44a858e07e89fb0b24bcfdda733070f38ef1e889.ach b/test/issues/testdata/issue927/44a858e07e89fb0b24bcfdda733070f38ef1e889.ach
new file mode 100644
index 00000000..6370c249
--- /dev/null
+++ b/test/issues/testdata/issue927/44a858e07e89fb0b24bcfdda733070f38ef1e889.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051122A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYY       0000200000               ISSUE927 LLC          Ap0273976366639555
+632273976369XXXXXXXXXXX      0000200000               MOOV FINANCIAL        OF0273976366639556
+82000000020034787912000000200000000000200000ISSUE927                           273976360000001
+9000001000001000000020034787912000000200000000000200000                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue927/54fa1e16738209076a14548ff3dd061ef2e671a1.ach b/test/issues/testdata/issue927/54fa1e16738209076a14548ff3dd061ef2e671a1.ach
new file mode 100644
index 00000000..3d982135
--- /dev/null
+++ b/test/issues/testdata/issue927/54fa1e16738209076a14548ff3dd061ef2e671a1.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051315A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYYY      0000342400               ISSUE927 LLC          Ap0273976364891931
+632273976369XXXXXXXXXXX      0000342400               MOOV FINANCIAL        OF0273976364891932
+82000000020034787912000000342400000000342400ISSUE927                           273976360000001
+9000001000001000000020034787912000000342400000000342400                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue927/9bb2e883c8cfa2bfc73f433fcac11a7e90f8d6e5.ach b/test/issues/testdata/issue927/9bb2e883c8cfa2bfc73f433fcac11a7e90f8d6e5.ach
new file mode 100644
index 00000000..f69ad576
--- /dev/null
+++ b/test/issues/testdata/issue927/9bb2e883c8cfa2bfc73f433fcac11a7e90f8d6e5.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051330A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYYY      0000200000               ISSUE927 LLC          Ap0273976363652315
+632273976369XXXXXXXXXXX      0000200000               MOOV FINANCIAL        OF0273976363652316
+82000000020034787912000000200000000000200000ISSUE927                           273976360000001
+9000001000001000000020034787912000000200000000000200000                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue927/a6ce094411be256f495f6135d5089b9d9533fa27.ach b/test/issues/testdata/issue927/a6ce094411be256f495f6135d5089b9d9533fa27.ach
new file mode 100644
index 00000000..1a57b93b
--- /dev/null
+++ b/test/issues/testdata/issue927/a6ce094411be256f495f6135d5089b9d9533fa27.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051255A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYY       0000250000               ISSUE927 LLC          Ap0273976366639555
+632273976369XXXXXXXXXXX      0000250000               MOOV FINANCIAL        OF0273976366639556
+82000000020034787912000000250000000000250000ISSUE927                           273976360000001
+9000001000001000000020034787912000000250000000000250000                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue927/bfd6b648c4b4e2d1581a0767740233f2c67cebea.ach b/test/issues/testdata/issue927/bfd6b648c4b4e2d1581a0767740233f2c67cebea.ach
new file mode 100644
index 00000000..2598c087
--- /dev/null
+++ b/test/issues/testdata/issue927/bfd6b648c4b4e2d1581a0767740233f2c67cebea.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051309A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYYY      0000029500               ISSUE927 LLC          Ap0273976361975007
+632273976369XXXXXXXXXXX      0000029500               MOOV FINANCIAL        OF0273976361975008
+82000000020034787912000000029500000000029500ISSUE927                           273976360000001
+9000001000001000000020034787912000000029500000000029500                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue927/e2ef49fe5fa2b676feff5f3669d5fffb99e22cdc.ach b/test/issues/testdata/issue927/e2ef49fe5fa2b676feff5f3669d5fffb99e22cdc.ach
new file mode 100644
index 00000000..9da1e61b
--- /dev/null
+++ b/test/issues/testdata/issue927/e2ef49fe5fa2b676feff5f3669d5fffb99e22cdc.ach
@@ -0,0 +1,10 @@
+101 071000301 2739763692105051227A094101FRBATLANTA                 ODFI CU                    
+5200MOOV FINANCIAL                      ISSUE927  CCDFUNDING   210505210506   1273976360000001
+627073902766YYYYYYYYYYY      0000052100               ISSUE927 LLC          Ap0273976364891931
+632273976369XXXXXXXXXXX      0000052100               MOOV FINANCIAL        OF0273976364891932
+82000000020034787912000000052100000000052100ISSUE927                           273976360000001
+9000001000001000000020034787912000000052100000000052100                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999