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  }