diff --git a/batch.go b/batch.go index 4e3748e68..392589ecf 100644 --- a/batch.go +++ b/batch.go @@ -1231,6 +1231,11 @@ func (b *Batch) upsertOffsets() error { debitED := createOffsetEntryDetail(b.offset, b) debitED.TraceNumber = fmt.Sprintf("%15.15d", lastTraceNumber(b.Entries)+offsetCount) debitED.Amount = b.Control.TotalCreditEntryDollarAmount + if len(b.offset.Addenda05) > 0 { + debitED.AddendaRecordIndicator = 1 + debitED.Addenda05 = make([]*Addenda05, len(b.offset.Addenda05)) + copy(debitED.Addenda05, b.offset.Addenda05) + } switch b.offset.AccountType { case OffsetChecking: debitED.TransactionCode = CheckingDebit @@ -1247,6 +1252,11 @@ func (b *Batch) upsertOffsets() error { creditED := createOffsetEntryDetail(b.offset, b) creditED.TraceNumber = fmt.Sprintf("%15.15d", lastTraceNumber(b.Entries)+offsetCount) creditED.Amount = b.Control.TotalDebitEntryDollarAmount + if len(b.offset.Addenda05) > 0 { + creditED.AddendaRecordIndicator = 1 + creditED.Addenda05 = make([]*Addenda05, len(b.offset.Addenda05)) + copy(creditED.Addenda05, b.offset.Addenda05) + } switch b.offset.AccountType { case OffsetChecking: creditED.TransactionCode = CheckingCredit @@ -1260,12 +1270,12 @@ func (b *Batch) upsertOffsets() error { // Add both EntryDetails to our Batch and recalculate some fields if debitED != nil { b.AddEntry(debitED) - b.Control.EntryAddendaCount += 1 + b.Control.EntryAddendaCount += 1 + debitED.addendaCount() b.Control.TotalDebitEntryDollarAmount += debitED.Amount } if creditED != nil { b.AddEntry(creditED) - b.Control.EntryAddendaCount += 1 + b.Control.EntryAddendaCount += 1 + creditED.addendaCount() b.Control.TotalCreditEntryDollarAmount += creditED.Amount } b.Header.ServiceClassCode = MixedDebitsAndCredits diff --git a/batch_test.go b/batch_test.go index b54cfb17f..90c019083 100644 --- a/batch_test.go +++ b/batch_test.go @@ -1419,6 +1419,43 @@ func TestBatch__CalculateBalancedOffsetDebit(t *testing.T) { } } +func TestBatch_OffsetAddenda05(t *testing.T) { + file, err := ReadFile(filepath.Join("test", "testdata", "ppd-debit.ach")) + require.NoError(t, err) + + entries := file.Batches[0].GetEntries() + require.Len(t, entries, 1) + + addenda05 := NewAddenda05() + addenda05.PaymentRelatedInformation = "abc123" + addenda05.SequenceNumber = 1 + addenda05.EntryDetailSequenceNumber = 2 + + file.Batches[0].WithOffset(&Offset{ + RoutingNumber: "121042882", + AccountNumber: "123456789", + AccountType: OffsetChecking, + Description: "test offset", + Addenda05: []*Addenda05{addenda05}, + }) + + // retabulate + err = file.Batches[0].Create() + require.NoError(t, err) + + err = file.Create() + require.NoError(t, err) + + // verify offset entry was added + entries = file.Batches[0].GetEntries() + require.Len(t, entries, 2) + require.Empty(t, entries[0].Addenda05) + + // Check the Addenda05 on our offset record PaymentRelatedInformation + require.Len(t, entries[1].Addenda05, 1) + require.Equal(t, "abc123", entries[1].Addenda05[0].PaymentRelatedInformation) +} + func TestBatch__CalculateBalancedOffsetDebitAndCredit(t *testing.T) { off := &Offset{ RoutingNumber: "121042882", diff --git a/batcher.go b/batcher.go index 542ee31dc..a685d9da2 100644 --- a/batcher.go +++ b/batcher.go @@ -54,12 +54,21 @@ type Batcher interface { SetValidation(*ValidateOpts) } -// Offset contains the associated information to append an 'Offset Record' on an ACH batch during Create. +// Offset contains the fields required to append a balancing EntryDetail to a Batch during Create. +// +// After tabulating the amounts and counts a batch with an Offset field populated will create +// an EntryDetail record that balances the net debit/credit of the file. This is often used to upload +// balanced files (files whose debit and credit totals match). +// +// The EntryDetail offset record will have an IndividualName of "OFFSET" and any existing EntryDetails +// with an exactly matching IndividualName will be deleted from the file. type Offset struct { RoutingNumber string `json:"routingNumber"` AccountNumber string `json:"accountNumber"` AccountType OffsetAccountType `json:"accountType"` Description string `json:"description"` + + Addenda05 []*Addenda05 `json:"addenda05"` } type OffsetAccountType string diff --git a/openapi.yaml b/openapi.yaml index 4602a7e50..79c7730de 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2571,6 +2571,15 @@ components: - totalDebit - totalCredit Offset: + description: | + Offset contains the fields required to append a balancing EntryDetail to a Batch during Create. + + After tabulating the amounts and counts a batch with an Offset field populated will create + an EntryDetail record that balances the net debit/credit of the file. This is often used to upload + balanced files (files whose debit and credit totals match). + + The EntryDetail offset record will have an IndividualName of "OFFSET" and any existing EntryDetails + with an exactly matching IndividualName will be deleted from the file. properties: routingNumber: type: string @@ -2590,6 +2599,11 @@ components: type: string description: Memo for Offset EntryDetail record example: OFFSET + addenda05: + type: array + description: List of Addenda05 records + items: + $ref: '#/components/schemas/Addenda05' required: - routingNumber - accountNumber diff --git a/server/files_test.go b/server/files_test.go index 715385295..de0b21c68 100644 --- a/server/files_test.go +++ b/server/files_test.go @@ -178,7 +178,7 @@ func TestFiles_CreateWithOffset(t *testing.T) { require.Equal(t, "088888880123460", entries[1].TraceNumber) offset := reflect.ValueOf(b).Elem().FieldByName("offset") - expected := `&ach.Offset{RoutingNumber:"987654320", AccountNumber:"123123123", AccountType:"checking", Description:"OFFSET"}` + expected := `&ach.Offset{RoutingNumber:"987654320", AccountNumber:"123123123", AccountType:"checking", Description:"OFFSET", Addenda05:[]*ach.Addenda05(nil)}` require.Equal(t, expected, fmt.Sprintf("%#v", offset)) }