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  }