Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

batch: tabulate on build/create calls #388

Merged
merged 1 commit into from
Dec 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 25 additions & 28 deletions batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package ach

import (
"encoding/json"
"errors"
"fmt"
"strconv"
Expand All @@ -25,6 +26,23 @@ type Batch struct {
converters
}

func (b *Batch) UnmarshalJSON(p []byte) error {
b.Header = NewBatchHeader()
b.Control = NewBatchControl()
b.ADVControl = NewADVBatchControl()

type Alias Batch
aux := struct {
*Alias
}{
(*Alias)(b),
}
if err := json.Unmarshal(p, &aux); err != nil {
return err
}
return nil
}

// NewBatch takes a BatchHeader and returns a matching SEC code batch type that is a batcher. Returns an error if the SEC code is not supported.
func NewBatch(bh *BatchHeader) (Batcher, error) {
switch bh.StandardEntryClassCode {
Expand Down Expand Up @@ -190,20 +208,7 @@ func (batch *Batch) build() error {

if !batch.IsADV() {
for i, entry := range batch.Entries {
entryCount++

// Add in Addenda Count
if entry.Addenda02 != nil {
entryCount++
}
entryCount = entryCount + len(entry.Addenda05)
if entry.Addenda98 != nil {
entryCount++
}

if entry.Addenda99 != nil {
entryCount++
}
entryCount += 1 + entry.addendaCount()

currentTraceNumberODFI, err := strconv.Atoi(entry.TraceNumberField()[:8])
if err != nil {
Expand All @@ -217,7 +222,7 @@ func (batch *Batch) build() error {

// Add a sequenced TraceNumber if one is not already set. Have to keep original trance number Return and NOC entries
if currentTraceNumberODFI != batchHeaderODFI {
batch.Entries[i].SetTraceNumber(batch.Header.ODFIIdentification, seq)
entry.SetTraceNumber(batch.Header.ODFIIdentification, seq)
}
seq++
addendaSeq := 1
Expand Down Expand Up @@ -307,6 +312,10 @@ func (batch *Batch) GetEntries() []*EntryDetail {

// AddEntry appends an EntryDetail to the Batch
func (batch *Batch) AddEntry(entry *EntryDetail) {
if entry == nil {
return
}

batch.category = entry.Category
batch.Entries = append(batch.Entries, entry)
}
Expand Down Expand Up @@ -395,19 +404,7 @@ func (batch *Batch) isBatchEntryCount() error {

if !batch.IsADV() {
for _, entry := range batch.Entries {
entryCount++

// Add in Addenda Count
if entry.Addenda02 != nil {
entryCount++
}
entryCount = entryCount + len(entry.Addenda05)
if entry.Addenda98 != nil {
entryCount++
}
if entry.Addenda99 != nil {
entryCount++
}
entryCount += 1 + entry.addendaCount()
}
if entryCount != batch.Control.EntryAddendaCount {
msg := fmt.Sprintf(msgBatchCalculatedControlEquality, entryCount, batch.Control.EntryAddendaCount)
Expand Down
2 changes: 1 addition & 1 deletion batcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (e *BatchError) Error() string {
var (
// generic messages
msgBatchHeaderControlEquality = "header %v is not equal to control %v"
msgBatchCalculatedControlEquality = "calculated %v is out-of-balance with control %v"
msgBatchCalculatedControlEquality = "calculated %v is out-of-balance with batch control %v"
msgBatchAscending = "%v is less than last %v. Must be in ascending order"
// specific messages for error
msgBatchCompanyEntryDescription = "Company entry description %v is not valid for batch type %v"
Expand Down
19 changes: 19 additions & 0 deletions entryDetail.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,22 @@ func (ed *EntryDetail) CreditOrDebit() string {
func (ed *EntryDetail) AddAddenda05(addenda05 *Addenda05) {
ed.Addenda05 = append(ed.Addenda05, addenda05)
}

// addendaCount returns the count of Addenda records added onto this EntryDetail
func (ed *EntryDetail) addendaCount() (n int) {
if ed.Addenda02 != nil {
n += 1
}
for i := range ed.Addenda05 {
if ed.Addenda05[i] != nil {
n += 1
}
}
if ed.Addenda98 != nil {
n += 1
}
if ed.Addenda99 != nil {
n += 1
}
return n
}
23 changes: 17 additions & 6 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (

// Errors strings specific to parsing a Batch container
var (
msgFileCalculatedControlEquality = "calculated %v is out-of-balance with control %v"
msgFileCalculatedControlEquality = "calculated %v is out-of-balance with file control %v"
// specific messages
msgRecordLength = "must be 94 characters and found %d"
msgFileBatchOutside = "outside of current batch"
Expand Down Expand Up @@ -118,11 +118,12 @@ func FileFromJSON(bs []byte) (*File, error) {

// Read FileHeader
header := fileHeader{
Header: NewFileHeader(),
Header: file.Header,
}
if err := json.NewDecoder(bytes.NewReader(bs)).Decode(&header); err != nil {
return nil, fmt.Errorf("problem reading FileHeader: %v", err)
}
file.Header = header.Header

if !file.IsADV() {
// Read FileControl
Expand All @@ -148,14 +149,15 @@ func FileFromJSON(bs []byte) (*File, error) {
if err := file.setBatchesFromJSON(bs); err != nil {
return nil, err
}
file.Header = header.Header

if !file.IsADV() {
file.Control.BatchCount = len(file.Batches)
} else {

file.ADVControl.BatchCount = len(file.Batches)
}

if err := file.Create(); err != nil {
return file, err
}
return file, nil
}

Expand All @@ -177,7 +179,7 @@ func (f *File) setBatchesFromJSON(bs []byte) error {
if err := json.Unmarshal(bs, &batches); err != nil {
return err
}
// Clear out any nil batchs
// Clear out any nil batchess
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

batches

for i := range f.Batches {
if f.Batches[i] == nil {
f.Batches = append(f.Batches[:i], f.Batches[i+1:]...)
Expand All @@ -188,6 +190,9 @@ func (f *File) setBatchesFromJSON(bs []byte) error {
if batches.Batches[i] == nil {
continue
}
if err := batches.Batches[i].build(); err != nil {
return fmt.Errorf("batch %s: %v", batches.Batches[i].Header.ID, err)
}
f.Batches = append(f.Batches, batches.Batches[i])
}
return nil
Expand All @@ -214,6 +219,12 @@ func (f *File) Create() error {
totalCreditAmount := 0

for i, batch := range f.Batches {
if v := f.Batches[i].GetHeader(); v == nil {
f.Batches[i].SetHeader(NewBatchHeader())
}
if v := f.Batches[i].GetControl(); v == nil {
f.Batches[i].SetControl(NewBatchControl())
}

// create ascending batch numbers
f.Batches[i].GetHeader().BatchNumber = batchSeq
Expand Down
38 changes: 36 additions & 2 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,14 @@ func TestFile__readFromJson(t *testing.T) {
t.Fatal(err)
}

// Ensure the file is valid
if err := file.Create(); err != nil {
t.Error(err)
}
if err := file.Validate(); err != nil {
t.Error(err)
}

if file.ID != "adam-01" {
t.Errorf("file.ID: %s", file.ID)
}
Expand All @@ -422,15 +430,15 @@ func TestFile__readFromJson(t *testing.T) {
}
batch := file.Batches[0]
batchControl := batch.GetControl()
if batchControl.EntryAddendaCount != 2 {
if batchControl.EntryAddendaCount != 1 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkmoovio Shouldn't this be 0? The ppd-valid.json file has no addenda records.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why, but Batch.build calls entryCount++ (which is now 1 + entry.addendaCount()). That doesn't seem right?

Copy link
Contributor

@bkmoovio bkmoovio Dec 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Adam,

EntryAddendaCount in the Company/Batch Control Record and File Control Record can be confusing as it is not the AddendaCount for an Entry. It is (for reference page OR108) the tally for each Entry Detail Record processed and each Addenda Record processed, within either the batch or file, as appropriate. For a BatchPPD with one EntryDetail , and no Addenda05, EntryAddendaCount should be 1, If the one EntryDetail has an Addenda05, EntryAddendaCount would be 2. Hopefully that helps . (Clear as Mud)

For the second question:

Prior to removing addendumer change, the code set entryCount as follows:

for i, entry := range batch.Entries {
entryCount = entryCount + 1 + len(entry.Addendum)....

going forward I had added +1 to entryCount for each entry (EntryDetail) + each Addenda property if the property was defined. Addenda02, len(Addenda05), Addenda98, Addenda99. it looks like you created a function for that which is great. I should have.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding ++,

golang/go#21263

To funny... :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay. Those names are confusing but it makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well that's a good idea.

t.Errorf("EntryAddendaCount: %d", batchControl.EntryAddendaCount)
}

// Control
if file.Control.BatchCount != 1 {
t.Errorf("BatchCount: %d", file.Control.BatchCount)
}
if file.Control.EntryAddendaCount != 2 {
if file.Control.EntryAddendaCount != 1 {
t.Errorf("File Control EntryAddendaCount: %d", file.Control.EntryAddendaCount)
}
if file.Control.TotalDebitEntryDollarAmountInFile != 0 || file.Control.TotalCreditEntryDollarAmountInFile != 100000 {
Expand All @@ -448,3 +456,29 @@ func TestFile__readFromJson(t *testing.T) {
t.Error(err)
}
}

// TestFile__jsonFileNoControlBlobs will read an ach.File from its JSON form, but the JSON has no
// batchControl or fileControl sub-objects.
func TestFile__jsonFileNoControlBlobs(t *testing.T) {
path := filepath.Join("test", "testdata", "ppd-no-control-blobs-valid.json")
bs, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
}

file, err := FileFromJSON(bs)
if err != nil {
t.Fatal(err)
}

if err := file.Create(); err != nil {
t.Fatal(err)
}
if err := file.Validate(); err != nil {
t.Fatal(err)
}

if file.ID != "adam-01" {
t.Errorf("file.ID: %s", file.ID)
}
}
1 change: 1 addition & 0 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ VERSION := $(shell grep -Eo '(v[0-9]+[\.][0-9]+[\.][0-9]+([-a-zA-Z0-9]*)?)' vers
build:
go fmt ./...
@mkdir -p ./bin/
go build github.com/moov-io/ach
CGO_ENABLED=0 go build -o ./bin/server github.com/moov-io/ach/cmd/server

generate: clean
Expand Down
2 changes: 1 addition & 1 deletion reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func NewReader(r io.Reader) *Reader {
// on the first character of each line. It also enforces ACH formatting rules and returns
// the appropriate error if issues are found.
//
// A parsed file may not be valid and callers should confirm with Validate(). Invalid files may
// A parsed file may not be valid and callers should confirm with Create() Validate(). Invalid files may
// be rejected by other Financial Institutions or ACH tools.
func (r *Reader) Read() (File, error) {
r.lineNum = 0
Expand Down
56 changes: 56 additions & 0 deletions test/testdata/ppd-no-control-blobs-valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"id": "adam-01",
"fileHeader": {
"id": "adam-01",
"immediateDestination": "231380104",
"immediateOrigin": "121042882",
"fileCreationDate": "2018-10-08T00:00:00Z",
"fileCreationTime": "0000-01-01T00:00:00Z",
"fileIDModifier": "A",
"immediateDestinationName": "Citadel",
"immediateOriginName": "Wells Fargo"
},
"batches": [
{
"batchHeader": {
"id": "adam-01",
"serviceClassCode": 200,
"companyName": "Wells Fargo",
"companyIdentification": "121042882",
"standardEntryClassCode": "PPD",
"companyEntryDescription": "Trans. Des",
"effectiveEntryDate": "2018-10-09T00:00:00Z",
"ODFIIdentification": "12104288",
"batchNumber": 1
},
"entryDetails": [
{
"id": "adam-01",
"transactionCode": 22,
"RDFIIdentification": "23138010",
"checkDigit": "4",
"DFIAccountNumber": "81967038518 ",
"amount": 100000,
"identificationNumber": "#83738AB# ",
"individualName": "Steven Tander ",
"discretionaryData": " ",
"addendaRecordIndicator": 1,
"traceNumber": "121042880000001",
"category": "Forward",
"addenda05": [
{
"entryDetailSequenceNumber": 1,
"sequenceNumber": 1,
"paymentRelatedInformation": "Bonus for working on #OSS!",
"typeCode": "",
"id": "gvluehibyuuqdoajiqfn"
}
]
}
]
}
],
"IATBatches": null,
"NotificationOfChange": null,
"ReturnEntries": null
}
6 changes: 3 additions & 3 deletions test/testdata/ppd-valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@
"identificationNumber": "#83738AB# ",
"individualName": "Steven Tander ",
"discretionaryData": " ",
"addendaRecordIndicator": 1,
"addendaRecordIndicator": 0,
"traceNumber": "121042880000001",
"category": "Forward"
}
],
"batchControl": {
"id": "adam-01",
"serviceClassCode": 200,
"entryAddendaÇount": 2,
"entryAddendaÇount": 0,
"entryHash": 23138010,
"totalDebit": 0,
"totalCredit": 100000,
Expand All @@ -57,7 +57,7 @@
"id": "adam-01",
"batchCount": 1,
"blockCount": 1,
"entryAddendaCount": 2,
"entryAddendaCount": 0,
"entryHash": 23138010,
"totalDebit": 0,
"totalCredit": 100000
Expand Down