github.com/moov-io/imagecashletter@v0.10.1/cashLetter.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 "errors" 9 "fmt" 10 ) 11 12 // CashLetterError is an Error that describes CashLetter validation issues 13 type CashLetterError struct { 14 CashLetterID string 15 FieldName string 16 Msg string 17 } 18 19 func (e *CashLetterError) Error() string { 20 return fmt.Sprintf("CashLetterNumber %s %s %s", e.CashLetterID, e.FieldName, e.Msg) 21 } 22 23 // Errors specific to parsing a CashLetter 24 var ( 25 msgCashLetterBundleEntries = "%v cannot have bundle entries" 26 msgCashLetterRoutingNumber = "%v cannot have a Routing Number Summary" 27 msgMandatoryRecord = "record is mandatory" 28 ) 29 30 // CashLetter contains CashLetterHeader, CashLetterControl and Bundle records. 31 type CashLetter struct { 32 // ID is a client defined string used as a reference to this record. 33 ID string `json:"id"` 34 // CashLetterHeader is a Cash Letter Header Record 35 CashLetterHeader *CashLetterHeader `json:"cashLetterHeader,omitempty"` 36 // Bundles is an array of Bundle 37 Bundles []*Bundle `json:"bundles,omitempty"` 38 // Credits is an array of Credit 39 Credits []*Credit `json:"credit,omitempty"` 40 // CreditItems is an array of CreditItem 41 CreditItems []*CreditItem `json:"creditItem,omitempty"` 42 // RoutingNumberSummary is an array of RoutingNumberSummary 43 RoutingNumberSummary []*RoutingNumberSummary `json:"routingNumberSummary,omitempty"` 44 // currentBundle is the currentBundle being parsed 45 currentBundle *Bundle 46 // RoutingNumberSummary is an imagecashletter RoutingNumberSummary 47 currentRoutingNumberSummary *RoutingNumberSummary 48 // CashLetterControl is a Cash Letter Control Record 49 CashLetterControl *CashLetterControl `json:"cashLetterControl,omitempty"` 50 } 51 52 // NewCashLetter takes a CashLetterHeader and returns a CashLetter 53 func NewCashLetter(clh *CashLetterHeader) CashLetter { 54 cl := CashLetter{} 55 cl.SetControl(NewCashLetterControl()) 56 cl.SetHeader(clh) 57 return cl 58 } 59 60 func (cl *CashLetter) setRecordType() { 61 if cl == nil { 62 return 63 } 64 65 cl.CashLetterHeader.setRecordType() 66 for i := range cl.Bundles { 67 cl.Bundles[i].setRecordType() 68 } 69 for i := range cl.CreditItems { 70 cl.CreditItems[i].setRecordType() 71 } 72 for i := range cl.RoutingNumberSummary { 73 cl.RoutingNumberSummary[i].setRecordType() 74 } 75 cl.CashLetterControl.setRecordType() 76 } 77 78 // Validate performs ImageCashLetter validations and format rule checks and returns an error if not Validated 79 func (cl *CashLetter) Validate() error { 80 if cl.CashLetterHeader == nil { 81 return errors.New("nil CashLetterHeader") 82 } 83 84 if cl.CashLetterHeader.RecordTypeIndicator == "N" { 85 if cl.GetBundles() != nil { 86 return &CashLetterError{ 87 CashLetterID: cl.CashLetterHeader.CashLetterID, 88 FieldName: "RecordTypeIndicator", 89 Msg: fmt.Sprintf(msgCashLetterBundleEntries, cl.CashLetterHeader.RecordTypeIndicator), 90 } 91 } 92 } 93 switch cl.CashLetterHeader.CollectionTypeIndicator { 94 case 95 "00", "01", "02": 96 default: 97 if cl.GetRoutingNumberSummary() != nil { 98 return &CashLetterError{ 99 CashLetterID: cl.CashLetterHeader.CashLetterID, 100 FieldName: "CollectionTypeIndicator", 101 Msg: fmt.Sprintf(msgCashLetterRoutingNumber, cl.CashLetterHeader.CollectionTypeIndicator), 102 } 103 } 104 } 105 106 if cl.CashLetterControl == nil { 107 return &CashLetterError{ 108 CashLetterID: cl.CashLetterHeader.CashLetterID, 109 FieldName: "CashLetterControl", 110 Msg: msgMandatoryRecord, 111 } 112 } 113 114 if err := cl.CashLetterControl.Validate(); err != nil { 115 return err 116 } 117 118 return nil 119 } 120 121 // build a valid CashLetter by building a CashLetterControl. An error is returned if 122 // the CashLetter being built has invalid records. 123 func (cl *CashLetter) build() error { 124 125 // Requires a valid CashLetterHeader 126 if err := cl.CashLetterHeader.Validate(); err != nil { 127 return err 128 } 129 130 // CashLetterControl Counts 131 cashLetterBundleCount := len(cl.Bundles) 132 cashLetterItemsCount := 0 133 cashLetterTotalAmount := 0 134 cashLetterImagesCount := 0 135 136 // Sequence Numbers 137 bundleSequenceNumber := 1 138 // creditIndicator 139 creditIndicator := 0 140 141 if len(cl.GetCreditItems()) > 0 { 142 cashLetterItemsCount = cashLetterItemsCount + len(cl.GetCreditItems()) 143 creditIndicator = 1 144 } 145 // Bundles 146 for _, b := range cl.Bundles { 147 148 // Set Bundle Sequence Numbers 149 b.BundleHeader.SetBundleSequenceNumber(bundleSequenceNumber) 150 151 // Sequence Number 152 cdSequenceNumber := 1 153 154 // Check Items 155 for _, cd := range b.Checks { 156 157 if cd.EceInstitutionItemSequenceNumber != "" { 158 i := cd.parseNumField(cd.EceInstitutionItemSequenceNumber) 159 cdSequenceNumber = i 160 } 161 162 // Record Numbers 163 cdAddendumARecordNumber := 1 164 cdAddendumCRecordNumber := 1 165 166 // Set CheckDetail Sequence Numbers 167 cd.SetEceInstitutionItemSequenceNumber(cdSequenceNumber) 168 169 // Set Addenda SequenceNumber and RecordNumber 170 for i := range cd.CheckDetailAddendumA { 171 cd.CheckDetailAddendumA[i].SetBOFDItemSequenceNumber(cdSequenceNumber) 172 cd.CheckDetailAddendumA[i].RecordNumber = cdAddendumARecordNumber 173 cdAddendumARecordNumber++ 174 if cdAddendumARecordNumber > 9 { 175 cdAddendumARecordNumber = 1 176 } 177 } 178 for x := range cd.CheckDetailAddendumC { 179 cd.CheckDetailAddendumC[x].SetEndorsingBankItemSequenceNumber(cdSequenceNumber) 180 cd.CheckDetailAddendumC[x].RecordNumber = cdAddendumARecordNumber 181 cdAddendumCRecordNumber++ 182 if cdAddendumCRecordNumber > 99 { 183 cdAddendumCRecordNumber = 1 184 } 185 } 186 cdSequenceNumber++ 187 188 cashLetterItemsCount = cashLetterItemsCount + 1 189 cashLetterTotalAmount = cashLetterTotalAmount + cd.ItemAmount 190 cashLetterImagesCount = cashLetterImagesCount + len(cd.ImageViewDetail) 191 } 192 193 // Returns Items 194 for _, rd := range b.Returns { 195 196 // Sequence Number 197 var rdSequenceNumber int 198 rdSequenceNumber++ 199 200 // Record Numbers 201 rdAddendumARecordNumber := 1 202 rdAddendumDRecordNumber := 1 203 204 // Set ReturnDetail Sequence Numbers 205 rd.SetEceInstitutionItemSequenceNumber(rdSequenceNumber) 206 207 // Set Addenda SequenceNumber and RecordNumber 208 for i := range rd.ReturnDetailAddendumA { 209 rd.ReturnDetailAddendumA[i].SetBOFDItemSequenceNumber(rdSequenceNumber) 210 rd.ReturnDetailAddendumA[i].RecordNumber = rdAddendumARecordNumber 211 rdAddendumARecordNumber++ 212 if rdAddendumARecordNumber > 9 { 213 rdAddendumARecordNumber = 1 214 } 215 } 216 217 for x := range rd.ReturnDetailAddendumD { 218 rd.ReturnDetailAddendumD[x].SetEndorsingBankItemSequenceNumber(rdSequenceNumber) 219 rd.ReturnDetailAddendumD[x].RecordNumber = rdAddendumDRecordNumber 220 rdAddendumDRecordNumber++ 221 if rdAddendumDRecordNumber > 99 { 222 rdAddendumDRecordNumber = 1 223 } 224 } 225 226 cashLetterItemsCount = cashLetterItemsCount + 1 227 cashLetterTotalAmount = cashLetterTotalAmount + rd.ItemAmount 228 cashLetterImagesCount = cashLetterImagesCount + len(rd.ImageViewDetail) 229 } 230 // Validate Bundle 231 if err := b.Validate(); err != nil { 232 return err 233 } 234 // Build Bundle 235 if err := b.build(); err != nil { 236 return err 237 } 238 239 bundleSequenceNumber++ 240 } 241 242 // build a CashLetterControl record 243 clc := NewCashLetterControl() 244 clc.CashLetterBundleCount = cashLetterBundleCount 245 clc.CashLetterItemsCount = cashLetterItemsCount 246 clc.CashLetterTotalAmount = cashLetterTotalAmount 247 clc.CashLetterImagesCount = cashLetterImagesCount 248 if cl.CashLetterControl.ECEInstitutionName != "" { 249 clc.ECEInstitutionName = cl.CashLetterControl.ECEInstitutionName 250 } else { 251 clc.ECEInstitutionName = cl.GetHeader().ECEInstitutionRoutingNumber 252 } 253 clc.CreditTotalIndicator = creditIndicator 254 cl.CashLetterControl = clc 255 return nil 256 } 257 258 // Create creates a CashLetter of Bundles containing CheckDetail or ReturnDetail 259 func (cl *CashLetter) Create() error { 260 if err := cl.build(); err != nil { 261 return err 262 } 263 return cl.Validate() 264 } 265 266 // SetHeader appends a CashLetterHeader to the CashLetter 267 func (cl *CashLetter) SetHeader(cashLetterHeader *CashLetterHeader) { 268 cl.CashLetterHeader = cashLetterHeader 269 } 270 271 // GetHeader returns the current CashLetter header 272 func (cl *CashLetter) GetHeader() *CashLetterHeader { 273 return cl.CashLetterHeader 274 } 275 276 // SetControl appends a CashLetterControl to the CashLetter 277 func (cl *CashLetter) SetControl(cashLetterControl *CashLetterControl) { 278 cl.CashLetterControl = cashLetterControl 279 } 280 281 // GetControl returns the current CashLetter Control 282 func (cl *CashLetter) GetControl() *CashLetterControl { 283 return cl.CashLetterControl 284 } 285 286 // AddBundle appends a Bundle to the CashLetter 287 func (cl *CashLetter) AddBundle(bundle *Bundle) []*Bundle { 288 cl.Bundles = append(cl.Bundles, bundle) 289 return cl.Bundles 290 } 291 292 // GetBundles returns a slice of Bundle for the CashLetter 293 func (cl *CashLetter) GetBundles() []*Bundle { 294 if cl == nil { 295 return nil 296 } 297 return cl.Bundles 298 } 299 300 // AddRoutingNumberSummary appends a RoutingNumberSummary to the CashLetter 301 func (cl *CashLetter) AddRoutingNumberSummary(rns *RoutingNumberSummary) []*RoutingNumberSummary { 302 cl.RoutingNumberSummary = append(cl.RoutingNumberSummary, rns) 303 return cl.RoutingNumberSummary 304 } 305 306 // GetRoutingNumberSummary returns a slice of RoutingNumberSummary for the CashLetter 307 func (cl *CashLetter) GetRoutingNumberSummary() []*RoutingNumberSummary { 308 if cl == nil { 309 return nil 310 } 311 return cl.RoutingNumberSummary 312 } 313 314 // AddCredit appends a CreditItem to the CashLetter 315 func (cl *CashLetter) AddCredit(cr *Credit) []*Credit { 316 cl.Credits = append(cl.Credits, cr) 317 return cl.Credits 318 } 319 320 // AddCreditItem appends a CreditItem to the CashLetter 321 func (cl *CashLetter) AddCreditItem(ci *CreditItem) []*CreditItem { 322 cl.CreditItems = append(cl.CreditItems, ci) 323 return cl.CreditItems 324 } 325 326 // GetCredits returns a slice of Credit for the CashLetter 327 func (cl *CashLetter) GetCredits() []*Credit { 328 if cl == nil { 329 return nil 330 } 331 return cl.Credits 332 } 333 334 // GetCreditItems returns a slice of CreditItem for the CashLetter 335 func (cl *CashLetter) GetCreditItems() []*CreditItem { 336 if cl == nil { 337 return nil 338 } 339 return cl.CreditItems 340 }