github.com/moov-io/imagecashletter@v0.10.1/creditItem.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 "strings" 11 "unicode/utf8" 12 ) 13 14 // Errors specific to a CreditItem Record 15 16 // Current Implementation: CreditItem(s) Precede CheckDetail(s) - CreditItem(s) outside the leading Bundle 17 // and Within the First Cash Letter. Please adjust reader and writer for your specific clearing arrangement 18 // implementation or contact MOOV for your particular implementation. 19 // 20 // FileHeader 21 // CashLetterHeader Record 22 // CreditItem 23 // BundleHeader Record 24 // 1st CheckDetail 25 // 2nd CheckDetail 26 // N* CheckDetail 27 // Last CheckDetail 28 // BundleControl 29 // BundleHeader 30 // 1st CheckDetail 31 // 2nd CheckDetail 32 // N* CheckDetail 33 // Last CheckDetail 34 // BundleControl 35 // CashLetterControl 36 // FileControl 37 38 // CreditItem Record 39 type CreditItem struct { 40 // ID is a client defined string used as a reference to this record. 41 ID string `json:"id"` 42 // RecordType defines the type of record. 43 recordType string 44 // AuxiliaryOnUs identifies a code used on commercial checks at the discretion of the payor bank. 45 AuxiliaryOnUs string `json:"auxiliaryOnUs"` 46 // ExternalProcessingCode identifies a code used for special purposes as authorized by the Accredited 47 // Standards Committee X9. Also known as Position 44. 48 ExternalProcessingCode string `json:"externalProcessingCode"` 49 // PostingBankRoutingNumber is a routing number assigned by the posting bank to identify this credit. 50 // Format: TTTTAAAA, where: 51 // TTTT: Federal Reserve Prefix 52 // AAAA: ABA Institution Identifier 53 PostingBankRoutingNumber string `json:"postingBankRoutingNumber"` 54 // OnUs identifies data specified by the payor bank. On-Us data usually consists of the payor’s account number, 55 // a serial number or transaction code, or both. 56 OnUs string `json:"onUs"` 57 // Amount identifies the amount of the check. All amounts fields have two implied decimal points. 58 // e.g., 100000 is $1,000.00 59 ItemAmount int `json:"itemAmount"` 60 // CreditItemSequenceNumber identifies a number assigned by the institution that creates the CreditItem 61 CreditItemSequenceNumber string `json:"creditItemSequenceNumber"` 62 // DocumentationTypeIndicator identifies a code that indicates the type of documentation that supports the check 63 // record. 64 // This field is superseded by the Cash Letter Documentation Type Indicator in the Cash Letter Header 65 // Record (Type 10) for all Defined Values except ‘Z’ Not Same Type. In the case of Defined Value of ‘Z’, the 66 // Documentation Type Indicator in this record takes precedent. 67 // 68 // Shall be present when Cash Letter Documentation Type Indicator (Field 9) in the Cash Letter Header Record 69 // (Type 10) is Defined Value of ‘Z’. 70 // 71 // Values: 72 // A: No image provided, paper provided separately 73 // B: No image provided, paper provided separately, image upon request 74 // C: Image provided separately, no paper provided 75 // D: Image provided separately, no paper provided, image upon request 76 // E: Image and paper provided separately 77 // F: Image and paper provided separately, image upon request 78 // G: Image included, no paper provided 79 // H: Image included, no paper provided, image upon request 80 // I: Image included, paper provided separately 81 // J: Image included, paper provided separately, image upon request 82 // K: No image provided, no paper provided 83 // L: No image provided, no paper provided, image upon request 84 DocumentationTypeIndicator string `json:"documentationTypeIndicator"` 85 // AccountTypeCode is a code that indicates the type of account to which this CreditItem is associated. 86 // Values: 87 // 0: Unknown 88 // 1: DDA account 89 // 2: General Ledger account 90 // 3: Savings account 91 // 4: Money Market account 92 // 5: Other account 93 AccountTypeCode string `json:"accountTypeCode"` 94 // SourceWorkCode is a code used to identify the source of the work associated with this CreditItem. 95 // Values: 96 // 00: Unknown 97 // 01: Internal–ATM 98 // 02: Internal–Branch 99 // 03: Internal–Other 100 // 04: External–Bank to Bank (Correspondent) 101 // 05: External–Business to Bank (Customer) 102 // 06: External–Business to Bank Remote Capture 103 // 07: External–Processor to Bank 104 // 08: External–Bank to Processor 105 // 09: Lockbox 106 // 10: International–Internal 107 // 11: International–External 108 // 21–50: User Defined 109 SourceWorkCode string `json:"sourceWorkCode"` 110 // UserField is a field used at the discretion of users of the standard. 111 UserField string `json:"userField"` 112 // reserved is a field reserved for future use. Reserved should be blank. 113 reserved string 114 validator 115 // converters is composed for imagecashletter to golang Converters 116 converters 117 } 118 119 // NewCreditItem returns a new CreditItem with default values for non exported fields 120 func NewCreditItem() *CreditItem { 121 ci := &CreditItem{} 122 ci.setRecordType() 123 return ci 124 } 125 126 func (ci *CreditItem) setRecordType() { 127 if ci == nil { 128 return 129 } 130 ci.recordType = "62" 131 } 132 133 // Parse takes the input record string and parses the CreditItem values 134 func (ci *CreditItem) Parse(record string) { 135 if utf8.RuneCountInString(record) < 96 { 136 return // line is too short 137 } 138 // Character position 1-2, Always "62" 139 ci.setRecordType() 140 // 03-17 141 ci.AuxiliaryOnUs = ci.parseStringField(record[2:17]) 142 // 18-18 143 ci.ExternalProcessingCode = ci.parseStringField(record[17:18]) 144 // 19-27 145 ci.PostingBankRoutingNumber = ci.parseStringField(record[18:27]) 146 // 28-47 147 ci.OnUs = ci.parseStringField(record[27:47]) 148 // 48-61 149 ci.ItemAmount = ci.parseNumField(record[47:61]) 150 // 62-76 151 ci.CreditItemSequenceNumber = ci.parseStringField(record[61:76]) 152 // 77-77 153 ci.DocumentationTypeIndicator = ci.parseStringField(record[76:77]) 154 // 78-78 155 ci.AccountTypeCode = ci.parseStringField(record[77:78]) 156 // 79-80 157 ci.SourceWorkCode = ci.parseStringField(record[78:80]) 158 // 81-96 159 ci.UserField = ci.parseStringField(record[80:96]) 160 // 97-100 161 ci.reserved = " " 162 } 163 164 func (ci *CreditItem) UnmarshalJSON(data []byte) error { 165 type Alias CreditItem 166 aux := struct { 167 *Alias 168 }{ 169 (*Alias)(ci), 170 } 171 if err := json.Unmarshal(data, &aux); err != nil { 172 return err 173 } 174 ci.setRecordType() 175 return nil 176 } 177 178 // String writes the CreditItem struct to a variable length string. 179 func (ci *CreditItem) String() string { 180 if ci == nil { 181 return "" 182 } 183 184 var buf strings.Builder 185 buf.Grow(100) 186 buf.WriteString(ci.recordType) 187 buf.WriteString(ci.AuxiliaryOnUsField()) 188 buf.WriteString(ci.ExternalProcessingCodeField()) 189 buf.WriteString(ci.PostingBankRoutingNumberField()) 190 buf.WriteString(ci.OnUsField()) 191 buf.WriteString(ci.ItemAmountField()) 192 buf.WriteString(ci.CreditItemSequenceNumberField()) 193 buf.WriteString(ci.DocumentationTypeIndicatorField()) 194 buf.WriteString(ci.AccountTypeCodeField()) 195 buf.WriteString(ci.SourceWorkCodeField()) 196 buf.WriteString(ci.UserFieldField()) 197 buf.WriteString(ci.reservedField()) 198 return buf.String() 199 } 200 201 // Validate performs imagecashletter format rule checks on the record and returns an error if not Validated 202 // The first error encountered is returned and stops the parsing. 203 func (ci *CreditItem) Validate() error { 204 if err := ci.fieldInclusion(); err != nil { 205 return err 206 } 207 if ci.recordType != "62" { 208 msg := fmt.Sprintf(msgRecordType, 62) 209 return &FieldError{FieldName: "recordType", Value: ci.recordType, Msg: msg} 210 } 211 if ci.DocumentationTypeIndicator != "" { 212 // Z is valid for CashLetter DocumentationTypeIndicator only 213 if ci.DocumentationTypeIndicator == "Z" { 214 msg := fmt.Sprint(msgDocumentationTypeIndicator) 215 return &FieldError{FieldName: "DocumentationTypeIndicator", Value: ci.DocumentationTypeIndicator, Msg: msg} 216 } 217 // M is not valid for CreditItem DocumentationTypeIndicator 218 if ci.DocumentationTypeIndicator == "M" { 219 msg := fmt.Sprint(msgDocumentationTypeIndicator) 220 return &FieldError{FieldName: "DocumentationTypeIndicator", Value: ci.DocumentationTypeIndicator, Msg: msg} 221 } 222 if err := ci.isDocumentationTypeIndicator(ci.DocumentationTypeIndicator); err != nil { 223 return &FieldError{FieldName: "DocumentationTypeIndicator", Value: ci.DocumentationTypeIndicator, Msg: err.Error()} 224 } 225 } 226 if err := ci.isAccountTypeCode(ci.AccountTypeCode); err != nil { 227 return &FieldError{FieldName: "AccountTypeCode", Value: ci.AccountTypeCode, Msg: err.Error()} 228 } 229 if err := ci.isSourceWorkCode(ci.SourceWorkCode); err != nil { 230 return &FieldError{FieldName: "SourceWorkCode", Value: ci.SourceWorkCode, Msg: err.Error()} 231 } 232 if err := ci.isAlphanumericSpecial(ci.UserField); err != nil { 233 return &FieldError{FieldName: "UserField", Value: ci.UserField, Msg: err.Error()} 234 } 235 return nil 236 } 237 238 // fieldInclusion validate mandatory fields are not default values. If fields are 239 // invalid the Electronic Exchange will be returned. 240 func (ci *CreditItem) fieldInclusion() error { 241 if ci.recordType == "" { 242 return &FieldError{FieldName: "recordType", 243 Value: ci.recordType, 244 Msg: msgFieldInclusion + ", did you use CreditItem()?"} 245 } 246 if ci.PostingBankRoutingNumber == "" { 247 return &FieldError{FieldName: "PostingBankRoutingNumber", 248 Value: ci.PostingBankRoutingNumber, 249 Msg: msgFieldInclusion + ", did you use CreditItem()?"} 250 } 251 if ci.PostingBankRoutingNumberField() == "000000000" { 252 return &FieldError{FieldName: "PostingBankRoutingNumber", 253 Value: ci.PostingBankRoutingNumber, 254 Msg: msgFieldInclusion + ", did you use CreditItem()?"} 255 } 256 if ci.CreditItemSequenceNumberField() == " " { 257 return &FieldError{FieldName: "CreditItemSequenceNumber", 258 Value: ci.CreditItemSequenceNumber, 259 Msg: msgFieldInclusion + ", did you use CreditItem()?"} 260 } 261 return nil 262 } 263 264 // AuxiliaryOnUsField gets the AuxiliaryOnUs field 265 func (ci *CreditItem) AuxiliaryOnUsField() string { 266 return ci.nbsmField(ci.AuxiliaryOnUs, 15) 267 } 268 269 // ExternalProcessingCodeField gets the ExternalProcessingCode field 270 func (ci *CreditItem) ExternalProcessingCodeField() string { 271 return ci.alphaField(ci.ExternalProcessingCode, 1) 272 } 273 274 // PostingBankRoutingNumberField gets the PostingBankRoutingNumber field 275 func (ci *CreditItem) PostingBankRoutingNumberField() string { 276 return ci.stringField(ci.PostingBankRoutingNumber, 9) 277 } 278 279 // OnUsField gets the OnUs field 280 func (ci *CreditItem) OnUsField() string { 281 return ci.nbsmField(ci.OnUs, 20) 282 } 283 284 // ItemAmountField gets the temAmount field 285 func (ci *CreditItem) ItemAmountField() string { 286 return ci.numericField(ci.ItemAmount, 14) 287 } 288 289 // CreditItemSequenceNumberField gets the CreditItemSequenceNumber field 290 func (ci *CreditItem) CreditItemSequenceNumberField() string { 291 return ci.alphaField(ci.CreditItemSequenceNumber, 15) 292 } 293 294 // DocumentationTypeIndicatorField gets the DocumentationTypeIndicator field 295 func (ci *CreditItem) DocumentationTypeIndicatorField() string { 296 return ci.alphaField(ci.DocumentationTypeIndicator, 1) 297 } 298 299 // AccountTypeCodeField gets the AccountTypeCode field 300 func (ci *CreditItem) AccountTypeCodeField() string { 301 return ci.alphaField(ci.AccountTypeCode, 1) 302 } 303 304 // SourceWorkCodeField gets the SourceWorkCode field 305 func (ci *CreditItem) SourceWorkCodeField() string { 306 return ci.alphaField(ci.SourceWorkCode, 2) 307 } 308 309 // UserFieldField gets the UserField field 310 func (ci *CreditItem) UserFieldField() string { 311 return ci.alphaField(ci.UserField, 16) 312 } 313 314 // reservedField gets reserved - blank space 315 func (ci *CreditItem) reservedField() string { 316 return ci.alphaField(ci.reserved, 4) 317 }