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  }