github.com/moov-io/imagecashletter@v0.10.1/fileHeader.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 "time" 12 "unicode/utf8" 13 ) 14 15 // FileHeader Record is mandatory 16 type FileHeader struct { 17 // ID is a client defined string used as a reference to this record. 18 ID string `json:"id"` 19 // recordType defines the type of record. 20 recordType string 21 // standardLevel identifies the standard level of the file. 22 // Values: 03, 30, 35 23 // 03: DSTU X9.37 - 2003 24 // 30: X9.100-187-2008 25 // 35: X9.100-187-2013 and 2016 26 StandardLevel string `json:"standardLevel"` 27 // TestFileIndicator identifies whether the file is a test or production file. 28 // Values: 29 // T: Test File 30 // P: Production File 31 TestFileIndicator string `json:"testIndicator"` 32 // ImmediateDestination contains the routing and transit number of the Federal Reserve Bank 33 // (FRB) or receiver to which the file is being sent. Format: TTTTAAAAC, where: 34 // TTTT Federal Reserve Prefix 35 // AAAA ABA Institution Identifier 36 // C Check Digit 37 // For a number that identifies a non-financial institution: NNNNNNNNN 38 ImmediateDestination string `json:"immediateDestination"` 39 // ImmediateOrigin contains the routing and transit number of the Federal Reserve Bank 40 // (FRB) or receiver from which the file is being sent. Format: TTTTAAAAC, where: 41 // TTTT Federal Reserve Prefix 42 // AAAA ABA Institution Identifier 43 // C Check Digit 44 // For a number that identifies a non-financial institution: NNNNNNNNN 45 ImmediateOrigin string `json:"immediateOrigin"` 46 // FileCreationDate is the date that the immediate origin institution creates the file. Default time shall be in 47 // Eastern Time zone format. Other time zones may be used under clearing arrangements. 48 // Format: YYYYMMDD, where: YYYY year, MM month, DD day 49 // Values: 50 // YYYY 1993 through 9999 51 // MM 01 through 12 52 // DD 01 through 31 53 FileCreationDate time.Time `json:"fileCreationDate"` 54 // FileCreationTime is the time the immediate origin institution creates the file. Default time shall be in 55 // Eastern Time zone format. Other time zones may be used under clearing arrangements. 56 // Format: hhmm, where: hh hour, mm minute 57 // Values: 58 // hh '00' through '23' 59 // mm '00' through '59' 60 FileCreationTime time.Time `json:"fileCreationTime"` 61 // ResendIndicator indicates whether the file has been previously transmitted. 62 // Values: 63 // Y: Yes 64 // N: No 65 ResendIndicator string `json:"ResendIndicator"` 66 // ImmediateDestinationName identifies the short name of the institution that receives the file. 67 ImmediateDestinationName string `json:"immediateDestinationName"` 68 // ImmediateOriginName identifies the short name of the institution that sends the file. 69 ImmediateOriginName string `json:"ImmediateOriginName"` 70 // FileIDModifier is a code that permits multiple files, created on the same date, same time and between the 71 // same institutions, to be distinguished one from another. If all of the following fields in a previous file are 72 // equal to the same fields in this file: FileHeader ImmediateDestination, ImmediateOrigin, FileCreationDate, and 73 // FileCreationTime, it must be defined. 74 FileIDModifier string `json:"fileIDModifier"` 75 // CountryCode is a 2-character code as approved by the International Organization for Standardization (ISO) used 76 // to identify the country in which the payer bank is located. 77 // Example: US = United States 78 // Values for other countries can be found on the International Organization for Standardization 79 // website: www.iso.org. 80 CountryCode string `json:"countryCode"` 81 // UserField identifies a field used at the discretion of users of the standard. 82 UserField string `json:"userField"` 83 // CompanionDocumentIndicator identifies a field used to indicate the Companion Document being used. 84 // Shall be present only under clearing arrangements. Companion Document usage and values 85 // defined by clearing arrangements. 86 // Values: 87 // 0–9 Reserved for United States use 88 // A–J Reserved for Canadian use 89 // Other - as defined by clearing arrangements. 90 CompanionDocumentIndicator string `json:"companionDocumentIndicator"` 91 // validator is composed for ImageCashLetter data validation 92 validator 93 // converters is composed for ImageCashLetter to golang Converters 94 converters 95 } 96 97 // NewFileHeader returns a new FileHeader with default values for non exported fields 98 func NewFileHeader() FileHeader { 99 fh := FileHeader{} 100 fh.setRecordType() 101 fh.StandardLevel = "35" 102 return fh 103 } 104 105 func (fh *FileHeader) setRecordType() { 106 if fh == nil { 107 return 108 } 109 fh.recordType = "01" 110 } 111 112 // Parse takes the input record string and parses the FileHeader values 113 func (fh *FileHeader) Parse(record string) { 114 if utf8.RuneCountInString(record) != 80 { 115 return 116 } 117 // Character position 1-2, Always "01" 118 fh.setRecordType() 119 // 03-04 120 fh.StandardLevel = fh.parseStringField(record[2:4]) 121 // 05-05 122 fh.TestFileIndicator = fh.parseStringField(record[4:5]) 123 // 06-14 124 fh.ImmediateDestination = fh.parseStringField(record[5:14]) 125 // 15-23 126 fh.ImmediateOrigin = fh.parseStringField(record[14:23]) 127 // 24-31 128 fh.FileCreationDate = fh.parseYYYYMMDDDate(record[23:31]) 129 // 32-35 130 fh.FileCreationTime = fh.parseSimpleTime(record[31:35]) 131 // 36-36 132 fh.ResendIndicator = fh.parseStringField(record[35:36]) 133 // 37-54 134 fh.ImmediateDestinationName = fh.parseStringField(record[36:54]) 135 // 55-72 136 fh.ImmediateOriginName = fh.parseStringField(record[54:72]) 137 // 73-73 138 fh.FileIDModifier = fh.parseStringField(record[72:73]) 139 // 74-75 140 fh.CountryCode = fh.parseStringField(record[73:75]) 141 // 76-79 142 fh.UserField = fh.parseStringField(record[75:79]) 143 // 80-80 144 fh.CompanionDocumentIndicator = fh.parseStringField(record[79:80]) 145 } 146 147 func (fh *FileHeader) UnmarshalJSON(data []byte) error { 148 type Alias FileHeader 149 aux := struct { 150 *Alias 151 }{ 152 (*Alias)(fh), 153 } 154 if err := json.Unmarshal(data, &aux); err != nil { 155 return err 156 } 157 fh.setRecordType() 158 return nil 159 } 160 161 // String writes the FileHeader struct to a string. 162 func (fh *FileHeader) String() string { 163 if fh == nil { 164 return "" 165 } 166 167 var buf strings.Builder 168 buf.Grow(80) 169 buf.WriteString(fh.recordType) 170 buf.WriteString(fh.StandardLevelField()) 171 buf.WriteString(fh.TestFileIndicatorField()) 172 buf.WriteString(fh.ImmediateDestinationField()) 173 buf.WriteString(fh.ImmediateOriginField()) 174 buf.WriteString(fh.FileCreationDateField()) 175 buf.WriteString(fh.FileCreationTimeField()) 176 buf.WriteString(fh.ResendIndicatorField()) 177 buf.WriteString(fh.ImmediateDestinationNameField()) 178 buf.WriteString(fh.ImmediateOriginNameField()) 179 buf.WriteString(fh.FileIDModifierField()) 180 buf.WriteString(fh.CountryCodeField()) 181 buf.WriteString(fh.UserFieldField()) 182 buf.WriteString(fh.CompanionDocumentIndicatorField()) 183 return buf.String() 184 } 185 186 // Validate performs imagecashletter format rule checks on the record and returns an error if not Validated 187 // The first error encountered is returned and stops the parsing. 188 func (fh *FileHeader) Validate() error { 189 if err := fh.fieldInclusion(); err != nil { 190 return err 191 } 192 if fh.recordType != "01" { 193 msg := fmt.Sprintf(msgRecordType, 01) 194 return &FieldError{FieldName: "recordType", Value: fh.recordType, Msg: msg} 195 } 196 if err := fh.isStandardLevel(fh.StandardLevel); err != nil { 197 return &FieldError{FieldName: "StandardLevel", Value: fh.StandardLevel, Msg: err.Error()} 198 } 199 // Mandatory 200 if err := fh.isTestFileIndicator(fh.TestFileIndicator); err != nil { 201 return &FieldError{FieldName: "TestFileIndicator", Value: fh.TestFileIndicator, Msg: err.Error()} 202 } 203 // Mandatory 204 if err := fh.isResendIndicator(fh.ResendIndicator); err != nil { 205 return &FieldError{FieldName: "ResendIndicator", Value: fh.ResendIndicator, Msg: err.Error()} 206 } 207 if err := fh.isAlphanumericSpecial(fh.ImmediateDestinationName); err != nil { 208 return &FieldError{FieldName: "ImmediateDestinationName", Value: fh.ImmediateDestinationName, Msg: err.Error()} 209 } 210 if err := fh.isAlphanumericSpecial(fh.ImmediateOriginName); err != nil { 211 return &FieldError{FieldName: "ImmediateOriginName", Value: fh.ImmediateOriginName, Msg: err.Error()} 212 } 213 if err := fh.isAlphanumeric(fh.FileIDModifier); err != nil { 214 return &FieldError{FieldName: "FileIDModifier", Value: fh.FileIDModifier, Msg: err.Error()} 215 } 216 // Conditional 217 if fh.CountryCode == "US" { 218 if err := fh.isCompanionDocumentIndicatorUS(fh.CompanionDocumentIndicator); err != nil { 219 return &FieldError{FieldName: "CompanionDocumentIndicator", Value: fh.CompanionDocumentIndicator, Msg: err.Error()} 220 } 221 } 222 // Conditional 223 if fh.CountryCode == "CA" { 224 if err := fh.isCompanionDocumentIndicatorCA(fh.CompanionDocumentIndicator); err != nil { 225 return &FieldError{FieldName: "CompanionDocumentIndicator", Value: fh.CompanionDocumentIndicator, Msg: err.Error()} 226 } 227 } 228 if err := fh.isAlphanumericSpecial(fh.UserField); err != nil { 229 return &FieldError{FieldName: "UserField", Value: fh.UserField, Msg: err.Error()} 230 } 231 return nil 232 } 233 234 // fieldInclusion validate mandatory fields are not default values. If fields are 235 // invalid the Electronic Exchange will be returned. 236 func (fh *FileHeader) fieldInclusion() error { 237 if fh.recordType == "" { 238 return &FieldError{FieldName: "recordType", 239 Value: fh.recordType, 240 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 241 } 242 if fh.StandardLevel == "" { 243 return &FieldError{FieldName: "StandardLevel", 244 Value: fh.StandardLevel, 245 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 246 } 247 if fh.TestFileIndicator == "" { 248 return &FieldError{FieldName: "TestFileIndicator", 249 Value: fh.TestFileIndicator, 250 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 251 } 252 if fh.ResendIndicator == "" { 253 return &FieldError{FieldName: "ResendIndicator", 254 Value: fh.ResendIndicator, 255 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 256 } 257 if fh.ImmediateDestination == "" { 258 return &FieldError{FieldName: "ImmediateDestination", 259 Value: fh.ImmediateDestination, 260 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 261 } 262 if fh.ImmediateOrigin == "" { 263 return &FieldError{FieldName: "ImmediateOrigin", 264 Value: fh.ImmediateOrigin, 265 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 266 } 267 if fh.ImmediateOriginField() == "000000000" { 268 return &FieldError{FieldName: "ImmediateOrigin", 269 Value: fh.ImmediateOrigin, 270 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 271 } 272 if fh.ImmediateDestinationField() == "000000000" { 273 return &FieldError{FieldName: "ImmediateDestination", 274 Value: fh.ImmediateDestination, 275 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 276 } 277 if fh.FileCreationDate.IsZero() { 278 return &FieldError{FieldName: "FileCreationDate", 279 Value: fh.FileCreationDate.String(), 280 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 281 } 282 if fh.FileCreationTime.IsZero() { 283 return &FieldError{FieldName: "FileCreationTime", 284 Value: fh.FileCreationTime.String(), 285 Msg: msgFieldInclusion + ", did you use FileHeader()?"} 286 } 287 return nil 288 289 } 290 291 // StandardLevelField gets the StandardLevel field 292 func (fh *FileHeader) StandardLevelField() string { 293 return fh.alphaField(fh.StandardLevel, 2) 294 } 295 296 // TestFileIndicatorField gets the TestFileIndicator field 297 func (fh *FileHeader) TestFileIndicatorField() string { 298 return fh.alphaField(fh.TestFileIndicator, 1) 299 } 300 301 // ImmediateDestinationField gets the ImmediateDestination routing number field 302 func (fh *FileHeader) ImmediateDestinationField() string { 303 return fh.stringField(fh.ImmediateDestination, 9) 304 } 305 306 // ImmediateOriginField gets the ImmediateOrigin routing number field 307 func (fh *FileHeader) ImmediateOriginField() string { 308 return fh.stringField(fh.ImmediateOrigin, 9) 309 } 310 311 // FileCreationDateField gets the FileCreationDate field in YYYYMMDD format 312 func (fh *FileHeader) FileCreationDateField() string { 313 return fh.formatYYYYMMDDDate(fh.FileCreationDate) 314 } 315 316 // FileCreationTimeField gets the FileCreationTime in HHMM format 317 func (fh *FileHeader) FileCreationTimeField() string { 318 return fh.formatSimpleTime(fh.FileCreationTime) 319 } 320 321 // ResendIndicatorField gets the TestFileIndicator field 322 func (fh *FileHeader) ResendIndicatorField() string { 323 return fh.alphaField(fh.ResendIndicator, 1) 324 } 325 326 // ImmediateDestinationNameField gets the ImmediateDestinationName field padded 327 func (fh *FileHeader) ImmediateDestinationNameField() string { 328 return fh.alphaField(fh.ImmediateDestinationName, 18) 329 } 330 331 // ImmediateOriginNameField gets the ImmediateOriginName field padded 332 func (fh *FileHeader) ImmediateOriginNameField() string { 333 return fh.alphaField(fh.ImmediateOriginName, 18) 334 } 335 336 // CountryCodeField gets the CountryCode field 337 func (fh *FileHeader) CountryCodeField() string { 338 return fh.alphaField(fh.CountryCode, 2) 339 } 340 341 // UserFieldField gets the UserField field 342 func (fh *FileHeader) UserFieldField() string { 343 return fh.alphaField(fh.UserField, 4) 344 } 345 346 // FileIDModifierField gets the FileIDModifier field 347 func (fh *FileHeader) FileIDModifierField() string { 348 return fh.alphaField(fh.FileIDModifier, 1) 349 } 350 351 // CompanionDocumentIndicatorField gets the CompanionDocumentIndicator field 352 func (fh *FileHeader) CompanionDocumentIndicatorField() string { 353 return fh.alphaField(fh.CompanionDocumentIndicator, 1) 354 }