Repository: moov-io/ach
Pull request #147 (2017-11-02 00:37:24)

Export File.ReturnEntires[]
EntryDetail.AddAddenda support AddendaReturn


Patch:
diff --git a/batch.go b/batch.go
index e2032059..94cb9dcd 100644
--- a/batch.go
+++ b/batch.go
@@ -7,6 +7,9 @@ type batch struct {
 	header  *BatchHeader
 	entries []*EntryDetail
 	control *BatchControl
+
+	// isReturn is true if a return entry was added to the batch
+	isReturn bool
 	// Converters is composed for ACH to GoLang Converters
 	converters
 }
@@ -160,9 +163,15 @@ func (batch *batch) GetEntries() []*EntryDetail {
 
 // AddEntry appends an EntryDetail to the Batch
 func (batch *batch) AddEntry(entry *EntryDetail) {
+	batch.isReturn = entry.isReturn
 	batch.entries = append(batch.entries, entry)
 }
 
+// IsReturn is true if the batch contains an Entry Return
+func (batch *batch) IsReturn() bool {
+	return batch.isReturn
+}
+
 // isFieldInclusion iterates through all the records in the batch and verifies against default fields
 func (batch *batch) isFieldInclusion() error {
 	if err := batch.header.Validate(); err != nil {
diff --git a/batcher.go b/batcher.go
index da004263..60587ac1 100644
--- a/batcher.go
+++ b/batcher.go
@@ -21,6 +21,7 @@ type Batcher interface {
 	AddEntry(*EntryDetail)
 	Create() error
 	Validate() error
+	IsReturn() bool
 }
 
 // BatchError is an Error that describes batch validation issues
diff --git a/entryDetail.go b/entryDetail.go
index e6b6f0da..0a86847d 100644
--- a/entryDetail.go
+++ b/entryDetail.go
@@ -28,7 +28,7 @@ type EntryDetail struct {
 	// Prenote for debit to savings account ‘38’
 	TransactionCode int
 
-	// rdfiIdentification is the RDFI's routing number without the last digit.
+	// RDFIIdentification is the RDFI's routing number without the last digit.
 	// Receiving Depository Financial Institution
 	RDFIIdentification int
 
@@ -78,6 +78,8 @@ type EntryDetail struct {
 	Addendum []Addendumer
 	// ReturnAddendum stores return types. These are processed separately
 	ReturnAddendum []AddendaReturn
+	// isReturn indicates the existence of a AddendaReturn in Addendum
+	isReturn bool
 	// validator is composed for data validation
 	validator
 	// converters is composed for ACH to golang Converters
@@ -200,7 +202,6 @@ func (ed *EntryDetail) Validate() error {
 		msg := fmt.Sprintf(msgValidCheckDigit, calculated)
 		return &FieldError{FieldName: "RDFIIdentification", Value: strconv.Itoa(ed.CheckDigit), Msg: msg}
 	}
-
 	return nil
 }
 
@@ -219,10 +220,6 @@ func (ed *EntryDetail) fieldInclusion() error {
 	if ed.DFIAccountNumber == "" {
 		return &FieldError{FieldName: "DFIAccountNumber", Value: ed.DFIAccountNumber, Msg: msgFieldInclusion}
 	}
-	// TODO: amount can be 0 if it's COR, should probably be more specific...
-	/*if ed.Amount == 0 {
-		return &FieldError{FieldName: "Amount", Value: ed.AmountField(), Msg: msgFieldInclusion}
-	}*/
 	if ed.IndividualName == "" {
 		return &FieldError{FieldName: "IndividualName", Value: ed.IndividualName, Msg: msgFieldInclusion}
 	}
@@ -236,11 +233,24 @@ func (ed *EntryDetail) fieldInclusion() error {
 func (ed *EntryDetail) AddAddenda(addenda Addendumer) []Addendumer {
 	ed.AddendaRecordIndicator = 1
 	// checks to make sure that we only have either or, not both
-	if ed.ReturnAddendum != nil {
-		return nil
+	switch addenda.(type) {
+	case *AddendaReturn:
+		ed.isReturn = true
+		// Only 1 Addendum can exist for returns. Overwrite existing Addendum
+		ed.Addendum = nil
+		ed.Addendum = append(ed.Addendum, addenda)
+		return ed.Addendum
+	case *AddendaNOC:
+		// Only 1 Addendum can exist for Notification of Change. Overwrite existing Addendum
+		ed.Addendum = nil
+		ed.Addendum = append(ed.Addendum, addenda)
+		return ed.Addendum
+	default:
+		// Batch type needs to validate number of addendum
+		ed.isReturn = false
+		ed.Addendum = append(ed.Addendum, addenda)
+		return ed.Addendum
 	}
-	ed.Addendum = append(ed.Addendum, addenda)
-	return ed.Addendum
 }
 
 // AddAddendaReturn appends an ReturnAddendum to the entry
diff --git a/file.go b/file.go
index 251297c3..c1159f14 100644
--- a/file.go
+++ b/file.go
@@ -68,6 +68,8 @@ type File struct {
 
 	// NotificationOfChange (Notification of change) is a slice of references to BatchCOR in file.Batches
 	NotificationOfChange []*BatchCOR
+	// ReturnEntries is a slice of references to file.Batches that contain return entires
+	ReturnEntries []Batcher
 
 	converters
 }
@@ -156,6 +158,9 @@ func (f *File) AddBatch(batch Batcher) []Batcher {
 	case *BatchCOR:
 		f.NotificationOfChange = append(f.NotificationOfChange, batch.(*BatchCOR))
 	}
+	if batch.IsReturn() {
+		f.ReturnEntries = append(f.ReturnEntries, batch)
+	}
 	f.Batches = append(f.Batches, batch)
 	return f.Batches
 }


Test patch:
diff --git a/entryDetail_internal_test.go b/entryDetail_internal_test.go
index 8789efaf..da946178 100644
--- a/entryDetail_internal_test.go
+++ b/entryDetail_internal_test.go
@@ -287,3 +287,28 @@ func TestEDFieldInclusionTraceNumber(t *testing.T) {
 		}
 	}
 }
+
+func TestEDAddAddendaAddendaReturn(t *testing.T) {
+	entry := mockEntryDetail()
+	entry.AddAddenda(mockAddendaReturn())
+	if !entry.isReturn {
+		t.Error("AddendaReturn added and isReturn is false")
+	}
+	if entry.AddendaRecordIndicator != 1 {
+		t.Error("AddendaReturn added and record indicator is not 1")
+	}
+
+}
+
+func TestEDAddAddendaAddendaReturnTwice(t *testing.T) {
+	entry := mockEntryDetail()
+	entry.AddAddenda(mockAddendaReturn())
+	entry.AddAddenda(mockAddendaReturn())
+	if !entry.isReturn {
+		t.Error("AddendaReturn added and isReturn is false")
+	}
+
+	if len(entry.Addendum) != 1 {
+		t.Error("AddendaReturn added and isReturn is false")
+	}
+}
diff --git a/file_test.go b/file_test.go
index fb04e40b..16bee67c 100644
--- a/file_test.go
+++ b/file_test.go
@@ -5,6 +5,7 @@
 package ach
 
 import (
+	"fmt"
 	"testing"
 )
 
@@ -176,3 +177,33 @@ func TestFileNotificationOfChange(t *testing.T) {
 		t.Error("BatchCOR added to File.AddBatch should exist in NotificationOfChange")
 	}
 }
+
+func TestFileReturnEntries(t *testing.T) {
+	// create or copy the entry to be returned record
+	entry := mockEntryDetail()
+	// Add the addenda return with appropriate ReturnCode and addenda information
+	entry.AddAddenda(mockAddendaReturn())
+	// create or copy the previous batch header of the item being returned
+	batchHeader := mockBatchHeader()
+	// create or copy the batch to be returned
+	batch, err := NewBatch(BatchParam{StandardEntryClass: batchHeader.StandardEntryClassCode})
+	if err != nil {
+		t.Error(err.Error())
+	}
+	// Add the entry to be returned to the batch
+	batch.AddEntry(entry)
+	// Create the batch
+	batch.Create()
+	// Add the batch to your file.
+	file := NewFile().SetHeader(mockFileHeader())
+	file.AddBatch(batch)
+	// Create the return file
+	if err := file.Create(); err != nil {
+		t.Error(err.Error())
+	}
+
+	for i := range file.ReturnEntries {
+		fmt.Printf("%+v\n", file.ReturnEntries[i])
+	}
+
+}