Skip to content

Commit

Permalink
Add BatchENR
Browse files Browse the repository at this point in the history
Fixes: #343
  • Loading branch information
adamdecaf committed Nov 2, 2018
1 parent ad70d15 commit 61f3301
Show file tree
Hide file tree
Showing 12 changed files with 587 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then test -z $(gofmt -s -l $GOFILES); fi
- go test ./...
- misspell -error -locale US $GOFILES
- gocyclo -over 19 $GOFILES
- gocyclo -over 25 $GOFILES
- golint -set_exit_status $GOFILES
- megacheck ./...
after_success:
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ ACH is under active development but already in production for multiple companies
* CIE (Customer-Initiated Entry)
* COR (Automated Notification of Change(NOC))
* CTX (Corporate Trade Exchange)
* DNE (Death Notification Entry)
* DNE (Death Notification Entry)
* ENR (Automatic Enrollment Entry)
* IAT (International ACH Transactions)
* POP (Point of Purchase)
* POS (Point of Sale)
Expand Down
2 changes: 2 additions & 0 deletions batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ func NewBatch(bh *BatchHeader) (Batcher, error) {
return NewBatchCTX(bh), nil
case "DNE":
return NewBatchDNE(bh), nil
case "ENR":
return NewBatchENR(bh), nil
case "IAT":
msg := fmt.Sprintf(msgFileIATSEC, bh.StandardEntryClassCode)
return nil, &FileError{FieldName: "StandardEntryClassCode", Value: bh.StandardEntryClassCode, Msg: msg}
Expand Down
125 changes: 125 additions & 0 deletions batchENR.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2018 The Moov Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

import (
"fmt"
"strings"
"unicode/utf8"
)

// BatchENR is a non-monetary entry that enrolls a person with an agency of the US government
// for a depository financial institution.
//
// Allowed TransactionCode values: 22 Demand Credit, 27 Demand Debit, 32 Savings Credit, 37 Savings Debit
type BatchENR struct {
batch
}

var (
msgBatchENRAddendaType = "%T found where Addenda05 is required for SEC type ENR"
)

func NewBatchENR(bh *BatchHeader) *BatchENR {
batch := new(BatchENR)
batch.SetControl(NewBatchControl())
batch.SetHeader(bh)
return batch
}

// Validate ensures the batch meets NACHA rules specific to this batch type.
func (batch *BatchENR) Validate() error {
if err := batch.verify(); err != nil {
return err
}

// Batch Header checks
if batch.Header.StandardEntryClassCode != "ENR" {
msg := fmt.Sprintf(msgBatchSECType, batch.Header.StandardEntryClassCode, "ENR")
return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg}
}
if batch.Header.CompanyEntryDescription != "AUTOENROLL" {
msg := fmt.Sprintf(msgBatchCompanyEntryDescription, batch.Header.CompanyEntryDescription, "ENR, must be AUTOENROLL")
return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "CompanyEntryDescription", Msg: msg}
}

// Range over Entries
for _, entry := range batch.Entries {
if err := entry.Validate(); err != nil {
return err
}

if entry.Amount != 0 {
msg := fmt.Sprintf(msgBatchAmountZero, entry.Amount, "ENR")
return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Amount", Msg: msg}
}

switch entry.TransactionCode {
case 22, 27, 32, 37:
default:
msg := fmt.Sprintf(msgBatchTransactionCode, entry.TransactionCode, "ENR")
return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "TransactionCode", Msg: msg}
}

// ENR must have one Addenda05
if len(entry.Addendum) <= 0 || len(entry.Addendum) > 9999 {
msg := fmt.Sprintf(msgBatchAddendaCount, len(entry.Addendum), 1, batch.Header.StandardEntryClassCode)
return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "AddendaCount", Msg: msg}
}
}

// Check Addenda05
return batch.isAddenda05()
}

// Create builds the batch sequence numbers and batch control.
func (batch *BatchENR) Create() error {
// generates sequence numbers and batch control
if err := batch.build(); err != nil {
return err
}
return batch.Validate()
}

// isAddenda05 verifies that a Addenda05 exists for each EntryDetail and is Validated
func (batch *BatchENR) isAddenda05() error {
if len(batch.Entries) != 1 {
return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "entries", Msg: msgBatchEntries}
}

for _, entry := range batch.Entries {
// Addenda type assertion must be Addenda05
addenda05, ok := entry.Addendum[0].(*Addenda05)
if !ok {
msg := fmt.Sprintf(msgBatchENRAddendaType, entry.Addendum[0])
return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Addendum", Msg: msg}
}
// Addenda05 must be Validated
if err := addenda05.Validate(); err != nil {
// convert the field error in to a batch error for a consistent api
if e, ok := err.(*FieldError); ok {
return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: e.FieldName, Msg: e.Msg}
}
}
}
return nil
}

// EnrolleeClassificationCode (also called Representative Payee Indicator) returns a code from a specific Addenda05 record.
// These codes represent:
// 0: (no) - Initiated by beneficiary
// 1: (yes) - Initiated by someone other than named beneficiary
// A: Enrollee is a consumer
// b: Enrollee is a company
//
// An empty code returned is an error.
func (batch *BatchENR) EnrolleeClassificationCode(addenda *Addenda05) string {
// PaymentRelatedInformation will end with X/ where X is our code
if v := addenda.PaymentRelatedInformation; !strings.HasSuffix(v, `\`) || utf8.RuneCountInString(v) < 2 {
return ""
} else {
return v[len(v)-2 : len(v)-1] // second to last character
}
}
Loading

0 comments on commit 61f3301

Please sign in to comment.