github.com/moov-io/imagecashletter@v0.10.1/cashLetter.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  	"errors"
     9  	"fmt"
    10  )
    11  
    12  // CashLetterError is an Error that describes CashLetter validation issues
    13  type CashLetterError struct {
    14  	CashLetterID string
    15  	FieldName    string
    16  	Msg          string
    17  }
    18  
    19  func (e *CashLetterError) Error() string {
    20  	return fmt.Sprintf("CashLetterNumber %s %s %s", e.CashLetterID, e.FieldName, e.Msg)
    21  }
    22  
    23  // Errors specific to parsing a CashLetter
    24  var (
    25  	msgCashLetterBundleEntries = "%v cannot have bundle entries"
    26  	msgCashLetterRoutingNumber = "%v cannot have a Routing Number Summary"
    27  	msgMandatoryRecord         = "record is mandatory"
    28  )
    29  
    30  // CashLetter contains CashLetterHeader, CashLetterControl and Bundle records.
    31  type CashLetter struct {
    32  	// ID is a client defined string used as a reference to this record.
    33  	ID string `json:"id"`
    34  	// CashLetterHeader is a Cash Letter Header Record
    35  	CashLetterHeader *CashLetterHeader `json:"cashLetterHeader,omitempty"`
    36  	// Bundles is an array of Bundle
    37  	Bundles []*Bundle `json:"bundles,omitempty"`
    38  	// Credits is an array of Credit
    39  	Credits []*Credit `json:"credit,omitempty"`
    40  	// CreditItems is an array of CreditItem
    41  	CreditItems []*CreditItem `json:"creditItem,omitempty"`
    42  	// RoutingNumberSummary is an array of RoutingNumberSummary
    43  	RoutingNumberSummary []*RoutingNumberSummary `json:"routingNumberSummary,omitempty"`
    44  	// currentBundle is the currentBundle being parsed
    45  	currentBundle *Bundle
    46  	// RoutingNumberSummary is an imagecashletter RoutingNumberSummary
    47  	currentRoutingNumberSummary *RoutingNumberSummary
    48  	// CashLetterControl is a Cash Letter Control Record
    49  	CashLetterControl *CashLetterControl `json:"cashLetterControl,omitempty"`
    50  }
    51  
    52  // NewCashLetter takes a CashLetterHeader and returns a CashLetter
    53  func NewCashLetter(clh *CashLetterHeader) CashLetter {
    54  	cl := CashLetter{}
    55  	cl.SetControl(NewCashLetterControl())
    56  	cl.SetHeader(clh)
    57  	return cl
    58  }
    59  
    60  func (cl *CashLetter) setRecordType() {
    61  	if cl == nil {
    62  		return
    63  	}
    64  
    65  	cl.CashLetterHeader.setRecordType()
    66  	for i := range cl.Bundles {
    67  		cl.Bundles[i].setRecordType()
    68  	}
    69  	for i := range cl.CreditItems {
    70  		cl.CreditItems[i].setRecordType()
    71  	}
    72  	for i := range cl.RoutingNumberSummary {
    73  		cl.RoutingNumberSummary[i].setRecordType()
    74  	}
    75  	cl.CashLetterControl.setRecordType()
    76  }
    77  
    78  // Validate performs ImageCashLetter validations and format rule checks and returns an error if not Validated
    79  func (cl *CashLetter) Validate() error {
    80  	if cl.CashLetterHeader == nil {
    81  		return errors.New("nil CashLetterHeader")
    82  	}
    83  
    84  	if cl.CashLetterHeader.RecordTypeIndicator == "N" {
    85  		if cl.GetBundles() != nil {
    86  			return &CashLetterError{
    87  				CashLetterID: cl.CashLetterHeader.CashLetterID,
    88  				FieldName:    "RecordTypeIndicator",
    89  				Msg:          fmt.Sprintf(msgCashLetterBundleEntries, cl.CashLetterHeader.RecordTypeIndicator),
    90  			}
    91  		}
    92  	}
    93  	switch cl.CashLetterHeader.CollectionTypeIndicator {
    94  	case
    95  		"00", "01", "02":
    96  	default:
    97  		if cl.GetRoutingNumberSummary() != nil {
    98  			return &CashLetterError{
    99  				CashLetterID: cl.CashLetterHeader.CashLetterID,
   100  				FieldName:    "CollectionTypeIndicator",
   101  				Msg:          fmt.Sprintf(msgCashLetterRoutingNumber, cl.CashLetterHeader.CollectionTypeIndicator),
   102  			}
   103  		}
   104  	}
   105  
   106  	if cl.CashLetterControl == nil {
   107  		return &CashLetterError{
   108  			CashLetterID: cl.CashLetterHeader.CashLetterID,
   109  			FieldName:    "CashLetterControl",
   110  			Msg:          msgMandatoryRecord,
   111  		}
   112  	}
   113  
   114  	if err := cl.CashLetterControl.Validate(); err != nil {
   115  		return err
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // build a valid CashLetter by building a CashLetterControl. An error is returned if
   122  // the CashLetter being built has invalid records.
   123  func (cl *CashLetter) build() error {
   124  
   125  	// Requires a valid CashLetterHeader
   126  	if err := cl.CashLetterHeader.Validate(); err != nil {
   127  		return err
   128  	}
   129  
   130  	// CashLetterControl Counts
   131  	cashLetterBundleCount := len(cl.Bundles)
   132  	cashLetterItemsCount := 0
   133  	cashLetterTotalAmount := 0
   134  	cashLetterImagesCount := 0
   135  
   136  	// Sequence Numbers
   137  	bundleSequenceNumber := 1
   138  	// creditIndicator
   139  	creditIndicator := 0
   140  
   141  	if len(cl.GetCreditItems()) > 0 {
   142  		cashLetterItemsCount = cashLetterItemsCount + len(cl.GetCreditItems())
   143  		creditIndicator = 1
   144  	}
   145  	// Bundles
   146  	for _, b := range cl.Bundles {
   147  
   148  		// Set Bundle Sequence Numbers
   149  		b.BundleHeader.SetBundleSequenceNumber(bundleSequenceNumber)
   150  
   151  		// Sequence  Number
   152  		cdSequenceNumber := 1
   153  
   154  		// Check Items
   155  		for _, cd := range b.Checks {
   156  
   157  			if cd.EceInstitutionItemSequenceNumber != "" {
   158  				i := cd.parseNumField(cd.EceInstitutionItemSequenceNumber)
   159  				cdSequenceNumber = i
   160  			}
   161  
   162  			// Record Numbers
   163  			cdAddendumARecordNumber := 1
   164  			cdAddendumCRecordNumber := 1
   165  
   166  			// Set CheckDetail Sequence Numbers
   167  			cd.SetEceInstitutionItemSequenceNumber(cdSequenceNumber)
   168  
   169  			// Set Addenda SequenceNumber and RecordNumber
   170  			for i := range cd.CheckDetailAddendumA {
   171  				cd.CheckDetailAddendumA[i].SetBOFDItemSequenceNumber(cdSequenceNumber)
   172  				cd.CheckDetailAddendumA[i].RecordNumber = cdAddendumARecordNumber
   173  				cdAddendumARecordNumber++
   174  				if cdAddendumARecordNumber > 9 {
   175  					cdAddendumARecordNumber = 1
   176  				}
   177  			}
   178  			for x := range cd.CheckDetailAddendumC {
   179  				cd.CheckDetailAddendumC[x].SetEndorsingBankItemSequenceNumber(cdSequenceNumber)
   180  				cd.CheckDetailAddendumC[x].RecordNumber = cdAddendumARecordNumber
   181  				cdAddendumCRecordNumber++
   182  				if cdAddendumCRecordNumber > 99 {
   183  					cdAddendumCRecordNumber = 1
   184  				}
   185  			}
   186  			cdSequenceNumber++
   187  
   188  			cashLetterItemsCount = cashLetterItemsCount + 1
   189  			cashLetterTotalAmount = cashLetterTotalAmount + cd.ItemAmount
   190  			cashLetterImagesCount = cashLetterImagesCount + len(cd.ImageViewDetail)
   191  		}
   192  
   193  		// Returns Items
   194  		for _, rd := range b.Returns {
   195  
   196  			// Sequence  Number
   197  			var rdSequenceNumber int
   198  			rdSequenceNumber++
   199  
   200  			// Record Numbers
   201  			rdAddendumARecordNumber := 1
   202  			rdAddendumDRecordNumber := 1
   203  
   204  			// Set ReturnDetail Sequence Numbers
   205  			rd.SetEceInstitutionItemSequenceNumber(rdSequenceNumber)
   206  
   207  			// Set Addenda SequenceNumber and RecordNumber
   208  			for i := range rd.ReturnDetailAddendumA {
   209  				rd.ReturnDetailAddendumA[i].SetBOFDItemSequenceNumber(rdSequenceNumber)
   210  				rd.ReturnDetailAddendumA[i].RecordNumber = rdAddendumARecordNumber
   211  				rdAddendumARecordNumber++
   212  				if rdAddendumARecordNumber > 9 {
   213  					rdAddendumARecordNumber = 1
   214  				}
   215  			}
   216  
   217  			for x := range rd.ReturnDetailAddendumD {
   218  				rd.ReturnDetailAddendumD[x].SetEndorsingBankItemSequenceNumber(rdSequenceNumber)
   219  				rd.ReturnDetailAddendumD[x].RecordNumber = rdAddendumDRecordNumber
   220  				rdAddendumDRecordNumber++
   221  				if rdAddendumDRecordNumber > 99 {
   222  					rdAddendumDRecordNumber = 1
   223  				}
   224  			}
   225  
   226  			cashLetterItemsCount = cashLetterItemsCount + 1
   227  			cashLetterTotalAmount = cashLetterTotalAmount + rd.ItemAmount
   228  			cashLetterImagesCount = cashLetterImagesCount + len(rd.ImageViewDetail)
   229  		}
   230  		// Validate Bundle
   231  		if err := b.Validate(); err != nil {
   232  			return err
   233  		}
   234  		// Build Bundle
   235  		if err := b.build(); err != nil {
   236  			return err
   237  		}
   238  
   239  		bundleSequenceNumber++
   240  	}
   241  
   242  	// build a CashLetterControl record
   243  	clc := NewCashLetterControl()
   244  	clc.CashLetterBundleCount = cashLetterBundleCount
   245  	clc.CashLetterItemsCount = cashLetterItemsCount
   246  	clc.CashLetterTotalAmount = cashLetterTotalAmount
   247  	clc.CashLetterImagesCount = cashLetterImagesCount
   248  	if cl.CashLetterControl.ECEInstitutionName != "" {
   249  		clc.ECEInstitutionName = cl.CashLetterControl.ECEInstitutionName
   250  	} else {
   251  		clc.ECEInstitutionName = cl.GetHeader().ECEInstitutionRoutingNumber
   252  	}
   253  	clc.CreditTotalIndicator = creditIndicator
   254  	cl.CashLetterControl = clc
   255  	return nil
   256  }
   257  
   258  // Create creates a CashLetter of Bundles containing CheckDetail or ReturnDetail
   259  func (cl *CashLetter) Create() error {
   260  	if err := cl.build(); err != nil {
   261  		return err
   262  	}
   263  	return cl.Validate()
   264  }
   265  
   266  // SetHeader appends a CashLetterHeader to the CashLetter
   267  func (cl *CashLetter) SetHeader(cashLetterHeader *CashLetterHeader) {
   268  	cl.CashLetterHeader = cashLetterHeader
   269  }
   270  
   271  // GetHeader returns the current CashLetter header
   272  func (cl *CashLetter) GetHeader() *CashLetterHeader {
   273  	return cl.CashLetterHeader
   274  }
   275  
   276  // SetControl appends a CashLetterControl to the CashLetter
   277  func (cl *CashLetter) SetControl(cashLetterControl *CashLetterControl) {
   278  	cl.CashLetterControl = cashLetterControl
   279  }
   280  
   281  // GetControl returns the current CashLetter Control
   282  func (cl *CashLetter) GetControl() *CashLetterControl {
   283  	return cl.CashLetterControl
   284  }
   285  
   286  // AddBundle appends a Bundle to the CashLetter
   287  func (cl *CashLetter) AddBundle(bundle *Bundle) []*Bundle {
   288  	cl.Bundles = append(cl.Bundles, bundle)
   289  	return cl.Bundles
   290  }
   291  
   292  // GetBundles returns a slice of Bundle for the CashLetter
   293  func (cl *CashLetter) GetBundles() []*Bundle {
   294  	if cl == nil {
   295  		return nil
   296  	}
   297  	return cl.Bundles
   298  }
   299  
   300  // AddRoutingNumberSummary appends a RoutingNumberSummary to the CashLetter
   301  func (cl *CashLetter) AddRoutingNumberSummary(rns *RoutingNumberSummary) []*RoutingNumberSummary {
   302  	cl.RoutingNumberSummary = append(cl.RoutingNumberSummary, rns)
   303  	return cl.RoutingNumberSummary
   304  }
   305  
   306  // GetRoutingNumberSummary returns a slice of RoutingNumberSummary for the CashLetter
   307  func (cl *CashLetter) GetRoutingNumberSummary() []*RoutingNumberSummary {
   308  	if cl == nil {
   309  		return nil
   310  	}
   311  	return cl.RoutingNumberSummary
   312  }
   313  
   314  // AddCredit appends a CreditItem to the CashLetter
   315  func (cl *CashLetter) AddCredit(cr *Credit) []*Credit {
   316  	cl.Credits = append(cl.Credits, cr)
   317  	return cl.Credits
   318  }
   319  
   320  // AddCreditItem appends a CreditItem to the CashLetter
   321  func (cl *CashLetter) AddCreditItem(ci *CreditItem) []*CreditItem {
   322  	cl.CreditItems = append(cl.CreditItems, ci)
   323  	return cl.CreditItems
   324  }
   325  
   326  // GetCredits returns a slice of Credit for the CashLetter
   327  func (cl *CashLetter) GetCredits() []*Credit {
   328  	if cl == nil {
   329  		return nil
   330  	}
   331  	return cl.Credits
   332  }
   333  
   334  // GetCreditItems returns a slice of CreditItem for the CashLetter
   335  func (cl *CashLetter) GetCreditItems() []*CreditItem {
   336  	if cl == nil {
   337  		return nil
   338  	}
   339  	return cl.CreditItems
   340  }