diff --git a/bundle.go b/bundle.go index f3c97252..fd48f69e 100644 --- a/bundle.go +++ b/bundle.go @@ -44,6 +44,8 @@ type Bundle struct { ID string `json:"id"` // BundleHeader is a Bundle Header Record BundleHeader *BundleHeader `json:"bundleHeader,omitempty"` + // Credits may be included as part of a bundle, and are often used to represent deposit tickets + Credits []*Credit `json:"credits,omitempty"` // Checks are Check Items: Check Detail Records, Check Detail Addendum Records, and Image Views Checks []*CheckDetail `json:"checks,omitempty"` // Returns are Return Items: Return Detail Records, Return Detail Addendum Records, and Image Views @@ -65,6 +67,9 @@ func (b *Bundle) setRecordType() { return } b.BundleHeader.setRecordType() + for i := range b.Credits { + b.Credits[i].setRecordType() + } for i := range b.Checks { b.Checks[i].setRecordType() } @@ -96,8 +101,8 @@ func (b *Bundle) Validate() error { return nil } -// build creates a valid Bundle by building BundleControl. An error is returned if -// the bundle being built has invalid records. +// build creates a valid Bundle by building BundleControl. An error is returned if the bundle being +// built has invalid records. func (b *Bundle) build() error { // Requires a valid BundleHeader if err := b.BundleHeader.Validate(); err != nil { @@ -115,9 +120,21 @@ func (b *Bundle) build() error { bundleTotalAmount := 0 micrValidTotalAmount := 0 bundleImagesCount := 0 - // The current Implementation doe snot support CreditItems as part of a bundle so BundleControl.CreditIndicator = 0 + // The current Implementation does not support CreditItems as part of a bundle so BundleControl.CreditIndicator = 0 creditIndicator := 0 + // Credit items + for _, cr := range b.Credits { + if err := b.ValidateCredit(cr); err != nil { + return err + } + + // A credit record within a Bundle is typically used to present a + // deposit ticket, and its amount is not included in the bundle total + itemCount++ + bundleImagesCount += len(cr.ImageViewDetail) + } + // Forward Items for _, cd := range b.Checks { @@ -126,13 +143,13 @@ func (b *Bundle) build() error { return err } - itemCount = itemCount + 1 - bundleTotalAmount = bundleTotalAmount + cd.ItemAmount + itemCount++ + bundleTotalAmount += cd.ItemAmount if cd.MICRValidIndicator == 1 { - micrValidTotalAmount = micrValidTotalAmount + cd.ItemAmount + micrValidTotalAmount += cd.ItemAmount } - bundleImagesCount = bundleImagesCount + len(cd.ImageViewDetail) + bundleImagesCount += len(cd.ImageViewDetail) } // Return Items @@ -142,9 +159,9 @@ func (b *Bundle) build() error { if err := b.ValidateReturnItems(rd); err != nil { return err } - itemCount = itemCount + 1 - bundleTotalAmount = bundleTotalAmount + rd.ItemAmount - bundleImagesCount = bundleImagesCount + len(rd.ImageViewDetail) + itemCount++ + bundleTotalAmount += rd.ItemAmount + bundleImagesCount += len(rd.ImageViewDetail) } // build a BundleControl record @@ -204,6 +221,20 @@ func (b *Bundle) GetReturns() []*ReturnDetail { return b.Returns } +func (b *Bundle) ValidateCredit(cr *Credit) error { + for _, ivDetail := range cr.ImageViewDetail { + if err := ivDetail.Validate(); err != nil { + return err + } + } + for _, ivData := range cr.ImageViewData { + if err := ivData.Validate(); err != nil { + return err + } + } + return nil +} + // ValidateForwardItems calls Validate function for check items func (b *Bundle) ValidateForwardItems(cd *CheckDetail) error { // Validate items diff --git a/credit.go b/credit.go index 0603d77a..d377e6c6 100644 --- a/credit.go +++ b/credit.go @@ -47,6 +47,10 @@ type Credit struct { DebitCreditIndicator string `json:"debitCreditIndicator,omitempty"` // reserved is a field reserved for future use. Reserved should be blank. reserved string + + ImageViewDetail []ImageViewDetail `json:"imageViewDetail"` + ImageViewData []ImageViewData `json:"imageViewData"` + // validator is composed for image cash letter data validation validator // converters is composed for image cash letter to golang Converters @@ -268,3 +272,11 @@ func (cr *Credit) DebitCreditIndicatorField() string { func (cr *Credit) reservedField() string { return cr.alphaField(cr.reserved, 3) } + +func (cr *Credit) AddImageViewData(ivData ImageViewData) { + cr.ImageViewData = append(cr.ImageViewData, ivData) +} + +func (cr *Credit) AddImageViewDetail(ivDetail ImageViewDetail) { + cr.ImageViewDetail = append(cr.ImageViewDetail, ivDetail) +} diff --git a/file.go b/file.go index 1ca5fdf4..3dc442bc 100644 --- a/file.go +++ b/file.go @@ -88,7 +88,7 @@ var ( msgFileCashLetterID = "%s is not unique" msgRecordType = "received expecting %d" msgFileCreditItem = "Credit item outside of cash letter" - msgFileCredit = "Credit outside of cash letter" + msgFileCredit = "Credit outside of cash letter or bundle" ) // FileError is an error describing issues validating a file diff --git a/fileControl.go b/fileControl.go index 436419f2..613a6d16 100644 --- a/fileControl.go +++ b/fileControl.go @@ -52,6 +52,13 @@ func NewFileControl() FileControl { return fc } +func (fc *FileControl) IsZero() bool { + if fc == nil { + return true + } + return *fc == (FileControl{}) || *fc == NewFileControl() +} + func (fc *FileControl) setRecordType() { if fc == nil { return diff --git a/fileHeader.go b/fileHeader.go index 9022526b..196267fc 100644 --- a/fileHeader.go +++ b/fileHeader.go @@ -102,6 +102,13 @@ func NewFileHeader() FileHeader { return fh } +func (fh *FileHeader) IsZero() bool { + if fh == nil { + return true + } + return *fh == (FileHeader{}) || *fh == NewFileHeader() +} + func (fh *FileHeader) setRecordType() { if fh == nil { return diff --git a/go.mod b/go.mod index ec97ff7a..bfae777d 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.5.0 // indirect @@ -30,7 +29,6 @@ require ( github.com/rickar/cal/v2 v2.1.13 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 712761ed..6688e278 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= -github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= @@ -18,11 +16,6 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -59,8 +52,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -80,7 +71,6 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -89,11 +79,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/imageViewDetail_test.go b/imageViewDetail_test.go index d330a364..6b465de7 100644 --- a/imageViewDetail_test.go +++ b/imageViewDetail_test.go @@ -57,7 +57,6 @@ func TestMockImageViewDetail(t *testing.T) { require.Equal(t, "", ivDetail.reserved) require.Equal(t, "0", ivDetail.OverrideIndicator) require.Equal(t, "", ivDetail.reservedTwo) - } // TestParseIVDetail validates parsing an ImageViewDetail diff --git a/reader.go b/reader.go index 144725eb..bbfe3ebe 100644 --- a/reader.go +++ b/reader.go @@ -191,16 +191,18 @@ func (r *Reader) Read() (File, error) { return r.File, r.error(err) } - if (FileHeader{}) == r.File.Header { - // There must be at least one File Header + // the file must have a non-zero header record + if r.File.Header.IsZero() { r.recordName = "FileHeader" return r.File, r.error(&FileError{Msg: msgFileHeader}) } - if (FileControl{}) == r.File.Control { - // There must be at least one File Control + + // the file must have a non-zero control record + if r.File.Control.IsZero() { r.recordName = "FileControl" return r.File, r.error(&FileError{Msg: msgFileControl}) } + return r.File, nil } @@ -279,7 +281,7 @@ func (r *Reader) parseLine() error { //nolint:gocyclo return err } if r.currentCashLetter.currentBundle == nil { - r.error(&FileError{Msg: msgFileBundleControl}) + return r.error(&FileError{Msg: msgFileBundleControl}) } // Add Bundle or ReturnBundle to CashLetter if r.currentCashLetter.currentBundle != nil { @@ -288,7 +290,7 @@ func (r *Reader) parseLine() error { //nolint:gocyclo return r.error(err) } r.currentCashLetter.AddBundle(r.currentCashLetter.currentBundle) - r.currentCashLetter.currentBundle = new(Bundle) + r.currentCashLetter.currentBundle = nil } case routingNumberSummaryPos, routingNumberSummaryEbcPos: if err := r.parseRoutingNumberSummary(); err != nil { @@ -326,9 +328,9 @@ func (r *Reader) parseLine() error { //nolint:gocyclo // parseFileHeader takes the input record string and parses the FileHeader values func (r *Reader) parseFileHeader() error { r.recordName = "FileHeader" - if (FileHeader{}) != r.File.Header { + if !r.File.Header.IsZero() { // There can only be one File Header per File - r.error(&FileError{Msg: msgFileHeader}) + return r.error(&FileError{Msg: msgFileHeader}) } lineOut, err := r.decodeLine(r.line) if err != nil { @@ -346,7 +348,7 @@ func (r *Reader) parseFileHeader() error { func (r *Reader) parseCashLetterHeader() error { r.recordName = "CashLetterHeader" if r.currentCashLetter.CashLetterHeader != nil { - // CashLetterHeader inside of current cash letter + // found duplicate cash letter header return r.error(&FileError{Msg: msgFileCashLetterInside}) } lineOut, err := r.decodeLine(r.line) @@ -359,6 +361,7 @@ func (r *Reader) parseCashLetterHeader() error { if err := clh.Validate(); err != nil { return r.error(err) } + // Passing CashLetterHeader into NewCashLetter creates a CashLetter cl := NewCashLetter(clh) r.addCurrentCashLetter(cl) @@ -368,11 +371,10 @@ func (r *Reader) parseCashLetterHeader() error { // parseBundleHeader takes the input record string and parses the BundleHeader values func (r *Reader) parseBundleHeader() error { r.recordName = "BundleHeader" - if r.currentCashLetter.currentBundle != nil { + if r.currentCashLetter.currentBundle != nil && + r.currentCashLetter.currentBundle.BundleHeader != nil { // BundleHeader inside of current Bundle - if r.currentCashLetter.currentBundle.BundleHeader != nil { - return r.error(&FileError{Msg: msgFileBundleInside}) - } + return r.error(&FileError{Msg: msgFileBundleInside}) } // Ensure we have a valid bundle header before building a bundle. lineOut, err := r.decodeLine(r.line) @@ -407,10 +409,10 @@ func (r *Reader) parseCheckDetail() error { if err := cd.Validate(); err != nil { return r.error(err) } + // Add CheckDetail - if r.currentCashLetter.currentBundle.BundleHeader != nil { - r.currentCashLetter.currentBundle.AddCheckDetail(cd) - } + r.currentCashLetter.currentBundle.AddCheckDetail(cd) + return nil } @@ -431,7 +433,6 @@ func (r *Reader) parseCheckDetailAddendumA() error { return r.error(err) } entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1 - // r.currentCashLetter.currentBundle.Checks[entryIndex].CheckDetailAddendumA = cdAddendumA r.currentCashLetter.currentBundle.Checks[entryIndex].AddCheckDetailAddendumA(cdAddendumA) return nil } @@ -493,9 +494,7 @@ func (r *Reader) parseReturnDetail() error { if err := rd.Validate(); err != nil { return r.error(err) } - if r.currentCashLetter.currentBundle.BundleHeader != nil { - r.currentCashLetter.currentBundle.AddReturnDetail(rd) - } + r.currentCashLetter.currentBundle.AddReturnDetail(rd) return nil } @@ -566,7 +565,6 @@ func (r *Reader) parseReturnDetailAddendumC() error { // parseReturnDetail*AddendumD takes the input record string and parses the ReturnDetail*AddendumD values func (r *Reader) parseReturnDetailAddendumD() error { r.recordName = "ReturnDetailAddendumD" - if r.currentCashLetter.currentBundle.GetReturns() == nil { msg := fmt.Sprint(msgFileBundleOutside) return r.error(&FileError{FieldName: "ReturnDetailAddendumD", Msg: msg}) @@ -588,39 +586,25 @@ func (r *Reader) parseReturnDetailAddendumD() error { // parseImageViewDetail takes the input record string and parses the ImageViewDetail values func (r *Reader) parseImageViewDetail() error { r.recordName = "ImageViewDetail" - if err := r.ImageViewDetail(); err != nil { + lineOut, err := r.decodeLine(r.line) + if err != nil { return err } - return nil -} + ivDetail := NewImageViewDetail() + ivDetail.Parse(lineOut) + if err := ivDetail.Validate(); err != nil { + return r.error(err) + } -// ImageViewDetail takes the input record string and parses ImageViewDetail for a check -func (r *Reader) ImageViewDetail() error { if r.currentCashLetter.currentBundle.GetChecks() != nil { - lineOut, err := r.decodeLine(r.line) - if err != nil { - return err - } - ivDetail := NewImageViewDetail() - ivDetail.Parse(lineOut) - if err := ivDetail.Validate(); err != nil { - return r.error(err) - } entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1 r.currentCashLetter.currentBundle.Checks[entryIndex].AddImageViewDetail(ivDetail) - } else if r.currentCashLetter.currentBundle.GetReturns() != nil { - lineOut, err := r.decodeLine(r.line) - if err != nil { - return err - } - ivDetail := NewImageViewDetail() - ivDetail.Parse(lineOut) - if err := ivDetail.Validate(); err != nil { - return r.error(err) - } entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1 r.currentCashLetter.currentBundle.Returns[entryIndex].AddImageViewDetail(ivDetail) + } else if len(r.currentCashLetter.currentBundle.Credits) > 0 { + entryIndex := len(r.currentCashLetter.currentBundle.Credits) - 1 + r.currentCashLetter.currentBundle.Credits[entryIndex].AddImageViewDetail(ivDetail) } else { msg := fmt.Sprint(msgFileBundleOutside) return r.error(&FileError{FieldName: "ImageViewDetail", Msg: msg}) @@ -632,31 +616,22 @@ func (r *Reader) ImageViewDetail() error { // parseImageViewData takes the input record string and parses the ImageViewData values func (r *Reader) parseImageViewData() error { r.recordName = "ImageViewData" - if err := r.ImageViewData(); err != nil { - return err + ivData := NewImageViewData() + ivData.ParseAndDecode(r.line, r.decodeLine) + if err := ivData.Validate(); err != nil { + return r.error(err) } - return nil -} -// ImageViewData takes the input record string and parses ImageViewData for a check -func (r *Reader) ImageViewData() error { if r.currentCashLetter.currentBundle.GetChecks() != nil { - ivData := NewImageViewData() - ivData.ParseAndDecode(r.line, r.decodeLine) - if err := ivData.Validate(); err != nil { - return r.error(err) - } entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1 r.currentCashLetter.currentBundle.Checks[entryIndex].AddImageViewData(ivData) } else if r.currentCashLetter.currentBundle.GetReturns() != nil { - ivData := NewImageViewData() - ivData.ParseAndDecode(r.line, r.decodeLine) - if err := ivData.Validate(); err != nil { - return r.error(err) - } entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1 r.currentCashLetter.currentBundle.Returns[entryIndex].AddImageViewData(ivData) + } else if len(r.currentCashLetter.currentBundle.Credits) > 0 { + entryIndex := len(r.currentCashLetter.currentBundle.Credits) - 1 + r.currentCashLetter.currentBundle.Credits[entryIndex].AddImageViewData(ivData) } else { msg := fmt.Sprint(msgFileBundleOutside) return r.error(&FileError{FieldName: "ImageViewData", Msg: msg}) @@ -668,37 +643,21 @@ func (r *Reader) ImageViewData() error { // parseImageViewAnalysis takes the input record string and parses ImageViewAnalysis values func (r *Reader) parseImageViewAnalysis() error { r.recordName = "ImageViewAnalysis" - if err := r.ImageViewAnalysis(); err != nil { + lineOut, err := r.decodeLine(r.line) + if err != nil { return err } - return nil -} + ivAnalysis := NewImageViewAnalysis() + ivAnalysis.Parse(lineOut) + if err := ivAnalysis.Validate(); err != nil { + return r.error(err) + } -// ImageViewAnalysis takes the input record string and parses ImageViewAnalysis for a check -func (r *Reader) ImageViewAnalysis() error { if r.currentCashLetter.currentBundle.GetChecks() != nil { - lineOut, err := r.decodeLine(r.line) - if err != nil { - return err - } - ivAnalysis := NewImageViewAnalysis() - ivAnalysis.Parse(lineOut) - if err := ivAnalysis.Validate(); err != nil { - return r.error(err) - } entryIndex := len(r.currentCashLetter.currentBundle.GetChecks()) - 1 r.currentCashLetter.currentBundle.Checks[entryIndex].AddImageViewAnalysis(ivAnalysis) } else if r.currentCashLetter.currentBundle.GetReturns() != nil { - lineOut, err := r.decodeLine(r.line) - if err != nil { - return err - } - ivAnalysis := NewImageViewAnalysis() - ivAnalysis.Parse(lineOut) - if err := ivAnalysis.Validate(); err != nil { - return r.error(err) - } entryIndex := len(r.currentCashLetter.currentBundle.GetReturns()) - 1 r.currentCashLetter.currentBundle.Returns[entryIndex].AddImageViewAnalysis(ivAnalysis) } else { @@ -713,7 +672,8 @@ func (r *Reader) ImageViewAnalysis() error { func (r *Reader) parseCredit() error { // Current implementation has the credit letter outside the bundle but within the cash letter r.recordName = "Credit" - if r.currentCashLetter.CashLetterHeader == nil { + if r.currentCashLetter.CashLetterHeader == nil && + r.currentCashLetter.currentBundle == nil { return r.error(&FileError{Msg: msgFileCredit}) } lineOut, err := r.decodeLine(r.line) @@ -725,6 +685,14 @@ func (r *Reader) parseCredit() error { if err := cr.Validate(); err != nil { return r.error(err) } + + // if we have an incomplete bundle, add the credit to the bundle + if r.currentCashLetter.currentBundle != nil { + r.currentCashLetter.currentBundle.Credits = append(r.currentCashLetter.currentBundle.Credits, cr) + return nil + } + + // otherwise add the credit to the cash letter r.currentCashLetter.AddCredit(cr) return nil } @@ -752,6 +720,7 @@ func (r *Reader) parseCreditItem() error { // parseBundleControl takes the input record string and parses the BundleControl values func (r *Reader) parseBundleControl() error { r.recordName = "BundleControl" + if r.currentCashLetter.currentBundle == nil || r.currentCashLetter.currentBundle.BundleControl == nil { return r.error(&FileError{Msg: msgFileBundleControl}) } diff --git a/reader_test.go b/reader_test.go index 56387ac1..5fe8fbc8 100644 --- a/reader_test.go +++ b/reader_test.go @@ -156,7 +156,7 @@ func TestTwoFileHeaders(t *testing.T) { r := NewReader(strings.NewReader(twoHeaders)) _, err := r.Read() fileErr := getFileError(t, err) - require.Equal(t, msgFileControl, fileErr.Msg) + require.Equal(t, msgFileHeader, fileErr.Msg) } // TestCashLetterHeaderErr validates error flows back from the parser @@ -876,3 +876,93 @@ func Test_DecodeEBCDIC(t *testing.T) { require.Equal(t, 3, n) require.Equal(t, r, utf8.RuneError) } + +func TestRead_multipleBundlesInCashLetter(t *testing.T) { + r := NewReader(strings.NewReader(multipleBundles)) + f, err := r.Read() + require.NoError(t, err) + require.Equal(t, 2, len(f.CashLetters[0].Bundles)) + require.Equal(t, "1", f.CashLetters[0].Bundles[0].BundleHeader.BundleSequenceNumber) + require.Equal(t, "2", f.CashLetters[0].Bundles[1].BundleHeader.BundleSequenceNumber) +} + +func TestRead_CreditInBundle(t *testing.T) { + r := NewReader(strings.NewReader(depositTicketAsCredit)) + f, err := r.Read() + require.NoError(t, err) + + bundles := f.CashLetters[0].Bundles + require.Equal(t, 1, len(bundles)) + credits := bundles[0].Credits + require.Equal(t, 1, len(credits)) + credit := credits[0] + require.Equal(t, 2, len(credit.ImageViewDetail)) + require.Equal(t, 2, len(credit.ImageViewData)) +} + +const multipleBundles = `0135T231380104121042882201810101237NCitadel Wells Fargo US +100123138010412104288220181010201810101237IGA1 Contact Name 5558675552 +62 123456789 031300012 5558881000000001000001 G101 +200123138010412104288220181010201810109999 1 01 +25 123456789 031300012 555888100001000001 GD1Y030B +261121042882201810101 938383 01 Test Payee Y10 +2711A 00340 CD Addendum B +2802121042882201810101 Y10A 0 +501031300012201810100000000000000000000000000000000000000 0 +52121042882201810101 1 Sec Orig Name Sec Auth Name SECURE 0 00000 0000001 +542202222222 10222222222222 +25 123456789 031300012 555888100001000002 GD1Y030B +262121042882201810102 938383 01 Test Payee Y10 +2711A 00340 CD Addendum B +2803121042882201810102 Y10A 0 +501031300012201810100000000000000000000000000000000000000 0 +52121042882201810101 1 Sec Orig Name Sec Auth Name SECURE 0 00000 0000001 +542202222222 10222222222222 +70001400000020000000000020000000002 0 +200123138010412104288220181010201810109999 2 01 +25 123456789 031300012 555888100001000001 GD1Y030B +261121042882201810101 938383 01 Test Payee Y10 +2711A 00340 CD Addendum B +2802121042882201810101 Y10A 0 +501031300012201810100000000000000000000000000000000000000 0 +52121042882201810101 1 Sec Orig Name Sec Auth Name SECURE 0 00000 0000001 +542202222222 10222222222222 +25 123456789 031300012 555888100001000002 GD1Y030B +262121042882201810102 938383 01 Test Payee Y10 +2711A 00340 CD Addendum B +2803121042882201810102 Y10A 0 +501031300012201810100000000000000000000000000000000000000 0 +52121042882201810101 1 Sec Orig Name Sec Auth Name SECURE 0 00000 0000001 +542202222222 10222222222222 +8511112222300000000100000000001 +70001400000020000000000020000000002 0 +900000010000001400000000200000000000002 201810100 +9900000200000038000000280000000000400000 0 ` + +const depositTicketAsCredit = `0135T123456789123456789201810101237NCitadel Wells Fargo US +100123138010412104288220181010201810101237IGA1 Contact Name 5558675552 +62 123456789 031300012 5558881000000001000001 G101 +200123138010412104288220181010201810109999 1 01 +61010910999940910 99992006050920060509383521210000010208812345 G13 +501031300012201809050000000000000000000000000000000000000 0 +52121042882202308161 1 Sec Orig Name Sec Auth Name SECURE 0 00000 0000001 +5010313000122023081600000000000100000 000000000000000 0 +52121042882202308161 1 Sec Orig Name Sec Auth Name SECURE 0 00000 0000001 +25 123456789 031300012 555888100001000001 GD1Y030B +261121042882201810101 938383 01 Test Payee Y10 +2711A 00340 CD Addendum B +2802121042882201810101 Y10A 0 +501031300012201810100000000000000000000000000000000000000 0 +52121042882201810101 1 Sec Orig Name Sec Auth Name SECURE 0 00000 0000001 +542202222222 10222222222222 +25 123456789 031300012 555888100001000002 GD1Y030B +262121042882201810102 938383 01 Test Payee Y10 +2711A 00340 CD Addendum B +2803121042882201810102 Y10A 0 +501031300012201810100000000000000000000000000000000000000 0 +52121042882201810101 1 Sec Orig Name Sec Auth Name SECURE 0 00000 0000001 +542202222222 10222222222222 +8511112222300000000100000000001 +70001400000020000000000020000000002 0 +900000010000001400000000200000000000002 201810100 +9900000200000038000000280000000000400000 0 `