diff --git a/batch.go b/batch.go index 4e3748e68..4b01a5dfb 100644 --- a/batch.go +++ b/batch.go @@ -1201,12 +1201,15 @@ func (b *Batch) upsertOffsets() error { } // remove any Offset records already on the batch + var found *EntryDetail for i := 0; i < len(b.Entries); i++ { // TODO(adam): Should we remove this based on checking the last element is // debit/credit and sums to all the other elements (which are mutually exclusive to // the last record being debit or credit)? // See: https://github.com/moov-io/ach/issues/540 if strings.EqualFold(b.Entries[i].IndividualName, offsetIndividualName) { + found = b.Entries[i] // save the EntryDetail to copy addenda records back onto + // fixup BatchControl records for our conditional after this for loop if b.Entries[i].TransactionCode == CheckingCredit || b.Entries[i].TransactionCode == SavingsCredit { b.Control.TotalCreditEntryDollarAmount -= b.Entries[i].Amount @@ -1259,13 +1262,29 @@ func (b *Batch) upsertOffsets() error { // Add both EntryDetails to our Batch and recalculate some fields if debitED != nil { + // Copy any addenda records from a previous OFFSET EntryDetail + if found != nil { + if len(found.Addenda05) > 0 { + debitED.AddendaRecordIndicator = 1 + debitED.Addenda05 = append(debitED.Addenda05, found.Addenda05...) + } + } + b.AddEntry(debitED) - b.Control.EntryAddendaCount += 1 + b.Control.EntryAddendaCount += 1 + debitED.addendaCount() - found.addendaCount() b.Control.TotalDebitEntryDollarAmount += debitED.Amount } if creditED != nil { + // Copy any addenda records from a previous OFFSET EntryDetail + if found != nil { + if len(found.Addenda05) > 0 { + creditED.AddendaRecordIndicator = 1 + creditED.Addenda05 = append(creditED.Addenda05, found.Addenda05...) + } + } + b.AddEntry(creditED) - b.Control.EntryAddendaCount += 1 + b.Control.EntryAddendaCount += 1 + creditED.addendaCount() - found.addendaCount() b.Control.TotalCreditEntryDollarAmount += creditED.Amount } b.Header.ServiceClassCode = MixedDebitsAndCredits diff --git a/entryDetail.go b/entryDetail.go index 590f836cf..a84b45f1b 100644 --- a/entryDetail.go +++ b/entryDetail.go @@ -688,6 +688,10 @@ func (ed *EntryDetail) AddAddenda05(addenda05 *Addenda05) { // addendaCount returns the count of Addenda records added onto this EntryDetail func (ed *EntryDetail) addendaCount() (n int) { + if ed == nil { + return 0 + } + if ed.Addenda02 != nil { n += 1 } diff --git a/test/issues/issue1656_test.go b/test/issues/issue1656_test.go new file mode 100644 index 000000000..b96c094c9 --- /dev/null +++ b/test/issues/issue1656_test.go @@ -0,0 +1,36 @@ +package issues + +import ( + "os" + "path/filepath" + "testing" + + "github.com/moov-io/ach" + "github.com/moov-io/ach/cmd/achcli/describe" + + "github.com/stretchr/testify/require" +) + +func TestIssue1656(t *testing.T) { + file, err := ach.ReadJSONFile(filepath.Join("testdata", "issue1656.json")) + require.NoError(t, err) + + if testing.Verbose() { + describe.File(os.Stdout, file, nil) + } + + require.Len(t, file.Batches, 1) + + entries := file.Batches[0].GetEntries() + require.Len(t, entries, 2) + + // First Entry + require.Equal(t, "Jane Smith", entries[0].IndividualName) + require.Len(t, entries[0].Addenda05, 1) + require.Equal(t, "abc", entries[0].Addenda05[0].PaymentRelatedInformation) + + // Second Entry + require.Equal(t, "OFFSET", entries[1].IndividualName) + require.Len(t, entries[1].Addenda05, 1) + require.Equal(t, "def", entries[1].Addenda05[0].PaymentRelatedInformation) +} diff --git a/test/issues/testdata/issue1656.json b/test/issues/testdata/issue1656.json new file mode 100644 index 000000000..9d910beaa --- /dev/null +++ b/test/issues/testdata/issue1656.json @@ -0,0 +1,74 @@ +{ + "fileHeader": { + "immediateDestination": "123456780", + "immediateOrigin": "123456780", + "fileCreationDate": "231219", + "fileCreationTime": "", + "fileIDModifier": "A", + "FormatCode": "1", + "immediateDestinationName": "Anonymous Bank", + "immediateOriginName": "Anonymous Bank" + }, + "batches": [ + { + "batchHeader": { + "serviceClassCode": 200, + "companyName": "Anonymous Bank", + "companyIdentification": "123456780", + "standardEntryClassCode": "CCD", + "companyEntryDescription": "Anonymous Bank", + "originatorStatusCode": 1, + "ODFIIdentification": "123456780", + "batchNumber": 1 + }, + "entryDetails": [ + { + "transactionCode": 22, + "RDFIIdentification": "12345678", + "checkDigit": "0", + "DFIAccountNumber": "12345", + "amount": 50000, + "individualName": "Jane Smith", + "traceNumber": "123456780000001", + "addendaRecordIndicator": 1, + "addenda05": [ + { + "typeCode": "05", + "paymentRelatedInformation": "abc", + "sequenceNumber": 1, + "entryDetailSequenceNumber": 10000 + } + ] + }, + { + "transactionCode": 27, + "RDFIIdentification": "12345678", + "checkDigit": "0", + "DFIAccountNumber": "12345", + "amount": 50000, + "individualName": "OFFSET", + "discretionaryData": "For offset", + "traceNumber": "123456780000002", + "addendaRecordIndicator": 1, + "addenda05": [ + { + "typeCode": "05", + "paymentRelatedInformation": "def", + "sequenceNumber": 1, + "entryDetailSequenceNumber": 10000 + } + ] + } + ], + "offset": { + "routingNumber": "123456780", + "accountNumber": "12345", + "accountType": "checking", + "description": "For offset" + } + } + ], + "IATBatches": null, + "NotificationOfChange": null, + "ReturnEntries": null +}