github.com/moov-io/imagecashletter@v0.10.1/bundle.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  	"fmt"
     9  )
    10  
    11  // BundleError is an Error that describes bundle validation issues
    12  type BundleError struct {
    13  	BundleSequenceNumber string
    14  	FieldName            string
    15  	Msg                  string
    16  }
    17  
    18  func (e *BundleError) Error() string {
    19  	return fmt.Sprintf("BundleNumber %s %s %s", e.BundleSequenceNumber, e.FieldName, e.Msg)
    20  }
    21  
    22  // Addendum Counts
    23  const (
    24  	CheckDetailAddendumACount  = 9
    25  	CheckDetailAddendumBCount  = 1
    26  	CheckDetailAddendumCCount  = 99
    27  	ReturnDetailAddendumACount = 9
    28  	ReturnDetailAddendumBCount = 1
    29  	ReturnDetailAddendumCCount = 1
    30  	ReturnDetailAddendumDCount = 99
    31  )
    32  
    33  // Errors specific to parsing a Bundle
    34  var (
    35  	msgBundleEntries          = "must have Check Detail or Return Detail to be built"
    36  	msgBundleAddendum         = "%v found is greater than maximum of %v"
    37  	msgBundleAddendumCount    = "%v does not match Addenda Records"
    38  	msgBundleImageDetailCount = "does not match Image View Detail count of %v"
    39  )
    40  
    41  // Bundle contains forward items (checks)
    42  type Bundle struct {
    43  	// ID is a client defined string used as a reference to this record.
    44  	ID string `json:"id"`
    45  	// BundleHeader is a Bundle Header Record
    46  	BundleHeader *BundleHeader `json:"bundleHeader,omitempty"`
    47  	// Checks are Check Items: Check Detail Records, Check Detail Addendum Records, and Image Views
    48  	Checks []*CheckDetail `json:"checks,omitempty"`
    49  	// Returns are Return Items: Return Detail Records, Return Detail Addendum Records, and Image Views
    50  	Returns []*ReturnDetail `json:"returns,omitempty"`
    51  	// BundleControl is a Bundle Control Record
    52  	BundleControl *BundleControl `json:"bundleControl,omitempty"`
    53  }
    54  
    55  // NewBundle takes a BundleHeader and returns a Bundle
    56  func NewBundle(bh *BundleHeader) *Bundle {
    57  	b := new(Bundle)
    58  	b.SetControl(NewBundleControl())
    59  	b.SetHeader(bh)
    60  	return b
    61  }
    62  
    63  func (b *Bundle) setRecordType() {
    64  	if b == nil {
    65  		return
    66  	}
    67  	b.BundleHeader.setRecordType()
    68  	for i := range b.Checks {
    69  		b.Checks[i].setRecordType()
    70  	}
    71  	for i := range b.Returns {
    72  		b.Returns[i].setRecordType()
    73  	}
    74  	b.BundleControl.setRecordType()
    75  }
    76  
    77  // Validate performs imagecashletter validations and format rule checks and returns an error if not Validated
    78  func (b *Bundle) Validate() error {
    79  	if (len(b.Checks) <= 0) && (len(b.Returns) <= 0) {
    80  		seqNumber := ""
    81  		if b.BundleHeader != nil {
    82  			seqNumber = b.BundleHeader.BundleSequenceNumber
    83  		}
    84  		return &BundleError{BundleSequenceNumber: seqNumber, FieldName: "entries", Msg: msgBundleEntries}
    85  	}
    86  
    87  	if len(b.Checks) > 0 {
    88  		if err := b.checkDetailAddendumCount(); err != nil {
    89  			return err
    90  		}
    91  	} else {
    92  		if err := b.returnDetailAddendumCount(); err != nil {
    93  			return err
    94  		}
    95  	}
    96  	return nil
    97  }
    98  
    99  // build creates a valid Bundle by building  BundleControl. An error is returned if
   100  // the bundle being built has invalid records.
   101  func (b *Bundle) build() error {
   102  	// Requires a valid BundleHeader
   103  	if err := b.BundleHeader.Validate(); err != nil {
   104  		return err
   105  	}
   106  	if (len(b.Checks) <= 0) && (len(b.Returns) <= 0) {
   107  		seqNumber := ""
   108  		if b.BundleHeader != nil {
   109  			seqNumber = b.BundleHeader.BundleSequenceNumber
   110  		}
   111  		return &BundleError{BundleSequenceNumber: seqNumber, FieldName: "entries", Msg: msgBundleEntries}
   112  	}
   113  
   114  	itemCount := 0
   115  	bundleTotalAmount := 0
   116  	micrValidTotalAmount := 0
   117  	bundleImagesCount := 0
   118  	// The current Implementation doe snot support CreditItems as part of a bundle so BundleControl.CreditIndicator = 0
   119  	creditIndicator := 0
   120  
   121  	// Forward Items
   122  	for _, cd := range b.Checks {
   123  
   124  		// Validate CheckDetailAddendum* and ImageView*
   125  		if err := b.ValidateForwardItems(cd); err != nil {
   126  			return err
   127  		}
   128  
   129  		itemCount = itemCount + 1
   130  		bundleTotalAmount = bundleTotalAmount + cd.ItemAmount
   131  		if cd.MICRValidIndicator == 1 {
   132  			micrValidTotalAmount = micrValidTotalAmount + cd.ItemAmount
   133  		}
   134  
   135  		bundleImagesCount = bundleImagesCount + len(cd.ImageViewDetail)
   136  	}
   137  
   138  	// Return Items
   139  	for _, rd := range b.Returns {
   140  
   141  		// Validate ReturnDetailAddendum* and ImageView*
   142  		if err := b.ValidateReturnItems(rd); err != nil {
   143  			return err
   144  		}
   145  		itemCount = itemCount + 1
   146  		bundleTotalAmount = bundleTotalAmount + rd.ItemAmount
   147  		bundleImagesCount = bundleImagesCount + len(rd.ImageViewDetail)
   148  	}
   149  
   150  	// build a BundleControl record
   151  	bc := NewBundleControl()
   152  	bc.BundleItemsCount = itemCount
   153  	bc.BundleTotalAmount = bundleTotalAmount
   154  	bc.MICRValidTotalAmount = micrValidTotalAmount
   155  	bc.BundleImagesCount = bundleImagesCount
   156  	bc.CreditTotalIndicator = creditIndicator
   157  	b.BundleControl = bc
   158  	return nil
   159  }
   160  
   161  // SetHeader appends an BundleHeader to the Bundle
   162  func (b *Bundle) SetHeader(bundleHeader *BundleHeader) {
   163  	b.BundleHeader = bundleHeader
   164  }
   165  
   166  // GetHeader returns the current Bundle header
   167  func (b *Bundle) GetHeader() *BundleHeader {
   168  	return b.BundleHeader
   169  }
   170  
   171  // SetControl appends an BundleControl to the Bundle
   172  func (b *Bundle) SetControl(bundleControl *BundleControl) {
   173  	b.BundleControl = bundleControl
   174  }
   175  
   176  // GetControl returns the current Bundle Control
   177  func (b *Bundle) GetControl() *BundleControl {
   178  	return b.BundleControl
   179  }
   180  
   181  // AddCheckDetail appends a CheckDetail to the Bundle
   182  func (b *Bundle) AddCheckDetail(cd *CheckDetail) {
   183  	b.Checks = append(b.Checks, cd)
   184  }
   185  
   186  // GetChecks returns a slice of check details for the Bundle
   187  func (b *Bundle) GetChecks() []*CheckDetail {
   188  	if b == nil {
   189  		return nil
   190  	}
   191  	return b.Checks
   192  }
   193  
   194  // AddReturnDetail appends a ReturnDetail to the Bundle
   195  func (b *Bundle) AddReturnDetail(rd *ReturnDetail) {
   196  	b.Returns = append(b.Returns, rd)
   197  }
   198  
   199  // GetReturns returns a slice of return details for the Bundle
   200  func (b *Bundle) GetReturns() []*ReturnDetail {
   201  	if b == nil {
   202  		return nil
   203  	}
   204  	return b.Returns
   205  }
   206  
   207  // ValidateForwardItems calls Validate function for check items
   208  func (b *Bundle) ValidateForwardItems(cd *CheckDetail) error {
   209  	// Validate items
   210  	for _, addendumA := range cd.CheckDetailAddendumA {
   211  		if err := addendumA.Validate(); err != nil {
   212  			return err
   213  		}
   214  	}
   215  	for _, addendumB := range cd.CheckDetailAddendumB {
   216  		if err := addendumB.Validate(); err != nil {
   217  			return err
   218  		}
   219  	}
   220  	for _, addendumC := range cd.CheckDetailAddendumC {
   221  		if err := addendumC.Validate(); err != nil {
   222  			return err
   223  		}
   224  	}
   225  	for _, ivDetail := range cd.ImageViewDetail {
   226  		if err := ivDetail.Validate(); err != nil {
   227  			return err
   228  		}
   229  	}
   230  	for _, ivData := range cd.ImageViewData {
   231  		if err := ivData.Validate(); err != nil {
   232  			return err
   233  		}
   234  	}
   235  	for _, ivAnalysis := range cd.ImageViewAnalysis {
   236  		if err := ivAnalysis.Validate(); err != nil {
   237  			return err
   238  		}
   239  	}
   240  	return nil
   241  }
   242  
   243  // ValidateReturnItems calls Validate function for return items
   244  func (b *Bundle) ValidateReturnItems(rd *ReturnDetail) error {
   245  	// Validate items
   246  	for _, addendumA := range rd.ReturnDetailAddendumA {
   247  		if err := addendumA.Validate(); err != nil {
   248  			return err
   249  		}
   250  	}
   251  	for _, addendumB := range rd.ReturnDetailAddendumB {
   252  		if err := addendumB.Validate(); err != nil {
   253  			return err
   254  		}
   255  	}
   256  	for _, addendumC := range rd.ReturnDetailAddendumC {
   257  		if err := addendumC.Validate(); err != nil {
   258  			return err
   259  		}
   260  	}
   261  	for _, addendumD := range rd.ReturnDetailAddendumD {
   262  		if err := addendumD.Validate(); err != nil {
   263  			return err
   264  		}
   265  	}
   266  	for _, ivDetail := range rd.ImageViewDetail {
   267  		if err := ivDetail.Validate(); err != nil {
   268  			return err
   269  		}
   270  	}
   271  	for _, ivData := range rd.ImageViewDetail {
   272  		if err := ivData.Validate(); err != nil {
   273  			return err
   274  		}
   275  	}
   276  	for _, ivAnalysis := range rd.ImageViewDetail {
   277  		if err := ivAnalysis.Validate(); err != nil {
   278  			return err
   279  		}
   280  	}
   281  	return nil
   282  }
   283  
   284  // checkDetailAddendumCount validates CheckDetail AddendumCount
   285  func (b *Bundle) checkDetailAddendumCount() error {
   286  	bundleSequenceNumber := "-"
   287  	if b.BundleHeader != nil {
   288  		bundleSequenceNumber = b.BundleHeader.BundleSequenceNumber
   289  	}
   290  
   291  	// Check Items
   292  	for _, cd := range b.Checks {
   293  		if cd.AddendumCount != len(cd.CheckDetailAddendumA)+len(cd.CheckDetailAddendumB)+len(cd.CheckDetailAddendumC) {
   294  			msg := fmt.Sprintf(msgBundleAddendumCount, cd.AddendumCount)
   295  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "AddendumCount", Msg: msg}
   296  		}
   297  		if len(cd.CheckDetailAddendumA) > CheckDetailAddendumACount {
   298  			msg := fmt.Sprintf(msgBundleAddendum, len(cd.CheckDetailAddendumA), CheckDetailAddendumACount)
   299  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "CheckDetailAddendumA", Msg: msg}
   300  		}
   301  		if len(cd.CheckDetailAddendumB) > CheckDetailAddendumBCount {
   302  			msg := fmt.Sprintf(msgBundleAddendum, len(cd.CheckDetailAddendumB), CheckDetailAddendumBCount)
   303  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "CheckDetailAddendumB", Msg: msg}
   304  		}
   305  		if len(cd.CheckDetailAddendumC) > CheckDetailAddendumCCount {
   306  			msg := fmt.Sprintf(msgBundleAddendum, len(cd.CheckDetailAddendumC), CheckDetailAddendumCCount)
   307  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "CheckDetailAddendumC", Msg: msg}
   308  		}
   309  
   310  	}
   311  	return nil
   312  }
   313  
   314  // returnDetailAddendumCount validates ReturnDetail AddendumCount
   315  func (b *Bundle) returnDetailAddendumCount() error {
   316  	bundleSequenceNumber := "-"
   317  	if b.BundleHeader != nil {
   318  		bundleSequenceNumber = b.BundleHeader.BundleSequenceNumber
   319  	}
   320  
   321  	for _, rd := range b.Returns {
   322  		if rd.AddendumCount != len(rd.ReturnDetailAddendumA)+len(rd.ReturnDetailAddendumB)+len(rd.ReturnDetailAddendumC)+len(rd.ReturnDetailAddendumD) {
   323  			msg := fmt.Sprintf(msgBundleAddendumCount, rd.AddendumCount)
   324  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "AddendumCount", Msg: msg}
   325  		}
   326  		if len(rd.ReturnDetailAddendumA) > ReturnDetailAddendumACount {
   327  			msg := fmt.Sprintf(msgBundleAddendum, len(rd.ReturnDetailAddendumA), ReturnDetailAddendumACount)
   328  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "ReturnDetailAddendumA", Msg: msg}
   329  		}
   330  		if len(rd.ReturnDetailAddendumB) > ReturnDetailAddendumBCount {
   331  			msg := fmt.Sprintf(msgBundleAddendum, len(rd.ReturnDetailAddendumB), ReturnDetailAddendumBCount)
   332  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "ReturnDetailAddendumB", Msg: msg}
   333  		}
   334  		if len(rd.ReturnDetailAddendumC) > ReturnDetailAddendumCCount {
   335  			msg := fmt.Sprintf(msgBundleAddendum, len(rd.ReturnDetailAddendumC), ReturnDetailAddendumCCount)
   336  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "ReturnDetailAddendumC", Msg: msg}
   337  		}
   338  		if len(rd.ReturnDetailAddendumD) > ReturnDetailAddendumDCount {
   339  			msg := fmt.Sprintf(msgBundleAddendum, len(rd.ReturnDetailAddendumD), ReturnDetailAddendumDCount)
   340  			return &BundleError{BundleSequenceNumber: bundleSequenceNumber, FieldName: "ReturnDetailAddendumD", Msg: msg}
   341  		}
   342  	}
   343  	return nil
   344  }