github.com/moov-io/imagecashletter@v0.10.1/returnDetailAddendumD.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 "encoding/json" 9 "fmt" 10 "strconv" 11 "strings" 12 "time" 13 "unicode/utf8" 14 ) 15 16 // Errors specific to a ReturnDetailAddendumD Record 17 18 // ReturnDetailAddendumD Record 19 type ReturnDetailAddendumD struct { 20 // ID is a client defined string used as a reference to this record. 21 ID string `json:"id"` 22 // RecordType defines the type of record. 23 recordType string 24 // RecordNumber is a number representing the order in which each ReturnDetailAddendumD was created. 25 // ReturnDetailAddendumD shall be in sequential order starting with 1. Maximum 99, 26 RecordNumber int `json:"recordNumber"` 27 // RoutingNumber (Endorsing Bank Routing Number) is valid routing and transit number indicating the bank that 28 // endorsed the check. 29 // Format: TTTTAAAAC, where: 30 // TTTT Federal Reserve Prefix 31 // AAAA ABA Institution Identifier 32 // C Check Digit 33 // For a number that identifies a non-financial institution: NNNNNNNNN 34 EndorsingBankRoutingNumber string `json:"endorsingBankRoutingNumber"` 35 // BOFDEndorsementBusinessDate is the business date the check was endorsed. 36 // Format: YYYYMMDD, where: YYYY year, MM month, DD day 37 // Values: 38 // YYYY 1993 through 9999 39 // MM 01 through 12 40 // DD 01 through 31 41 BOFDEndorsementBusinessDate time.Time `json:"bofdEndorsementBusinessDate"` 42 // EndorsingItemSequenceNumber is a number that identifies the item at the endorsing bank. 43 // This field is optional in earlier version of the specs. 44 EndorsingBankItemSequenceNumber string `json:"endorsingBankItemSequenceNumber"` 45 // TruncationIndicator identifies if the institution truncated the original check item. 46 // Values: Y: Yes this institution truncated this original check item and this is first endorsement 47 // for the institution. 48 // N: No this institution did not truncate the original check or, this is not the first endorsement for the 49 // institution or, this item is an IRD not an original check item (EPC equals 4). 50 TruncationIndicator string `json:"truncationIndicator"` 51 // EndorsingConversionIndicator is a code that indicates the conversion within the processing institution among 52 // original paper check, image and IRD. The indicator is specific to the action of institution identified in the 53 // Endorsing Bank RoutingNumber. 54 // Values: 55 // 0: Did not convert physical document 56 // 1: Original paper converted to IRD 57 // 2: Original paper converted to image 58 // 3: IRD converted to another IRD 59 // 4: IRD converted to image of IRD 60 // 5: Image converted to an IRD 61 // 6: Image converted to another image (e.g., transcoded) 62 // 7: Did not convert image (e.g., same as source) 63 // 8: Undetermined 64 EndorsingBankConversionIndicator string `json:"endorsingBankConversionIndicator"` 65 // EndorsingCorrectionIndicator identifies whether and how the MICR line of this item was repaired by the 66 // creator of this ReturnDetailAddendumD Record for fields other than Payor Bank Routing Number and Amount. 67 // Values: 68 // 0: No Repair 69 // 1: Repaired (form of repair unknown) 70 // 2: Repaired without Operator intervention 71 // 3: Repaired with Operator intervention 72 // 4: Undetermined if repair has been done or no 73 EndorsingBankCorrectionIndicator int `json:"endorsingBankCorrectionIndicator"` 74 // ReturnReason is a code that indicates the reason for non-payment. 75 ReturnReason string `json:"returnReason"` 76 // UserField identifies a field used at the discretion of users of the standard. 77 UserField string `json:"userField"` 78 // EndorsingBankIdentifier 79 // Values: 80 // 0: Depository Bank (BOFD) - this value is used when the ReturnDetailAddendumD Record reflects the Return 81 // Processing Bank in lieu of BOFD. 82 // 1: Other Collecting Bank 83 // 2: Other Returning Bank 84 // 3: Payor Bank 85 EndorsingBankIdentifier int `json:"endorsingBankIdentifier"` 86 // reserved is a field reserved for future use. Reserved should be blank. 87 reserved string 88 // validator is composed for image cash letter data validation 89 validator 90 // converters is composed for image cash letter to golang Converters 91 converters 92 } 93 94 // NewReturnDetailAddendumD returns a new ReturnDetailAddendumD with default values for non exported fields 95 func NewReturnDetailAddendumD() ReturnDetailAddendumD { 96 rdAddendumD := ReturnDetailAddendumD{} 97 rdAddendumD.setRecordType() 98 return rdAddendumD 99 } 100 101 func (rdAddendumD *ReturnDetailAddendumD) setRecordType() { 102 if rdAddendumD == nil { 103 return 104 } 105 rdAddendumD.recordType = "35" 106 } 107 108 // Parse takes the input record string and parses the ReturnDetailAddendumD values 109 func (rdAddendumD *ReturnDetailAddendumD) Parse(record string) { 110 if utf8.RuneCountInString(record) < 60 { 111 return // line too short 112 } 113 114 // Character position 1-2, Always "35" 115 rdAddendumD.setRecordType() 116 // 03-04 117 rdAddendumD.RecordNumber = rdAddendumD.parseNumField(record[2:4]) 118 // 05-13 119 rdAddendumD.EndorsingBankRoutingNumber = rdAddendumD.parseStringField(record[4:13]) 120 // 14-21 121 rdAddendumD.BOFDEndorsementBusinessDate = rdAddendumD.parseYYYYMMDDDate(record[13:21]) 122 // 22-36 123 rdAddendumD.EndorsingBankItemSequenceNumber = rdAddendumD.parseStringField(record[21:36]) 124 // 37-37 125 rdAddendumD.TruncationIndicator = rdAddendumD.parseStringField(record[36:37]) 126 // 38-38 127 rdAddendumD.EndorsingBankConversionIndicator = rdAddendumD.parseStringField(record[37:38]) 128 // 39-39 129 rdAddendumD.EndorsingBankCorrectionIndicator = rdAddendumD.parseNumField(record[38:39]) 130 // 40-40 131 rdAddendumD.ReturnReason = rdAddendumD.parseStringField(record[39:40]) 132 // 41-59 133 rdAddendumD.UserField = rdAddendumD.parseStringField(record[40:59]) 134 // 60-60 135 rdAddendumD.EndorsingBankIdentifier = rdAddendumD.parseNumField(record[59:60]) 136 // 61-80 137 rdAddendumD.reserved = " " 138 } 139 140 func (rdAddendumD *ReturnDetailAddendumD) UnmarshalJSON(data []byte) error { 141 type Alias ReturnDetailAddendumD 142 aux := struct { 143 *Alias 144 }{ 145 (*Alias)(rdAddendumD), 146 } 147 if err := json.Unmarshal(data, &aux); err != nil { 148 return err 149 } 150 rdAddendumD.setRecordType() 151 return nil 152 } 153 154 // String writes the ReturnDetailAddendumD struct to a string. 155 func (rdAddendumD *ReturnDetailAddendumD) String() string { 156 if rdAddendumD == nil { 157 return "" 158 } 159 160 var buf strings.Builder 161 buf.Grow(80) 162 buf.WriteString(rdAddendumD.recordType) 163 buf.WriteString(rdAddendumD.RecordNumberField()) 164 buf.WriteString(rdAddendumD.EndorsingBankRoutingNumberField()) 165 buf.WriteString(rdAddendumD.BOFDEndorsementBusinessDateField()) 166 buf.WriteString(rdAddendumD.EndorsingBankItemSequenceNumberField()) 167 buf.WriteString(rdAddendumD.TruncationIndicatorField()) 168 buf.WriteString(rdAddendumD.EndorsingBankConversionIndicatorField()) 169 buf.WriteString(rdAddendumD.EndorsingBankCorrectionIndicatorField()) 170 buf.WriteString(rdAddendumD.ReturnReasonField()) 171 buf.WriteString(rdAddendumD.UserFieldField()) 172 buf.WriteString(rdAddendumD.EndorsingBankIdentifierField()) 173 buf.WriteString(rdAddendumD.reservedField()) 174 return buf.String() 175 } 176 177 // Validate performs image cash letter format rule checks on the record and returns an error if not Validated 178 // The first error encountered is returned and stops the parsing. 179 func (rdAddendumD *ReturnDetailAddendumD) Validate() error { 180 if err := rdAddendumD.fieldInclusion(); err != nil { 181 return err 182 } 183 if rdAddendumD.recordType != "35" { 184 msg := fmt.Sprintf(msgRecordType, 35) 185 return &FieldError{FieldName: "recordType", Value: rdAddendumD.recordType, Msg: msg} 186 } 187 if err := rdAddendumD.isNumeric(rdAddendumD.EndorsingBankRoutingNumber); err != nil { 188 return &FieldError{FieldName: "EndorsingBankRoutingNumber", 189 Value: rdAddendumD.EndorsingBankRoutingNumber, Msg: err.Error()} 190 } 191 if err := rdAddendumD.isNumeric(rdAddendumD.EndorsingBankItemSequenceNumber); err != nil { 192 return &FieldError{FieldName: "EndorsingBankItemSequenceNumber", 193 Value: rdAddendumD.EndorsingBankItemSequenceNumber, Msg: msgNumeric} 194 } 195 // Mandatory 196 if err := rdAddendumD.isTruncationIndicator(rdAddendumD.TruncationIndicator); err != nil { 197 return &FieldError{FieldName: "TruncationIndicator", 198 Value: rdAddendumD.TruncationIndicator, Msg: err.Error()} 199 } 200 // Conditional 201 if rdAddendumD.EndorsingBankConversionIndicator != "" { 202 if err := rdAddendumD.isConversionIndicator(rdAddendumD.EndorsingBankConversionIndicator); err != nil { 203 return &FieldError{FieldName: "EndorsingBankConversionIndicator", 204 Value: rdAddendumD.EndorsingBankConversionIndicator, Msg: err.Error()} 205 } 206 } 207 // Conditional 208 if rdAddendumD.EndorsingBankCorrectionIndicatorField() != "" { 209 if err := rdAddendumD.isCorrectionIndicator(rdAddendumD.EndorsingBankCorrectionIndicator); err != nil { 210 return &FieldError{FieldName: "EndorsingBankCorrectionIndicator", 211 Value: rdAddendumD.EndorsingBankCorrectionIndicatorField(), Msg: err.Error()} 212 } 213 } 214 if err := rdAddendumD.isAlphanumeric(rdAddendumD.ReturnReason); err != nil { 215 return &FieldError{FieldName: "ReturnReason", 216 Value: rdAddendumD.ReturnReason, Msg: err.Error()} 217 } 218 if err := rdAddendumD.isAlphanumericSpecial(rdAddendumD.UserField); err != nil { 219 return &FieldError{FieldName: "UserField", Value: rdAddendumD.UserField, Msg: err.Error()} 220 } 221 if err := rdAddendumD.isEndorsingBankIdentifier(rdAddendumD.EndorsingBankIdentifier); err != nil { 222 return &FieldError{FieldName: "EndorsingBankIdentifier", 223 Value: rdAddendumD.EndorsingBankIdentifierField(), Msg: err.Error()} 224 } 225 return nil 226 } 227 228 // fieldInclusion validate mandatory fields are not default values. If fields are 229 // invalid the Electronic Exchange will be returned. 230 func (rdAddendumD *ReturnDetailAddendumD) fieldInclusion() error { 231 if rdAddendumD.recordType == "" { 232 return &FieldError{FieldName: "recordType", 233 Value: rdAddendumD.recordType, 234 Msg: msgFieldInclusion + ", did you use ReturnDetailAddendumD()?"} 235 } 236 if rdAddendumD.RecordNumber == 0 { 237 return &FieldError{FieldName: "RecordNumber", 238 Value: rdAddendumD.RecordNumberField(), 239 Msg: msgFieldInclusion + ", did you use ReturnDetailAddendumD()?"} 240 } 241 if rdAddendumD.EndorsingBankRoutingNumber == "" { 242 return &FieldError{FieldName: "EndorsingBankRoutingNumber", 243 Value: rdAddendumD.EndorsingBankRoutingNumber, 244 Msg: msgFieldInclusion + ", did you use ReturnDetailAddendumD()?"} 245 } 246 if rdAddendumD.EndorsingBankRoutingNumberField() == "000000000" { 247 return &FieldError{FieldName: "EndorsingBankRoutingNumber", 248 Value: rdAddendumD.EndorsingBankRoutingNumber, 249 Msg: msgFieldInclusion + ", did you use ReturnDetailAddendumD()?"} 250 } 251 if rdAddendumD.BOFDEndorsementBusinessDate.IsZero() { 252 return &FieldError{FieldName: "BOFDEndorsementBusinessDate", 253 Value: rdAddendumD.BOFDEndorsementBusinessDate.String(), 254 Msg: msgFieldInclusion + ", did you use ReturnDetailAddendumD()?"} 255 } 256 if rdAddendumD.TruncationIndicator == "" { 257 return &FieldError{FieldName: "TruncationIndicator", 258 Value: rdAddendumD.TruncationIndicator, 259 Msg: msgFieldInclusion + ", did you use ReturnDetailAddendumD()?"} 260 } 261 return nil 262 } 263 264 // RecordNumberField gets a string of the RecordNumber field 265 func (rdAddendumD *ReturnDetailAddendumD) RecordNumberField() string { 266 return rdAddendumD.numericField(rdAddendumD.RecordNumber, 2) 267 } 268 269 // EndorsingBankRoutingNumberField gets a string of the EndorsingBankRoutingNumber field 270 func (rdAddendumD *ReturnDetailAddendumD) EndorsingBankRoutingNumberField() string { 271 return rdAddendumD.stringField(rdAddendumD.EndorsingBankRoutingNumber, 9) 272 } 273 274 // BOFDEndorsementBusinessDateField gets the BOFDEndorsementBusinessDate in YYYYMMDD format 275 func (rdAddendumD *ReturnDetailAddendumD) BOFDEndorsementBusinessDateField() string { 276 return rdAddendumD.formatYYYYMMDDDate(rdAddendumD.BOFDEndorsementBusinessDate) 277 } 278 279 // EndorsingBankItemSequenceNumberField gets the EndorsingBankItemSequenceNumber field 280 func (rdAddendumD *ReturnDetailAddendumD) EndorsingBankItemSequenceNumberField() string { 281 return rdAddendumD.alphaField(rdAddendumD.EndorsingBankItemSequenceNumber, 15) 282 } 283 284 // TruncationIndicatorField gets the TruncationIndicator field 285 func (rdAddendumD *ReturnDetailAddendumD) TruncationIndicatorField() string { 286 return rdAddendumD.alphaField(rdAddendumD.TruncationIndicator, 1) 287 } 288 289 // EndorsingBankConversionIndicatorField gets the EndorsingBankConversionIndicator field 290 func (rdAddendumD *ReturnDetailAddendumD) EndorsingBankConversionIndicatorField() string { 291 return rdAddendumD.alphaField(rdAddendumD.EndorsingBankConversionIndicator, 1) 292 } 293 294 // EndorsingBankCorrectionIndicatorField gets a string of the EndorsingBankCorrectionIndicator field 295 func (rdAddendumD *ReturnDetailAddendumD) EndorsingBankCorrectionIndicatorField() string { 296 return rdAddendumD.numericField(rdAddendumD.EndorsingBankCorrectionIndicator, 1) 297 } 298 299 // ReturnReasonField gets the ReturnReason field 300 func (rdAddendumD *ReturnDetailAddendumD) ReturnReasonField() string { 301 return rdAddendumD.alphaField(rdAddendumD.ReturnReason, 1) 302 } 303 304 // UserFieldField gets the UserField field 305 func (rdAddendumD *ReturnDetailAddendumD) UserFieldField() string { 306 return rdAddendumD.alphaField(rdAddendumD.UserField, 19) 307 } 308 309 // EndorsingBankIdentifierField gets the EndorsingBankIdentifier field 310 func (rdAddendumD *ReturnDetailAddendumD) EndorsingBankIdentifierField() string { 311 return rdAddendumD.numericField(rdAddendumD.EndorsingBankIdentifier, 1) 312 } 313 314 // reservedField gets reserved - blank space 315 func (rdAddendumD *ReturnDetailAddendumD) reservedField() string { 316 return rdAddendumD.alphaField(rdAddendumD.reserved, 20) 317 } 318 319 // SetEndorsingBankItemSequenceNumber sets EndorsingBankItemSequenceNumber 320 func (rdAddendumD *ReturnDetailAddendumD) SetEndorsingBankItemSequenceNumber(seq int) string { 321 itemSequence := strconv.Itoa(seq) 322 rdAddendumD.EndorsingBankItemSequenceNumber = itemSequence 323 return rdAddendumD.EndorsingBankItemSequenceNumber 324 }