github.com/moov-io/imagecashletter@v0.10.1/bundle.go (about) 1 // Copyright 2020 The Moov Authors 2 // Use of this source code is governed by an Apache License 3 // license that can be found in the LICENSE file. 4 5 package imagecashletter 6 7 import ( 8 "fmt" 9 ) 10 11 // BundleError is an Error that describes bundle validation issues 12 type BundleError struct { 13 BundleSequenceNumber string 14 FieldName string 15 Msg string 16 } 17 18 func (e *BundleError) Error() string { 19 return fmt.Sprintf("BundleNumber %s %s %s", e.BundleSequenceNumber, e.FieldName, e.Msg) 20 } 21 22 // Addendum Counts 23 const ( 24 CheckDetailAddendumACount = 9 25 CheckDetailAddendumBCount = 1 26 CheckDetailAddendumCCount = 99 27 ReturnDetailAddendumACount = 9 28 ReturnDetailAddendumBCount = 1 29 ReturnDetailAddendumCCount = 1 30 ReturnDetailAddendumDCount = 99 31 ) 32 33 // Errors specific to parsing a Bundle 34 var ( 35 msgBundleEntries = "must have Check Detail or Return Detail to be built" 36 msgBundleAddendum = "%v found is greater than maximum of %v" 37 msgBundleAddendumCount = "%v does not match Addenda Records" 38 msgBundleImageDetailCount = "does not match Image View Detail count of %v" 39 ) 40 41 // Bundle contains forward items (checks) 42 type Bundle struct { 43 // ID is a client defined string used as a reference to this record. 44 ID string `json:"id"` 45 // BundleHeader is a Bundle Header Record 46 BundleHeader *BundleHeader `json:"bundleHeader,omitempty"` 47 // Checks are Check Items: Check Detail Records, Check Detail Addendum Records, and Image Views 48 Checks []*CheckDetail `json:"checks,omitempty"` 49 // Returns are Return Items: Return Detail Records, Return Detail Addendum Records, and Image Views 50 Returns []*ReturnDetail `json:"returns,omitempty"` 51 // BundleControl is a Bundle Control Record 52 BundleControl *BundleControl `json:"bundleControl,omitempty"` 53 } 54 55 // NewBundle takes a BundleHeader and returns a Bundle 56 func NewBundle(bh *BundleHeader) *Bundle { 57 b := new(Bundle) 58 b.SetControl(NewBundleControl()) 59 b.SetHeader(bh) 60 return b 61 } 62 63 func (b *Bundle) setRecordType() { 64 if b == nil { 65 return 66 } 67 b.BundleHeader.setRecordType() 68 for i := range b.Checks { 69 b.Checks[i].setRecordType() 70 } 71 for i := range b.Returns { 72 b.Returns[i].setRecordType() 73 } 74 b.BundleControl.setRecordType() 75 } 76 77 // Validate performs imagecashletter validations and format rule checks and returns an error if not Validated 78 func (b *Bundle) Validate() error { 79 if (len(b.Checks) <= 0) && (len(b.Returns) <= 0) { 80 seqNumber := "" 81 if b.BundleHeader != nil { 82 seqNumber = b.BundleHeader.BundleSequenceNumber 83 } 84 return &BundleError{BundleSequenceNumber: seqNumber, FieldName: "entries", Msg: msgBundleEntries} 85 } 86 87 if len(b.Checks) > 0 { 88 if err := b.checkDetailAddendumCount(); err != nil { 89 return err 90 } 91 } else { 92 if err := b.returnDetailAddendumCount(); err != nil { 93 return err 94 } 95 } 96 return nil 97 } 98 99 // build creates a valid Bundle by building BundleControl. An error is returned if 100 // the bundle being built has invalid records. 101 func (b *Bundle) build() error { 102 // Requires a valid BundleHeader 103 if err := b.BundleHeader.Validate(); err != nil { 104 return err 105 } 106 if (len(b.Checks) <= 0) && (len(b.Returns) <= 0) { 107 seqNumber := "" 108 if b.BundleHeader != nil { 109 seqNumber = b.BundleHeader.BundleSequenceNumber 110 } 111 return &BundleError{BundleSequenceNumber: seqNumber, FieldName: "entries", Msg: msgBundleEntries} 112 } 113 114 itemCount := 0 115 bundleTotalAmount := 0 116 micrValidTotalAmount := 0 117 bundleImagesCount := 0 118 // The current Implementation doe snot support CreditItems as part of a bundle so BundleControl.CreditIndicator = 0 119 creditIndicator := 0 120 121 // Forward Items 122 for _, cd := range b.Checks { 123 124 // Validate CheckDetailAddendum* and ImageView* 125 if err := b.ValidateForwardItems(cd); err != nil { 126 return err 127 } 128 129 itemCount = itemCount + 1 130 bundleTotalAmount = bundleTotalAmount + cd.ItemAmount 131 if cd.MICRValidIndicator == 1 { 132 micrValidTotalAmount = micrValidTotalAmount + cd.ItemAmount 133 } 134 135 bundleImagesCount = bundleImagesCount + len(cd.ImageViewDetail) 136 } 137 138 // Return Items 139 for _, rd := range b.Returns { 140 141 // Validate ReturnDetailAddendum* and ImageView* 142 if err := b.ValidateReturnItems(rd); err != nil { 143 return err 144 } 145 itemCount = itemCount + 1 146 bundleTotalAmount = bundleTotalAmount + rd.ItemAmount 147 bundleImagesCount = bundleImagesCount + len(rd.ImageViewDetail) 148 } 149 150 // build a BundleControl record 151 bc := NewBundleControl() 152 bc.BundleItemsCount = itemCount 153 bc.BundleTotalAmount = bundleTotalAmount 154 bc.MICRValidTotalAmount = micrValidTotalAmount 155 bc.BundleImagesCount = bundleImagesCount 156 bc.CreditTotalIndicator = creditIndicator 157 b.BundleControl = bc 158 return nil 159 } 160 161 // SetHeader appends an BundleHeader to the Bundle 162 func (b *Bundle) SetHeader(bundleHeader *BundleHeader) { 163 b.BundleHeader = bundleHeader 164 } 165 166 // GetHeader returns the current Bundle header 167 func (b *Bundle) GetHeader() *BundleHeader { 168 return b.BundleHeader 169 } 170 171 // SetControl appends an BundleControl to the Bundle 172 func (b *Bundle) SetControl(bundleControl *BundleControl) { 173 b.BundleControl = bundleControl 174 } 175 176 // GetControl returns the current Bundle Control 177 func (b *Bundle) GetControl() *BundleControl { 178 return b.BundleControl 179 } 180 181 // AddCheckDetail appends a CheckDetail to the Bundle 182 func (b *Bundle) AddCheckDetail(cd *CheckDetail) { 183 b.Checks = append(b.Checks, cd) 184 } 185 186 // GetChecks returns a slice of check details for the Bundle 187 func (b *Bundle) GetChecks() []*CheckDetail { 188 if b == nil { 189 return nil 190 } 191 return b.Checks 192 } 193 194 // AddReturnDetail appends a ReturnDetail to the Bundle 195 func (b *Bundle) AddReturnDetail(rd *ReturnDetail) { 196 b.Returns = append(b.Returns, rd) 197 } 198 199 // GetReturns returns a slice of return details for the Bundle 200 func (b *Bundle) GetReturns() []*ReturnDetail { 201 if b == nil { 202 return nil 203 } 204 return b.Returns 205 } 206 207 // ValidateForwardItems calls Validate function for check items 208 func (b *Bundle) ValidateForwardItems(cd *CheckDetail) error { 209 // Validate items 210 for _, addendumA := range cd.CheckDetailAddendumA { 211 if err := addendumA.Validate(); err != nil { 212 return err 213 } 214 } 215 for _, addendumB := range cd.CheckDetailAddendumB { 216 if err := addendumB.Validate(); err != nil { 217 return err 218 } 219 } 220 for _, addendumC := range cd.CheckDetailAddendumC { 221 if err := addendumC.Validate(); err != nil { 222 return err 223 } 224 } 225 for _, ivDetail := range cd.ImageViewDetail { 226 if err := ivDetail.Validate(); err != nil { 227 return err 228 } 229 } 230 for _, ivData := range cd.ImageViewData { 231 if err := ivData.Validate(); err != nil { 232 return err 233 } 234 } 235 for _, ivAnalysis := range cd.ImageViewAnalysis { 236 if err := ivAnalysis.Validate(); err != nil { 237 return err 238 } 239 } 240 return nil 241 } 242 243 // ValidateReturnItems calls Validate function for return items 244 func (b *Bundle) ValidateReturnItems(rd *ReturnDetail) error { 245 // Validate items 246 for _, addendumA := range rd.ReturnDetailAddendumA { 247 if err := addendumA.Validate(); err != nil { 248 return err 249 } 250 } 251 for _, addendumB := range rd.ReturnDetailAddendumB { 252 if err := addendumB.Validate(); err != nil { 253 return err 254 } 255 } 256 for _, addendumC := range rd.ReturnDetailAddendumC { 257 if err := addendumC.Validate(); err != nil { 258 return err 259 } 260 } 261 for _, addendumD := range rd.ReturnDetailAddendumD { 262 if err := addendumD.Validate(); err != nil { 263 return err 264 } 265 } 266 for _, ivDetail := range rd.ImageViewDetail { 267 if err := ivDetail.Validate(); err != nil { 268 return err 269 } 270 } 271 for _, ivData := range rd.ImageViewDetail { 272 if err := ivData.Validate(); err != nil { 273 return err 274 } 275 } 276 for _, ivAnalysis := range rd.ImageViewDetail { 277 if err := ivAnalysis.Validate(); err != nil { 278 return err 279 } 280 } 281 return nil 282 } 283 284 // checkDetailAddendumCount validates CheckDetail AddendumCount 285 func (b *Bundle) checkDetailAddendumCount() error { 286 bundleSequenceNumber := "-" 287 if b.BundleHeader != nil { 288 bundleSequenceNumber = b.BundleHeader.BundleSequenceNumber 289 } 290 291 // Check Items 292 for _, cd := range b.Checks { 293 if cd.AddendumCount != len(cd.CheckDetailAddendumA)+len(cd.CheckDetailAddendumB)+len(cd.CheckDetailAddendumC) { 294 msg := fmt.Sprintf(msgBundleAddendumCount, cd.AddendumCount) 295 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "AddendumCount", Msg: msg} 296 } 297 if len(cd.CheckDetailAddendumA) > CheckDetailAddendumACount { 298 msg := fmt.Sprintf(msgBundleAddendum, len(cd.CheckDetailAddendumA), CheckDetailAddendumACount) 299 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "CheckDetailAddendumA", Msg: msg} 300 } 301 if len(cd.CheckDetailAddendumB) > CheckDetailAddendumBCount { 302 msg := fmt.Sprintf(msgBundleAddendum, len(cd.CheckDetailAddendumB), CheckDetailAddendumBCount) 303 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "CheckDetailAddendumB", Msg: msg} 304 } 305 if len(cd.CheckDetailAddendumC) > CheckDetailAddendumCCount { 306 msg := fmt.Sprintf(msgBundleAddendum, len(cd.CheckDetailAddendumC), CheckDetailAddendumCCount) 307 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "CheckDetailAddendumC", Msg: msg} 308 } 309 310 } 311 return nil 312 } 313 314 // returnDetailAddendumCount validates ReturnDetail AddendumCount 315 func (b *Bundle) returnDetailAddendumCount() error { 316 bundleSequenceNumber := "-" 317 if b.BundleHeader != nil { 318 bundleSequenceNumber = b.BundleHeader.BundleSequenceNumber 319 } 320 321 for _, rd := range b.Returns { 322 if rd.AddendumCount != len(rd.ReturnDetailAddendumA)+len(rd.ReturnDetailAddendumB)+len(rd.ReturnDetailAddendumC)+len(rd.ReturnDetailAddendumD) { 323 msg := fmt.Sprintf(msgBundleAddendumCount, rd.AddendumCount) 324 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "AddendumCount", Msg: msg} 325 } 326 if len(rd.ReturnDetailAddendumA) > ReturnDetailAddendumACount { 327 msg := fmt.Sprintf(msgBundleAddendum, len(rd.ReturnDetailAddendumA), ReturnDetailAddendumACount) 328 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "ReturnDetailAddendumA", Msg: msg} 329 } 330 if len(rd.ReturnDetailAddendumB) > ReturnDetailAddendumBCount { 331 msg := fmt.Sprintf(msgBundleAddendum, len(rd.ReturnDetailAddendumB), ReturnDetailAddendumBCount) 332 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "ReturnDetailAddendumB", Msg: msg} 333 } 334 if len(rd.ReturnDetailAddendumC) > ReturnDetailAddendumCCount { 335 msg := fmt.Sprintf(msgBundleAddendum, len(rd.ReturnDetailAddendumC), ReturnDetailAddendumCCount) 336 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "ReturnDetailAddendumC", Msg: msg} 337 } 338 if len(rd.ReturnDetailAddendumD) > ReturnDetailAddendumDCount { 339 msg := fmt.Sprintf(msgBundleAddendum, len(rd.ReturnDetailAddendumD), ReturnDetailAddendumDCount) 340 return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "ReturnDetailAddendumD", Msg: msg} 341 } 342 } 343 return nil 344 }