Skip to content

Commit

Permalink
Merge branch 'master' into Support-XCK---Destroyed-Check-Entry-#347
Browse files Browse the repository at this point in the history
  • Loading branch information
bkmoovio committed Nov 14, 2018
2 parents 3d6012e + 8774163 commit 46c8b97
Show file tree
Hide file tree
Showing 28 changed files with 3,479 additions and 343 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ACH is under active development but already in production for multiple companies
| SEC Code | Name | Read Example | Write Example |
|----------|---------------------------------------|-----------------------------------|------------------------------------|
| ACK | Acknowledgment Entry for CCD | [Link](test/ach-ack-read/main.go) | [Link](test/ach-ack-write/main.go) |
| ADV | Automated Accounting Advice | [Link](test/ach-adv-read/main.go) | [Link](test/ach-adv-write/main.go) |
| ARC | Accounts Receivable Entry | [Link](test/ach-arc-read/main.go) | [Link](test/ach-arc-write/main.go) |
| ATX | Acknowledgment Entry for CTX | [Link](test/ach-atx-read/main.go) | [Link](test/ach-atx-write/main.go) |
| BOC | Back Office Conversion | [Link](test/ach-boc-read/main.go) | [Link](test/ach-boc-write/main.go) |
Expand Down
187 changes: 187 additions & 0 deletions advBatchControl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// 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"
"strconv"
"strings"
"unicode/utf8"
)

// ADVBatchControl contains entry counts, dollar total and has totals for all
// entries contained in the preceding batch
type ADVBatchControl struct {
// ID is a client defined string used as a reference to this record.
ID string `json:"id"`
// RecordType defines the type of record in the block.
recordType string
// ServiceClassCode ACH Mixed Debits and Credits ‘200’
// ACH Credits Only ‘220’
// ACH Debits Only ‘225'
// Same as 'ServiceClassCode' in BatchHeaderRecord
ServiceClassCode int `json:"serviceClassCode"`
// EntryAddendaCount is a tally of each Entry Detail Record and each Addenda
// Record processed, within either the batch or file as appropriate.
EntryAddendaCount int `json:"entryAddendaÇount"`
// validate the Receiving DFI Identification in each Entry Detail Record is hashed
// to provide a check against inadvertent alteration of data contents due
// to hardware failure or program error
//
// In this context the Entry Hash is the sum of the corresponding fields in the
// Entry Detail Records on the file.
EntryHash int `json:"entryHash"`
// TotalDebitEntryDollarAmount Contains accumulated Entry debit totals within the batch.
TotalDebitEntryDollarAmount int `json:"totalDebit"`
// TotalCreditEntryDollarAmount Contains accumulated Entry credit totals within the batch.
TotalCreditEntryDollarAmount int `json:"totalCredit"`
// ACHOperatorData is an alphanumeric code used to identify an ACH Operator
ACHOperatorData string `json:"achOperatorData"`
// ODFIIdentification the routing number is used to identify the DFI originating entries within a given branch.
ODFIIdentification string `json:"ODFIIdentification"`
// BatchNumber this number is assigned in ascending sequence to each batch by the ODFI
// or its Sending Point in a given file of entries. Since the batch number
// in the Batch Header Record and the Batch Control Record is the same,
// the ascending sequence number should be assigned by batch and not by record.
BatchNumber int `json:"batchNumber"`
// validator is composed for data validation
validator
// converters is composed for ACH to golang Converters
converters
}

// Parse takes the input record string and parses the EntryDetail values
func (bc *ADVBatchControl) Parse(record string) {
if utf8.RuneCountInString(record) != 94 {
return
}

// 1-1 Always "8"
bc.recordType = "8"
// 2-4 This is the same as the "Service code" field in previous Batch Header Record
bc.ServiceClassCode = bc.parseNumField(record[1:4])
// 5-10 Total number of Entry Detail Record in the batch
bc.EntryAddendaCount = bc.parseNumField(record[4:10])
// 11-20 Total of all positions 4-11 on each Entry Detail Record in the batch. This is essentially the sum of all the RDFI routing numbers in the batch.
// If the sum exceeds 10 digits (because you have lots of Entry Detail Records), lop off the most significant digits of the sum until there are only 10
bc.EntryHash = bc.parseNumField(record[10:20])
// 21-32 Number of cents of debit entries within the batch
bc.TotalDebitEntryDollarAmount = bc.parseNumField(record[20:40])
// 33-44 Number of cents of credit entries within the batch
bc.TotalCreditEntryDollarAmount = bc.parseNumField(record[40:60])
// 45-54 ACH Operator Data
bc.ACHOperatorData = strings.TrimSpace(record[60:79])
// 80-87 This is the same as the "ODFI identification" field in previous Batch Header Record
bc.ODFIIdentification = bc.parseStringField(record[79:87])
// 88-94 This is the same as the "Batch number" field in previous Batch Header Record
bc.BatchNumber = bc.parseNumField(record[87:94])
}

