Repository: moov-io/ach
Pull request #1137 (2022-11-21 16:30:18)

feat: add UnequalAddendaCounts validation option
Fixes: https://github.com/moov-io/ach/issues/1136


Patch:
diff --git a/batch.go b/batch.go
index f33b989f..8c7de391 100644
--- a/batch.go
+++ b/batch.go
@@ -634,6 +634,9 @@ func (batch *Batch) isBatchEntryCount() error {
 			entryCount += 1 + entry.addendaCount()
 		}
 		if entryCount != batch.Control.EntryAddendaCount {
+			if batch.validateOpts != nil && batch.validateOpts.UnequalAddendaCounts {
+				return nil
+			}
 			return batch.Error("EntryAddendaCount",
 				NewErrBatchCalculatedControlEquality(entryCount, batch.Control.EntryAddendaCount))
 		}
@@ -645,6 +648,9 @@ func (batch *Batch) isBatchEntryCount() error {
 			}
 		}
 		if entryCount != batch.ADVControl.EntryAddendaCount {
+			if batch.validateOpts != nil && batch.validateOpts.UnequalAddendaCounts {
+				return nil
+			}
 			return batch.Error("EntryAddendaCount",
 				NewErrBatchCalculatedControlEquality(entryCount, batch.ADVControl.EntryAddendaCount))
 		}
diff --git a/docs/create-file.md b/docs/create-file.md
index bbf5dfc6..fff3703e 100644
--- a/docs/create-file.md
+++ b/docs/create-file.md
@@ -56,6 +56,7 @@ Example: `POST /files/create?requireABAOrigin=true&bypassDestination=true`
 | `unequalServiceClassCode`          | `UnequalServiceClassCode`          |
 | `unorderedBatchNumbers`            | `AllowUnorderedBatchNumbers`       |
 | `allowInvalidCheckDigit`           | `AllowInvalidCheckDigit`           |
+| `unequalAddendaCounts`             | `UnequalAddendaCounts`             |
 
 ## Upload a raw ACH file
 
diff --git a/docs/custom-validation.md b/docs/custom-validation.md
index 160f5c47..a14d3fc5 100644
--- a/docs/custom-validation.md
+++ b/docs/custom-validation.md
@@ -80,6 +80,9 @@ UnequalServiceClassCode bool `json:"unequalServiceClassCode"`
 
 // AllowUnorderedBatchNumebrs allows a file to be read with unordered batch numbers.
 AllowUnorderedBatchNumbers bool `json:"allowUnorderedBatchNumbers"`
