View Javadoc
1   /*
2    * Copyright 2008 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.module.purap.service.impl;
17  
18  import org.apache.commons.lang.ArrayUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.math.NumberUtils;
21  import org.apache.log4j.Logger;
22  import org.kuali.ole.module.purap.PurapConstants;
23  import org.kuali.ole.module.purap.PurapKeyConstants;
24  import org.kuali.ole.module.purap.PurapParameterConstants;
25  import org.kuali.ole.module.purap.batch.ElectronicInvoiceStep;
26  import org.kuali.ole.module.purap.businessobject.*;
27  import org.kuali.ole.module.purap.document.PurchaseOrderDocument;
28  import org.kuali.ole.module.purap.service.ElectronicInvoiceMatchingService;
29  import org.kuali.ole.module.purap.util.ElectronicInvoiceUtils;
30  import org.kuali.ole.module.purap.util.PurApItemUtils;
31  import org.kuali.ole.sys.context.SpringContext;
32  import org.kuali.ole.sys.service.TaxService;
33  import org.kuali.ole.sys.service.impl.OleParameterConstants;
34  import org.kuali.ole.vnd.businessobject.PurchaseOrderCostSource;
35  import org.kuali.ole.vnd.businessobject.VendorDetail;
36  import org.kuali.ole.vnd.document.service.VendorService;
37  import org.kuali.rice.core.api.datetime.DateTimeService;
38  import org.kuali.rice.core.api.util.type.KualiDecimal;
39  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
40  import org.kuali.rice.krad.service.BusinessObjectService;
41  import org.kuali.rice.krad.util.ObjectUtils;
42  
43  import java.math.BigDecimal;
44  import java.util.*;
45  
46  public class ElectronicInvoiceMatchingServiceImpl implements ElectronicInvoiceMatchingService {
47  
48      private Logger LOG = Logger.getLogger(ElectronicInvoiceMatchingServiceImpl.class);
49  
50      private Map<String, ElectronicInvoiceRejectReasonType> rejectReasonTypes;
51      private VendorService vendorService;
52      private TaxService taxService;
53      private DateTimeService dateTimeService;
54  
55      String upperVariancePercentString;
56      String lowerVariancePercentString;
57  
58      public void doMatchingProcess(ElectronicInvoiceOrderHolder orderHolder) {
59  
60          if (LOG.isInfoEnabled()) {
61              LOG.info("Matching process started");
62          }
63  
64          upperVariancePercentString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.SALES_TAX_UPPER_VARIANCE_PERCENT);
65          lowerVariancePercentString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.SALES_TAX_LOWER_VARIANCE_PERCENT);
66          ;
67  
68          try {
69              if (orderHolder.isValidateHeaderInformation()) {
70  
71                  validateHeaderInformation(orderHolder);
72  
73                  if (orderHolder.isInvoiceRejected()) {
74                      if (LOG.isInfoEnabled()) {
75                          LOG.info("Matching process failed at header validation");
76                      }
77                      return;
78                  }
79              }
80  
81              validateInvoiceDetails(orderHolder);
82  
83              if (orderHolder.isInvoiceRejected()) {
84                  if (LOG.isInfoEnabled()) {
85                      LOG.info("Matching process failed at order detail validation");
86                  }
87                  return;
88              }
89  
90          } catch (NumberFormatException e) {
91              if (LOG.isInfoEnabled()) {
92                  LOG.info("Matching process matching failed due to number format exception " + e.getMessage());
93              }
94              ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVALID_NUMBER_FORMAT, e.getMessage(), orderHolder.getFileName());
95              orderHolder.addInvoiceHeaderRejectReason(rejectReason);
96              return;
97          }
98  
99          if (LOG.isInfoEnabled()) {
100             LOG.info("Matching process ended successfully");
101         }
102     }
103 
104     protected void validateHeaderInformation(ElectronicInvoiceOrderHolder orderHolder) {
105 
106         String dunsField = PurapConstants.ElectronicInvoice.RejectDocumentFields.VENDOR_DUNS_NUMBER;
107         String applnResourceKeyName = PurapKeyConstants.ERROR_REJECT_INVALID_DUNS;
108 
109         if (StringUtils.isEmpty(orderHolder.getDunsNumber())) {
110             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_NOT_FOUND, null, orderHolder.getFileName());
111             orderHolder.addInvoiceHeaderRejectReason(rejectReason, dunsField, applnResourceKeyName);
112             return;
113         }
114 
115         if (orderHolder.isRejectDocumentHolder()) {
116             VendorDetail vendorDetail = SpringContext.getBean(VendorService.class).getVendorByDunsNumber(orderHolder.getDunsNumber());
117             if (vendorDetail == null) {
118                 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_INVALID, null, orderHolder.getFileName());
119                 orderHolder.addInvoiceHeaderRejectReason(rejectReason, dunsField, applnResourceKeyName);
120                 return;
121             }
122         } else {
123             if (orderHolder.getVendorHeaderId() == null && orderHolder.getVendorDetailId() == null) {
124                 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_INVALID, null, orderHolder.getFileName());
125                 orderHolder.addInvoiceHeaderRejectReason(rejectReason, dunsField, applnResourceKeyName);
126                 return;
127             }
128         }
129 
130         String invoiceNumberField = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_NUMBER;
131         if (!orderHolder.isInvoiceNumberAcceptIndicatorEnabled()) {
132             if (StringUtils.isEmpty(orderHolder.getInvoiceNumber())) {
133                 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_ID_EMPTY, null, orderHolder.getFileName());
134                 orderHolder.addInvoiceHeaderRejectReason(rejectReason, invoiceNumberField, PurapKeyConstants.ERROR_REJECT_INVOICE_NUMBER_EMPTY);
135                 return;
136             }
137         }
138 
139         String invoiceDateField = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE;
140 
141         if (StringUtils.isEmpty(orderHolder.getInvoiceDateString()) || orderHolder.getInvoiceDate() == null) {
142             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_INVALID, null, orderHolder.getFileName());
143             orderHolder.addInvoiceHeaderRejectReason(rejectReason, invoiceDateField, PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_INVALID);
144             return;
145         } else if (orderHolder.getInvoiceDate().after(dateTimeService.getCurrentDate())) {
146             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_GREATER, null, orderHolder.getFileName());
147             orderHolder.addInvoiceOrderRejectReason(rejectReason, invoiceDateField, PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_GREATER);
148             return;
149         }
150 
151         if (orderHolder.isInformationOnly()) {
152             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INFORMATION_ONLY, null, orderHolder.getFileName());
153             orderHolder.addInvoiceHeaderRejectReason(rejectReason);
154             return;
155         }
156 
157         validateSummaryAmounts(orderHolder);
158 
159         if (orderHolder.isInvoiceRejected()) {
160             return;
161         }
162 
163         validateItemTypes(orderHolder);
164 
165         if (orderHolder.isInvoiceRejected()) {
166             return;
167         }
168 
169     }
170 
171     protected void validateSummaryAmounts(ElectronicInvoiceOrderHolder orderHolder) {
172 
173         if (orderHolder.isRejectDocumentHolder()) {
174             /**
175              * If there are any rejects related to the summary, we're retaining it since 
176              * it's not possible to get the summary amount totals from the reject doc
177              */
178             return;
179         }
180 
181         ElectronicInvoiceDetailRequestSummary summary = orderHolder.getElectronicInvoice().getInvoiceDetailRequestSummary();
182 
183         boolean enableSalesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
184 
185         boolean salesTaxUsed = false;
186         PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
187         if (poDoc != null) {  // we handle bad PO's in the eInvoice later, so just skip this
188             List<PurApItem> items = PurApItemUtils.getAboveTheLineOnly(poDoc.getItems());
189             for (PurApItem item : items) {
190                 if (item.getItemType().isTaxableIndicator()) {
191                     salesTaxUsed = true;
192                     break;
193                 }
194             }
195 
196             boolean useTaxUsed = poDoc.isUseTaxIndicator();
197             enableSalesTaxInd &= (salesTaxUsed || useTaxUsed);
198 
199             BigDecimal summaryTaxAmount = summary.getInvoiceTaxAmount();
200             if (!enableSalesTaxInd) {
201                 // if sales tax is disabled, total tax amount shall be zero 
202                 if (summaryTaxAmount.compareTo(new BigDecimal(0)) != 0) {
203                     String extraDescription = "Summary Tax Amount:" + summaryTaxAmount;
204                     ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_EXISTS, extraDescription, orderHolder.getFileName());
205                     orderHolder.addInvoiceHeaderRejectReason(rejectReason);
206                 }
207             } else if (orderHolder.isTaxInLine()) {
208                 validateSummaryAmount(orderHolder, summaryTaxAmount, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_MISMATCH);
209             }
210         }
211 
212         if (orderHolder.isShippingInLine()) {
213             validateSummaryAmount(orderHolder, summary.getInvoiceShippingAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, PurapConstants.ElectronicInvoice.SHIPPING_SUMMARY_AMT_MISMATCH);
214         }
215 
216         if (orderHolder.isSpecialHandlingInLine()) {
217             validateSummaryAmount(orderHolder, summary.getInvoiceSpecialHandlingAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, PurapConstants.ElectronicInvoice.SPL_HANDLING_SUMMARY_AMT_MISMATCH);
218         }
219 
220         if (orderHolder.isDiscountInLine()) {
221             validateSummaryAmount(orderHolder, summary.getInvoiceDiscountAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, PurapConstants.ElectronicInvoice.DISCOUNT_SUMMARY_AMT_MISMATCH);
222         }
223 
224     }
225 
226     protected void validateSummaryAmount(ElectronicInvoiceOrderHolder orderHolder,
227                                          BigDecimal summaryAmount,
228                                          String invoiceLineItemTypeCode,
229                                          String rejectDescriptionCode) {
230 
231         BigDecimal lineItemTotalAmount = orderHolder.getElectronicInvoice().getFileTotalAmountForInLineItems(invoiceLineItemTypeCode);
232 
233 //        if (lineItemTotalAmount.compareTo(BigDecimal.ZERO) != 0) { // old way, but it's not needed
234         if ((lineItemTotalAmount.compareTo(summaryAmount)) != 0) {
235             String extraDescription = "Line Total Amount:" + lineItemTotalAmount + ",Summary Total Amount:" + summaryAmount;
236             ElectronicInvoiceRejectReason rejectReason = createRejectReason(rejectDescriptionCode, extraDescription, orderHolder.getFileName());
237             orderHolder.addInvoiceHeaderRejectReason(rejectReason);
238         }
239 //        }
240     }
241 
242     protected void validateItemTypes(ElectronicInvoiceOrderHolder orderHolder) {
243 
244         validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_ITEM);
245         validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX);
246         validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING);
247         validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING);
248         validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT);
249         validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_EXMT);
250 
251     }
252 
253     protected void validateItemMapping(ElectronicInvoiceOrderHolder orderHolder, String kualiItemTypeCode) {
254 
255         if (!orderHolder.isItemTypeAvailableInItemMapping(kualiItemTypeCode)) {
256             String extraDescription = kualiItemTypeCode;
257             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.ITEM_MAPPING_NOT_AVAILABLE, extraDescription, orderHolder.getFileName());
258             orderHolder.addInvoiceHeaderRejectReason(rejectReason);
259             return;
260         }
261 
262     }
263 
264     protected void validateInvoiceDetails(ElectronicInvoiceOrderHolder orderHolder) {
265 
266         validatePurchaseOrderMatch(orderHolder);
267 
268         if (orderHolder.isInvoiceRejected()) {
269             return;
270         }
271 
272         validateInvoiceItems(orderHolder);
273 
274         if (LOG.isInfoEnabled()) {
275             if (!orderHolder.isInvoiceRejected()) {
276                 LOG.info("Purchase order document match done successfully");
277             }
278         }
279     }
280 
281     protected void validatePurchaseOrderMatch(ElectronicInvoiceOrderHolder orderHolder) {
282 
283         String poIDFieldName = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_PO_ID;
284         String poID = orderHolder.getInvoicePurchaseOrderID();
285 
286         if (StringUtils.isEmpty(poID)) {
287             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ID_EMPTY, null, orderHolder.getFileName());
288             orderHolder.addInvoiceOrderRejectReason(rejectReason, poIDFieldName, PurapKeyConstants.ERROR_REJECT_INVOICE_POID_EMPTY);
289             return;
290         }
291 
292         String extraDesc = "Invoice Order ID:" + poID;
293 
294         if (!NumberUtils.isDigits(poID)) {
295             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ID_INVALID_FORMAT, extraDesc, orderHolder.getFileName());
296             orderHolder.addInvoiceOrderRejectReason(rejectReason, poIDFieldName, PurapKeyConstants.ERROR_REJECT_INVOICE_POID_INVALID);
297             return;
298         }
299 
300         PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
301 
302         if (poDoc == null) {
303             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_NOT_EXISTS, extraDesc, orderHolder.getFileName());
304             orderHolder.addInvoiceOrderRejectReason(rejectReason, poIDFieldName, PurapKeyConstants.ERROR_REJECT_INVOICE__PO_NOT_EXISTS);
305             return;
306         }
307 
308         if (poDoc.getVendorHeaderGeneratedIdentifier() == null ||
309                 poDoc.getVendorDetailAssignedIdentifier() == null ||
310                 !(poDoc.getVendorHeaderGeneratedIdentifier().equals(orderHolder.getVendorHeaderId()) &&
311                         poDoc.getVendorDetailAssignedIdentifier().equals(orderHolder.getVendorDetailId()))) {
312             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_VENDOR_NOT_MATCHES_WITH_INVOICE_VENDOR, null, orderHolder.getFileName());
313             orderHolder.addInvoiceOrderRejectReason(rejectReason);
314             return;
315         }
316 
317     }
318 
319     protected void validateInvoiceItems(ElectronicInvoiceOrderHolder orderHolder) {
320 
321         Set poLineNumbers = new HashSet();
322 
323         ElectronicInvoiceItemHolder[] itemHolders = orderHolder.getItems();
324         if (itemHolders != null) {
325             for (int i = 0; i < itemHolders.length; i++) {
326                 validateInvoiceItem(itemHolders[i], poLineNumbers);
327             }
328         }
329     }
330 
331     protected void validateInvoiceItem(ElectronicInvoiceItemHolder itemHolder,
332                                        Set poLineNumbers) {
333 
334         PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
335         ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
336 
337         if (poItem == null) {
338             String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
339             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.NO_MATCHING_PO_ITEM, extraDescription, orderHolder.getFileName());
340             orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_INVOICE__ITEM_NOMATCH);
341             return;
342         }
343 
344         if (poLineNumbers.contains(itemHolder.getInvoiceItemLineNumber())) {
345             String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
346             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUPLIATE_INVOICE_LINE_ITEM, extraDescription, orderHolder.getFileName());
347             orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_PO_ITEM_DUPLICATE);
348             return;
349         } else {
350             poLineNumbers.add(itemHolder.getInvoiceItemLineNumber());
351         }
352 
353         if (!poItem.isItemActiveIndicator()) {
354             String extraDescription = "PO Item Line Number:" + poItem.getItemLineNumber();
355             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INACTIVE_LINE_ITEM, extraDescription, orderHolder.getFileName());
356             orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_PO_ITEM_INACTIVE);
357             return;
358         }
359 
360         if (!itemHolder.isCatalogNumberAcceptIndicatorEnabled()) {
361             validateCatalogNumber(itemHolder);
362             if (orderHolder.isInvoiceRejected()) {
363                 return;
364             }
365         }
366 
367         if (!itemHolder.isUnitOfMeasureAcceptIndicatorEnabled()) {
368             if (!StringUtils.equals(poItem.getItemUnitOfMeasureCode(), itemHolder.getInvoiceItemUnitOfMeasureCode())) {
369                 String extraDescription = "Invoice UOM:" + itemHolder.getInvoiceItemUnitOfMeasureCode() + ", PO UOM:" + poItem.getItemUnitOfMeasureCode();
370                 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.UNIT_OF_MEASURE_MISMATCH, extraDescription, orderHolder.getFileName());
371                 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UOM, PurapKeyConstants.ERROR_REJECT_UOM_MISMATCH);
372                 return;
373             }
374         }
375 
376         validateUnitPrice(itemHolder);
377 
378         if (orderHolder.isInvoiceRejected()) {
379             return;
380         }
381 
382         validateSalesTax(itemHolder);
383 
384         if (orderHolder.isInvoiceRejected()) {
385             return;
386         }
387 
388         if (poItem.getItemQuantity() != null) {
389             validateQtyBasedItem(itemHolder);
390         } else {
391             validateNonQtyBasedItem(itemHolder);
392         }
393 
394     }
395 
396     protected void validateCatalogNumber(ElectronicInvoiceItemHolder itemHolder) {
397 
398         PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
399         ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
400 
401         String invoiceCatalogNumberStripped = itemHolder.getCatalogNumberStripped();
402         String poCatalogNumberStripped = ElectronicInvoiceUtils.stripSplChars(poItem.getItemCatalogNumber());
403 
404         /**
405          * If Catalog number in invoice and po are not empty, create reject reason if it doesn't match 
406          */
407         if (StringUtils.isNotBlank(invoiceCatalogNumberStripped) &&
408                 StringUtils.isNotBlank(poCatalogNumberStripped)) {
409 
410             if (!StringUtils.equals(poCatalogNumberStripped, invoiceCatalogNumberStripped)) {
411 
412                 String extraDescription = "Invoice Catalog No:" + invoiceCatalogNumberStripped + ", PO Catalog No:" + poCatalogNumberStripped;
413                 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.CATALOG_NUMBER_MISMATCH, extraDescription, orderHolder.getFileName());
414                 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_CATALOG_NUMBER, PurapKeyConstants.ERROR_REJECT_CATALOG_MISMATCH);
415             }
416 
417         } else {
418 
419             /**
420              * If catalog number is empty in PO/&Invoice, check whether the catalog check is required for the requisition source.
421              * If exists in param, create reject reason.
422              * If not exists, continue with UOM and unit price match.
423              */
424             String reqSourceRequiringCatalogMatch = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.REQUISITION_SOURCES_REQUIRING_CATALOG_MATCHING);
425             String requisitionSourceCodeInPO = orderHolder.getPurchaseOrderDocument().getRequisitionSourceCode();
426 
427             if (StringUtils.isNotEmpty(reqSourceRequiringCatalogMatch)) {
428                 String[] requisitionSourcesFromParam = StringUtils.split(reqSourceRequiringCatalogMatch, ';');
429                 if (ArrayUtils.contains(requisitionSourcesFromParam, requisitionSourceCodeInPO)) {
430                     String extraDescription = "Invoice Catalog No:" + invoiceCatalogNumberStripped + ", PO Catalog No:" + poItem.getItemCatalogNumber();
431                     ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.CATALOG_NUMBER_MISMATCH, extraDescription, orderHolder.getFileName());
432                     orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_CATALOG_NUMBER, PurapKeyConstants.ERROR_REJECT_CATALOG_MISMATCH);
433                 }
434             }
435         }
436     }
437 
438     protected void validateQtyBasedItem(ElectronicInvoiceItemHolder itemHolder) {
439 
440         PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
441 
442         String fileName = itemHolder.getInvoiceOrderHolder().getFileName();
443         ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
444 
445         if (KualiDecimal.ZERO.compareTo(poItem.getItemOutstandingEncumberedQuantity()) >= 0) {
446             //we have no quantity left encumbered on the po item
447             String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
448             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.OUTSTANDING_ENCUMBERED_QTY_AVAILABLE, extraDescription, orderHolder.getFileName());
449             orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY, PurapKeyConstants.ERROR_REJECT_POITEM_OUTSTANDING_QTY);
450             return;
451         }
452 
453         if (itemHolder.getInvoiceItemQuantity() == null) {
454             //we have quantity entered on the PO Item but the Invoice has no quantity
455             String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
456             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_QTY_EMPTY, extraDescription, orderHolder.getFileName());
457             orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY, PurapKeyConstants.ERROR_REJECT_POITEM_INVOICE_QTY_EMPTY);
458             return;
459         } else {
460 
461             if (!itemHolder.getInvoiceOrderHolder().getPurchaseOrderDocument().isReceivingDocumentRequiredIndicator()) {
462 
463                 if ((itemHolder.getInvoiceItemQuantity().compareTo(poItem.getItemOutstandingEncumberedQuantity().bigDecimalValue())) > 0) {
464                     //we have more quantity on the e-invoice than left outstanding encumbered on the PO item
465                     String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
466                     ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ITEM_QTY_LESSTHAN_INVOICE_ITEM_QTY, extraDescription, orderHolder.getFileName());
467                     orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY, PurapKeyConstants.ERROR_REJECT_POITEM_LESS_OUTSTANDING_QTY);
468                     return;
469                 }
470             }
471         }
472 
473     }
474 
475     protected void validateNonQtyBasedItem(ElectronicInvoiceItemHolder itemHolder) {
476 
477         PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
478 
479         String fileName = itemHolder.getInvoiceOrderHolder().getFileName();
480         ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
481 
482         if ((KualiDecimal.ZERO.compareTo(poItem.getItemOutstandingEncumberedAmount())) >= 0) {
483             //we have no dollars left encumbered on the po item
484             String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
485             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.OUTSTANDING_ENCUMBERED_AMT_AVAILABLE, extraDescription, orderHolder.getFileName());
486             orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_POITEM_OUTSTANDING_EMCUMBERED_AMOUNT);
487             return;
488         } else {
489             //we have encumbered dollars left on PO
490             if (((itemHolder.getInvoiceItemSubTotalAmount().setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)).compareTo(poItem.getItemOutstandingEncumberedAmount().bigDecimalValue())) > 0) {
491                 String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
492                 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ITEM_AMT_LESSTHAN_INVOICE_ITEM_AMT, extraDescription, orderHolder.getFileName());
493                 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER, PurapKeyConstants.ERROR_REJECT_POITEM_LESS_OUTSTANDING_EMCUMBERED_AMOUNT);
494                 return;
495             }
496 
497         }
498     }
499 
500     protected void validateUnitPrice(ElectronicInvoiceItemHolder itemHolder) {
501 
502         PurchaseOrderCostSource costSource = itemHolder.getInvoiceOrderHolder().getPurchaseOrderDocument().getPurchaseOrderCostSource();
503         PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
504         ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
505 
506         String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
507 
508         BigDecimal actualVariance = itemHolder.getInvoiceItemUnitPrice().subtract(poItem.getItemUnitPrice());
509 
510         BigDecimal lowerPercentage = null;
511         if (costSource.getItemUnitPriceLowerVariancePercent() != null) {
512             //Checking for lower variance
513             lowerPercentage = costSource.getItemUnitPriceLowerVariancePercent();
514         } else {
515             //If the cost source itemUnitPriceLowerVariancePercent is null then
516             //we'll use the exact match (100%).
517             lowerPercentage = new BigDecimal(100);
518         }
519 
520         BigDecimal lowerAcceptableVariance = (lowerPercentage.divide(new BigDecimal(100))).multiply(poItem.getItemUnitPrice()).negate();
521 
522         if (lowerAcceptableVariance.compareTo(actualVariance) > 0) {
523             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_AMT_LESSER_THAN_LOWER_VARIANCE, extraDescription, orderHolder.getFileName());
524             orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UNIT_PRICE, PurapKeyConstants.ERROR_REJECT_UNITPRICE_LOWERVARIANCE);
525         }
526 
527         BigDecimal upperPercentage = null;
528 
529         if (costSource.getItemUnitPriceUpperVariancePercent() != null) {
530             //Checking for upper variance
531             upperPercentage = costSource.getItemUnitPriceUpperVariancePercent();
532         } else {
533             //If the cost source itemUnitPriceLowerVariancePercent is null then
534             //we'll use the exact match (100%).
535             upperPercentage = new BigDecimal(100);
536         }
537         BigDecimal upperAcceptableVariance = (upperPercentage.divide(new BigDecimal(100))).multiply(poItem.getItemUnitPrice());
538 
539         if (upperAcceptableVariance.compareTo(actualVariance) < 0) {
540             ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_AMT_GREATER_THAN_UPPER_VARIANCE, extraDescription, orderHolder.getFileName());
541             orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UNIT_PRICE, PurapKeyConstants.ERROR_REJECT_UNITPRICE_UPPERVARIANCE);
542         }
543 
544     }
545 
546     protected void validateSalesTax(ElectronicInvoiceItemHolder itemHolder) {
547 
548         if (LOG.isInfoEnabled()) {
549             LOG.info("Validating sales tax");
550         }
551 
552         ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
553         PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
554         KualiDecimal invoiceSalesTaxAmount = new KualiDecimal(itemHolder.getTaxAmount());
555 
556         boolean enableSalesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
557 
558         boolean salesTaxUsed = false;
559         PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
560         List<PurApItem> items = PurApItemUtils.getAboveTheLineOnly(poDoc.getItems());
561         for (PurApItem item : items) {
562             if (item.getItemType().isTaxableIndicator()) {
563                 salesTaxUsed = true;
564                 break;
565             }
566         }
567         boolean useTaxUsed = poDoc.isUseTaxIndicator();
568         enableSalesTaxInd &= (poItem.getItemType().isTaxableIndicator() && (salesTaxUsed || useTaxUsed));
569 
570         if (LOG.isInfoEnabled()) {
571             LOG.info("Sales Tax Enable Indicator - " + enableSalesTaxInd);
572             LOG.info("Invoice item tax amount - " + invoiceSalesTaxAmount);
573         }
574         if (!enableSalesTaxInd) {
575             // if sales tax is disabled, item tax amount shall be zero 
576             if (invoiceSalesTaxAmount.compareTo(KualiDecimal.ZERO) != 0) {
577                 String extraDescription = "Item Tax Amount:" + invoiceSalesTaxAmount;
578                 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_EXISTS, extraDescription, orderHolder.getFileName());
579                 orderHolder.addInvoiceHeaderRejectReason(rejectReason);
580             }
581             return;
582         }
583 
584         // For reject doc, trans date should be the einvoice processed date.
585         java.sql.Date transTaxDate = itemHolder.getInvoiceOrderHolder().getInvoiceProcessedDate();
586         String deliveryPostalCode = poItem.getPurchaseOrder().getDeliveryPostalCode();
587         KualiDecimal extendedPrice = new KualiDecimal(getExtendedPrice(itemHolder).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR));
588 
589         KualiDecimal salesTaxAmountCalculated = taxService.getTotalSalesTaxAmount(transTaxDate, deliveryPostalCode, extendedPrice);
590         KualiDecimal actualVariance = invoiceSalesTaxAmount.subtract(salesTaxAmountCalculated);
591 
592         if (LOG.isInfoEnabled()) {
593             LOG.info("Sales Tax Upper Variance param - " + upperVariancePercentString);
594             LOG.info("Sales Tax Lower Variance param - " + lowerVariancePercentString);
595             LOG.info("Trans date (from invoice/rejectdoc) - " + transTaxDate);
596             LOG.info("Delivery Postal Code - " + deliveryPostalCode);
597             LOG.info("Extended price - " + extendedPrice);
598             LOG.info("Sales Tax amount (from sales tax service) - " + salesTaxAmountCalculated);
599         }
600 
601         if (StringUtils.isNotEmpty(upperVariancePercentString)) {
602 
603             KualiDecimal upperVariancePercent = new KualiDecimal(upperVariancePercentString);
604             BigDecimal upperAcceptableVariance = (upperVariancePercent.divide(new KualiDecimal(100))).multiply(salesTaxAmountCalculated).bigDecimalValue();
605 
606             if (upperAcceptableVariance.compareTo(actualVariance.bigDecimalValue()) < 0) {
607                 ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_GREATER_THAN_UPPER_VARIANCE, null, orderHolder.getFileName());
608                 orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT, PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_UPPERVARIANCE);
609                 return;
610             }
611 
612         }
613 
614         if (StringUtils.isNotEmpty(lowerVariancePercentString)) {
615 
616             KualiDecimal lowerVariancePercent = new KualiDecimal(lowerVariancePercentString);
617             BigDecimal lowerAcceptableVariance = (lowerVariancePercent.divide(new KualiDecimal(100))).multiply(salesTaxAmountCalculated).bigDecimalValue().negate();
618 
619             if (lowerAcceptableVariance.compareTo(BigDecimal.ZERO) >= 0 &&
620                     actualVariance.compareTo(KualiDecimal.ZERO) >= 0) {
621                 if (actualVariance.bigDecimalValue().compareTo(lowerAcceptableVariance) > 0) {
622                     ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_LESSER_THAN_LOWER_VARIANCE, null, orderHolder.getFileName());
623                     orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT, PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_LOWERVARIANCE);
624                 }
625             } else {
626                 if (actualVariance.bigDecimalValue().compareTo(lowerAcceptableVariance) < 0) {
627                     ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_LESSER_THAN_LOWER_VARIANCE, null, orderHolder.getFileName());
628                     orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT, PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_LOWERVARIANCE);
629                 }
630             }
631         }
632 
633     }
634 
635 
636     //Copied from PurApItemBase.calculateExtendedPrice
637     protected BigDecimal getExtendedPrice(ElectronicInvoiceItemHolder itemHolder) {
638         if (itemHolder.getPurchaseOrderItem().getItemType().isAmountBasedGeneralLedgerIndicator()) {
639             // SERVICE ITEM: return unit price as extended price
640             return itemHolder.getUnitPrice();
641         } else if (ObjectUtils.isNotNull(itemHolder.getQuantity())) { // qty wont be null since it's defined as a reqd field in xsd
642             BigDecimal calcExtendedPrice = itemHolder.getUnitPrice().multiply(itemHolder.getQuantity());
643             // ITEM TYPE (qty driven): return (unitPrice x qty)
644             return calcExtendedPrice;
645         }
646         return BigDecimal.ZERO;
647     }
648 
649     public ElectronicInvoiceRejectReason createRejectReason(String rejectReasonTypeCode, String extraDescription, String fileName) {
650 
651         ElectronicInvoiceRejectReasonType rejectReasonType = getElectronicInvoiceRejectReasonType(rejectReasonTypeCode);
652         ElectronicInvoiceRejectReason eInvoiceRejectReason = new ElectronicInvoiceRejectReason();
653 
654         if (rejectReasonType == null) {
655             throw new NullPointerException("Reject reason type for " + rejectReasonTypeCode + " not available in DB");
656         }
657         eInvoiceRejectReason.setInvoiceFileName(fileName);
658         eInvoiceRejectReason.setInvoiceRejectReasonTypeCode(rejectReasonTypeCode);
659 
660         if (StringUtils.isNotEmpty(extraDescription)) {
661             eInvoiceRejectReason.setInvoiceRejectReasonDescription(rejectReasonType.getInvoiceRejectReasonTypeDescription() + " (" + extraDescription + ")");
662         } else {
663             eInvoiceRejectReason.setInvoiceRejectReasonDescription(rejectReasonType.getInvoiceRejectReasonTypeDescription());
664         }
665 
666         return eInvoiceRejectReason;
667 
668     }
669 
670     public ElectronicInvoiceRejectReasonType getElectronicInvoiceRejectReasonType(String rejectReasonTypeCode) {
671         if (rejectReasonTypes == null) {
672             rejectReasonTypes = getElectronicInvoiceRejectReasonTypes();
673         }
674         return rejectReasonTypes.get(rejectReasonTypeCode);
675     }
676 
677     protected Map<String, ElectronicInvoiceRejectReasonType> getElectronicInvoiceRejectReasonTypes() {
678 
679         Collection<ElectronicInvoiceRejectReasonType> collection = SpringContext.getBean(BusinessObjectService.class).findAll(ElectronicInvoiceRejectReasonType.class);
680         Map rejectReasonTypesMap = new HashMap<String, ElectronicInvoiceRejectReasonType>();
681 
682         if (collection != null &&
683                 collection.size() > 0) {
684             ElectronicInvoiceRejectReasonType[] rejectReasonTypesArr = new ElectronicInvoiceRejectReasonType[collection.size()];
685             collection.toArray(rejectReasonTypesArr);
686             for (int i = 0; i < rejectReasonTypesArr.length; i++) {
687                 rejectReasonTypesMap.put(rejectReasonTypesArr[i].getInvoiceRejectReasonTypeCode(), rejectReasonTypesArr[i]);
688             }
689         }
690 
691         return rejectReasonTypesMap;
692     }
693 
694     public void setVendorService(VendorService vendorService) {
695         this.vendorService = vendorService;
696     }
697 
698     public void setTaxService(TaxService taxService) {
699         this.taxService = taxService;
700     }
701 
702     public void setDateTimeService(DateTimeService dateTimeService) {
703         this.dateTimeService = dateTimeService;
704     }
705 
706 }