// NewADVBatchControl returns a new ADVBatchControl with default values for none exported fields
func NewADVBatchControl() *ADVBatchControl {
return &ADVBatchControl{
recordType: "8",
ServiceClassCode: 280,
EntryHash: 1,
BatchNumber: 1,
}
}

// String writes the ADVBatchControl struct to a 94 character string.
func (bc *ADVBatchControl) String() string {
var buf strings.Builder
buf.Grow(94)
buf.WriteString(bc.recordType)
buf.WriteString(fmt.Sprintf("%v", bc.ServiceClassCode))
buf.WriteString(bc.EntryAddendaCountField())
buf.WriteString(bc.EntryHashField())
buf.WriteString(bc.TotalDebitEntryDollarAmountField())
buf.WriteString(bc.TotalCreditEntryDollarAmountField())
buf.WriteString(bc.ACHOperatorDataField())
buf.WriteString(bc.ODFIIdentificationField())
buf.WriteString(bc.BatchNumberField())
return buf.String()
}

// Validate performs NACHA format rule checks on the record and returns an error if not Validated
// The first error encountered is returned and stops that parsing.
func (bc *ADVBatchControl) Validate() error {
if err := bc.fieldInclusion(); err != nil {
return err
}
if bc.recordType != "8" {
msg := fmt.Sprintf(msgRecordType, 7)
return &FieldError{FieldName: "recordType", Value: bc.recordType, Msg: msg}
}
if err := bc.isServiceClass(bc.ServiceClassCode); err != nil {
return &FieldError{FieldName: "ServiceClassCode", Value: strconv.Itoa(bc.ServiceClassCode), Msg: err.Error()}
}

if err := bc.isAlphanumeric(bc.ACHOperatorData); err != nil {
return &FieldError{FieldName: "ACHOperatorData", Value: bc.ACHOperatorData, Msg: err.Error()}
}
return nil
}

// fieldInclusion validate mandatory fields are not default values. If fields are
// invalid the ACH transfer will be returned.
func (bc *ADVBatchControl) fieldInclusion() error {
if bc.recordType == "" {
return &FieldError{
FieldName: "recordType",
Value: bc.recordType,
Msg: msgFieldInclusion + ", did you use NewADVBatchControl()?"}
}
if bc.ServiceClassCode == 0 {
return &FieldError{
FieldName: "ServiceClassCode",
Value: strconv.Itoa(bc.ServiceClassCode),
Msg: msgFieldInclusion + ", did you use NewADVBatchControl()?",
}
}
if bc.ODFIIdentification == "000000000" || bc.ODFIIdentification == "" {
return &FieldError{
FieldName: "ODFIIdentification",
Value: bc.ODFIIdentificationField(),
Msg: msgFieldInclusion + ", did you use NewADVBatchControl()?",
}
}
return nil
}

// EntryAddendaCountField gets a string of the addenda count zero padded
func (bc *ADVBatchControl) EntryAddendaCountField() string {
return bc.numericField(bc.EntryAddendaCount, 6)
}

// EntryHashField get a zero padded EntryHash
func (bc *ADVBatchControl) EntryHashField() string {
return bc.numericField(bc.EntryHash, 10)
}

//TotalDebitEntryDollarAmountField get a zero padded Debit Entry Amount
func (bc *ADVBatchControl) TotalDebitEntryDollarAmountField() string {
return bc.numericField(bc.TotalDebitEntryDollarAmount, 20)
}

// TotalCreditEntryDollarAmountField get a zero padded Credit Entry Amount
func (bc *ADVBatchControl) TotalCreditEntryDollarAmountField() string {
return bc.numericField(bc.TotalCreditEntryDollarAmount, 20)
}

// ACHOperatorDataField get the ACHOperatorData right padded
func (bc *ADVBatchControl) ACHOperatorDataField() string {
return bc.alphaField(bc.ACHOperatorData, 19)
}

// ODFIIdentificationField get the odfi number zero padded
func (bc *ADVBatchControl) ODFIIdentificationField() string {
return bc.stringField(bc.ODFIIdentification, 8)
}

// BatchNumberField gets a string of the batch number zero padded
func (bc *ADVBatchControl) BatchNumberField() string {
return bc.numericField(bc.BatchNumber, 7)
}
Loading

0 comments on commit 46c8b97

Please sign in to comment.