+
+// UnequalAddendaCounts skips checking that Addenda Count fields match their expected and computed values.
+UnequalAddendaCounts bool `json:"unequalAddendaCounts"`
 ```
 
 ### File Header
diff --git a/file.go b/file.go
index b9c216b8..46a45ba2 100644
--- a/file.go
+++ b/file.go
@@ -675,6 +675,9 @@ type ValidateOpts struct {
 	// AllowInvalidCheckDigit allows the CheckDigit field in EntryDetail to differ from
 	// the expected calculation
 	AllowInvalidCheckDigit bool `json:"allowInvalidCheckDigit"`
+
+	// UnequalAddendaCounts skips checking that Addenda Count fields match their expected and computed values.
+	UnequalAddendaCounts bool `json:"unequalAddendaCounts"`
 }
 
 // ValidateWith performs checks on each record according to Nacha guidelines.
@@ -766,6 +769,9 @@ func (f *File) isEntryAddendaCount(IsADV bool) error {
 			count += iatBatch.GetControl().EntryAddendaCount
 		}
 		if f.Control.EntryAddendaCount != count {
+			if f.validateOpts != nil && f.validateOpts.UnequalAddendaCounts {
+				return nil
+			}
 			return NewErrFileCalculatedControlEquality("EntryAddendaCount", count, f.Control.EntryAddendaCount)
 		}
 	} else {
@@ -773,6 +779,9 @@ func (f *File) isEntryAddendaCount(IsADV bool) error {
 			count += batch.GetADVControl().EntryAddendaCount
 		}
 		if f.ADVControl.EntryAddendaCount != count {
+			if f.validateOpts != nil && f.validateOpts.UnequalAddendaCounts {
+				return nil
+			}
 			return NewErrFileCalculatedControlEquality("EntryAddendaCount", count, f.ADVControl.EntryAddendaCount)
 		}
 	}
diff --git a/iatBatch.go b/iatBatch.go
index 2df6642d..73b2b7c6 100644
--- a/iatBatch.go
+++ b/iatBatch.go
@@ -349,6 +349,9 @@ func (iatBatch *IATBatch) isBatchEntryCount() error {
 		}
 	}
 	if entryCount != iatBatch.Control.EntryAddendaCount {
+		if iatBatch.validateOpts != nil && iatBatch.validateOpts.UnequalAddendaCounts {
+			return nil
+		}
 		return iatBatch.Error("EntryAddendaCount",
 			NewErrBatchCalculatedControlEquality(entryCount, iatBatch.Control.EntryAddendaCount))
 	}
diff --git a/openapi.yaml b/openapi.yaml
index bd5fa8f8..8663ad82 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -141,6 +141,11 @@ paths:
           description: Allow the CheckDigit field in EntryDetail to differ from the expected calculation
           schema:
             type: boolean
+        - name: unequalAddendaCounts
+          in: query
+          description: Optional parameter to configure UnequalAddendaCounts validation
+          schema:
+            type: boolean
       requestBody:
         description: Content of the ACH file (in json or raw text)
         required: true
@@ -2282,6 +2287,10 @@ components:
           type: boolean
           default: false
           description: Allow the CheckDigit field in EntryDetail to differ from the expected calculation
+        unequalAddendaCounts:
+          type: boolean
+          default: false
+          description: Skip checking that Addenda Count fields match their expected and computed values.
     SegmentFileConfiguration:
       properties: {} # TODO: Are there any config options people need?
     SegmentFile:
diff --git a/server/files.go b/server/files.go
index 10906182..5c2c7760 100644
--- a/server/files.go
+++ b/server/files.go
@@ -135,6 +135,7 @@ func decodeCreateFileRequest(_ context.Context, request *http.Request) (interfac
 		unequalServiceClassCode          = "unequalServiceClassCode"
 		unorderedBatchNumbers            = "unorderedBatchNumbers"
 		allowInvalidCheckDigit           = "allowInvalidCheckDigit"
+		unequalAddendaCounts             = "unequalAddendaCounts"
 	)
 
 	validationNames := []string{
@@ -150,6 +151,7 @@ func decodeCreateFileRequest(_ context.Context, request *http.Request) (interfac
 		unequalServiceClassCode,
 		unorderedBatchNumbers,
 		allowInvalidCheckDigit,
+		unequalAddendaCounts,
 	}
 
 	for _, name := range validationNames {
@@ -191,6 +193,8 @@ func decodeCreateFileRequest(_ context.Context, request *http.Request) (interfac
 			req.validateOpts.AllowUnorderedBatchNumbers = true
 		case allowInvalidCheckDigit:
 			req.validateOpts.AllowInvalidCheckDigit = true
+		case unequalAddendaCounts:
+			req.validateOpts.UnequalAddendaCounts = true
 		}
 	}
 


Test patch:
diff --git a/batch_test.go b/batch_test.go
index b61ae1b2..58e6c3b3 100644
--- a/batch_test.go
+++ b/batch_test.go
@@ -338,6 +338,12 @@ func testBatchEntryCountEquality(t testing.TB) {
 	if !base.Match(err, NewErrBatchCalculatedControlEquality(3, 1)) {
 		t.Errorf("%T: %s", err, err)
 	}
+
+	mockBatch.SetValidation(&ValidateOpts{
+		CustomTraceNumbers:   true,
+		UnequalAddendaCounts: true,
+	})
+	require.NoError(t, mockBatch.verify())
 }
 
 func TestBatchEntryCountEquality(t *testing.T) {