/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.purap.service.impl;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses;
import org.kuali.kfs.module.purap.PurapKeyConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.batch.ElectronicInvoiceStep;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoice;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceDetailRequestSummary;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceRejectReason;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceRejectReasonType;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.module.purap.service.ElectronicInvoiceMatchingService;
import org.kuali.kfs.module.purap.util.ElectronicInvoiceUtils;
import org.kuali.kfs.module.purap.util.PurApItemUtils;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.TaxService;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.kfs.vnd.businessobject.PurchaseOrderCostSource;
import org.kuali.kfs.vnd.businessobject.VendorDetail;
import org.kuali.kfs.vnd.document.service.VendorService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.ObjectUtils;
public class ElectronicInvoiceMatchingServiceImpl implements ElectronicInvoiceMatchingService {
private Logger LOG = Logger.getLogger(ElectronicInvoiceMatchingServiceImpl.class);
private Map<String,ElectronicInvoiceRejectReasonType> rejectReasonTypes;
private VendorService vendorService;
private TaxService taxService;
private DateTimeService dateTimeService;
String upperVariancePercentString;
String lowerVariancePercentString;
@Override
public void doMatchingProcess(ElectronicInvoiceOrderHolder orderHolder) {
if (LOG.isInfoEnabled()){
LOG.info("Matching process started");
}
upperVariancePercentString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.SALES_TAX_UPPER_VARIANCE_PERCENT);
lowerVariancePercentString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.SALES_TAX_LOWER_VARIANCE_PERCENT);;
try {
if (orderHolder.isValidateHeaderInformation()) {
validateHeaderInformation(orderHolder);
if (orderHolder.isInvoiceRejected()) {
if (LOG.isInfoEnabled()){
LOG.info("Matching process failed at header validation");
}
return;
}
}
validateInvoiceDetails(orderHolder);
if (orderHolder.isInvoiceRejected()) {
if (LOG.isInfoEnabled()){
LOG.info("Matching process failed at order detail validation");
}
return;
}
}
catch (NumberFormatException e) {
if (LOG.isInfoEnabled()){
LOG.info("Matching process matching failed due to number format exception " + e.getMessage());
}
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVALID_NUMBER_FORMAT, e.getMessage(), orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason);
return;
}
if (LOG.isInfoEnabled()){
LOG.info("Matching process ended successfully");
}
}
protected void validateHeaderInformation(ElectronicInvoiceOrderHolder orderHolder){
String dunsField = PurapConstants.ElectronicInvoice.RejectDocumentFields.VENDOR_DUNS_NUMBER;
String applnResourceKeyName = PurapKeyConstants.ERROR_REJECT_INVALID_DUNS;
if (StringUtils.isEmpty(orderHolder.getDunsNumber())){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_NOT_FOUND,null,orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason,dunsField,applnResourceKeyName);
return;
}
if (orderHolder.isRejectDocumentHolder()){
VendorDetail vendorDetail = SpringContext.getBean(VendorService.class).getVendorByDunsNumber(orderHolder.getDunsNumber());
if (vendorDetail == null){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_INVALID, null, orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason,dunsField,applnResourceKeyName);
return;
}
}else{
if (orderHolder.getVendorHeaderId() == null && orderHolder.getVendorDetailId() == null) {
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUNS_INVALID, null, orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason,dunsField,applnResourceKeyName);
return;
}
}
String invoiceNumberField = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_NUMBER;
if (!orderHolder.isInvoiceNumberAcceptIndicatorEnabled()){
if (StringUtils.isEmpty(orderHolder.getInvoiceNumber())){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_ID_EMPTY,null,orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason,invoiceNumberField,PurapKeyConstants.ERROR_REJECT_INVOICE_NUMBER_EMPTY);
return;
}
}
String invoiceDateField = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE;
if (StringUtils.isEmpty(orderHolder.getInvoiceDateString()) || orderHolder.getInvoiceDate() == null){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_INVALID,null,orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason,invoiceDateField,PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_INVALID);
return;
}else if (orderHolder.getInvoiceDate().after(dateTimeService.getCurrentDate())) {
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_GREATER,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,invoiceDateField,PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_GREATER);
return;
}
if (orderHolder.isInformationOnly()){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INFORMATION_ONLY,null,orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason);
return;
}
validateSummaryAmounts(orderHolder);
if (orderHolder.isInvoiceRejected()) {
return;
}
validateItemTypes(orderHolder);
if (orderHolder.isInvoiceRejected()) {
return;
}
}
protected void validateSummaryAmounts(ElectronicInvoiceOrderHolder orderHolder) {
if (orderHolder.isRejectDocumentHolder()){
/**
* If there are any rejects related to the summary, we're retaining it since
* it's not possible to get the summary amount totals from the reject doc
*/
return;
}
ElectronicInvoiceDetailRequestSummary summary = orderHolder.getElectronicInvoice().getInvoiceDetailRequestSummary();
boolean enableSalesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
boolean salesTaxUsed = false;
PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
if (poDoc != null) { // we handle bad PO's in the eInvoice later, so just skip this
List<PurApItem> items = PurApItemUtils.getAboveTheLineOnly(poDoc.getItems());
for (PurApItem item : items) {
if (item.getItemType().isTaxableIndicator()) {
salesTaxUsed = true;
break;
}
}
boolean useTaxUsed = poDoc.isUseTaxIndicator();
enableSalesTaxInd &= (salesTaxUsed || useTaxUsed);
BigDecimal summaryTaxAmount = summary.getInvoiceTaxAmount();
if (!enableSalesTaxInd) {
// if sales tax is disabled, total tax amount shall be zero
if (summaryTaxAmount.compareTo(new BigDecimal(0)) != 0) {
String extraDescription = "Summary Tax Amount:" + summaryTaxAmount;
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_EXISTS, extraDescription, orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason);
}
}
else if (orderHolder.isTaxInLine()) {
validateSummaryAmount(orderHolder, summaryTaxAmount, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_MISMATCH);
}
}
if (orderHolder.isShippingInLine()) {
validateSummaryAmount(orderHolder, summary.getInvoiceShippingAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, PurapConstants.ElectronicInvoice.SHIPPING_SUMMARY_AMT_MISMATCH);
}
if (orderHolder.isSpecialHandlingInLine()) {
validateSummaryAmount(orderHolder, summary.getInvoiceSpecialHandlingAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, PurapConstants.ElectronicInvoice.SPL_HANDLING_SUMMARY_AMT_MISMATCH);
}
if (orderHolder.isDiscountInLine()) {
validateSummaryAmount(orderHolder, summary.getInvoiceDiscountAmount(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, PurapConstants.ElectronicInvoice.DISCOUNT_SUMMARY_AMT_MISMATCH);
}
}
protected void validateSummaryAmount(ElectronicInvoiceOrderHolder orderHolder,
BigDecimal summaryAmount,
String invoiceLineItemTypeCode,
String rejectDescriptionCode) {
BigDecimal lineItemTotalAmount = orderHolder.getElectronicInvoice().getFileTotalAmountForInLineItems(invoiceLineItemTypeCode);
// if (lineItemTotalAmount.compareTo(BigDecimal.ZERO) != 0) { // old way, but it's not needed
if ((lineItemTotalAmount.compareTo(summaryAmount)) != 0) {
String extraDescription = "Line Total Amount:" + lineItemTotalAmount + ",Summary Total Amount:" + summaryAmount;
ElectronicInvoiceRejectReason rejectReason = createRejectReason(rejectDescriptionCode, extraDescription, orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason);
}
// }
}
protected void validateItemTypes(ElectronicInvoiceOrderHolder orderHolder) {
validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_ITEM);
validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX);
validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING);
validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING);
validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT);
validateItemMapping(orderHolder, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_EXMT);
}
protected void validateItemMapping(ElectronicInvoiceOrderHolder orderHolder, String kualiItemTypeCode) {
if (!orderHolder.isItemTypeAvailableInItemMapping(kualiItemTypeCode)) {
String extraDescription = kualiItemTypeCode;
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.ITEM_MAPPING_NOT_AVAILABLE, extraDescription, orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason);
return;
}
}
protected void validateInvoiceDetails(ElectronicInvoiceOrderHolder orderHolder){
validatePurchaseOrderMatch(orderHolder);
if (orderHolder.isInvoiceRejected()){
return;
}
validateInvoiceItems(orderHolder);
if (LOG.isInfoEnabled()){
if (!orderHolder.isInvoiceRejected()){
LOG.info("Purchase order document match done successfully");
}
}
}
protected void validatePurchaseOrderMatch(ElectronicInvoiceOrderHolder orderHolder){
String poIDFieldName = PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_PO_ID;
String poID = orderHolder.getInvoicePurchaseOrderID();
if (StringUtils.isEmpty(poID)){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ID_EMPTY,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,poIDFieldName,PurapKeyConstants.ERROR_REJECT_INVOICE_POID_EMPTY);
return;
}
String extraDesc = "Invoice Order ID:" + poID;
if (!NumberUtils.isDigits(poID)){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ID_INVALID_FORMAT,extraDesc,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,poIDFieldName,PurapKeyConstants.ERROR_REJECT_INVOICE_POID_INVALID);
return;
}
PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
if (poDoc == null){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_NOT_EXISTS,extraDesc,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,poIDFieldName,PurapKeyConstants.ERROR_REJECT_INVOICE__PO_NOT_EXISTS);
return;
}
if (!poDoc.getApplicationDocumentStatus().equals(PurchaseOrderStatuses.APPDOC_OPEN)) {
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_NOT_OPEN,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,poIDFieldName,PurapKeyConstants.ERROR_REJECT_INVOICE_PO_CLOSED);
return;
}
if (poDoc.getVendorHeaderGeneratedIdentifier() == null ||
poDoc.getVendorDetailAssignedIdentifier() == null ||
!(poDoc.getVendorHeaderGeneratedIdentifier().equals(orderHolder.getVendorHeaderId()) &&
poDoc.getVendorDetailAssignedIdentifier().equals(orderHolder.getVendorDetailId()))){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_VENDOR_NOT_MATCHES_WITH_INVOICE_VENDOR,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason);
return;
}
}
protected void validateInvoiceItems(ElectronicInvoiceOrderHolder orderHolder){
Set poLineNumbers = new HashSet();
ElectronicInvoiceItemHolder[] itemHolders = orderHolder.getItems();
if (itemHolders != null){
for (int i = 0; i < itemHolders.length; i++) {
validateInvoiceItem(itemHolders[i],poLineNumbers);
}
}
}
protected void validateInvoiceItem(ElectronicInvoiceItemHolder itemHolder,
Set poLineNumbers){
PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
if (poItem == null){
String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.NO_MATCHING_PO_ITEM,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER,PurapKeyConstants.ERROR_REJECT_INVOICE__ITEM_NOMATCH);
return;
}
if (poLineNumbers.contains(itemHolder.getInvoiceItemLineNumber())){
String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.DUPLIATE_INVOICE_LINE_ITEM,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER,PurapKeyConstants.ERROR_REJECT_PO_ITEM_DUPLICATE);
return;
}else{
poLineNumbers.add(itemHolder.getInvoiceItemLineNumber());
}
if (!poItem.isItemActiveIndicator()){
String extraDescription = "PO Item Line Number:" + poItem.getItemLineNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INACTIVE_LINE_ITEM,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER,PurapKeyConstants.ERROR_REJECT_PO_ITEM_INACTIVE);
return;
}
if (!itemHolder.isCatalogNumberAcceptIndicatorEnabled()){
validateCatalogNumber(itemHolder);
if (orderHolder.isInvoiceRejected()){
return;
}
}
if (!itemHolder.isUnitOfMeasureAcceptIndicatorEnabled()){
if (!StringUtils.equals(poItem.getItemUnitOfMeasureCode(), itemHolder.getInvoiceItemUnitOfMeasureCode())){
String extraDescription = "Invoice UOM:" + itemHolder.getInvoiceItemUnitOfMeasureCode() + ", PO UOM:" + poItem.getItemUnitOfMeasureCode();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.UNIT_OF_MEASURE_MISMATCH,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UOM,PurapKeyConstants.ERROR_REJECT_UOM_MISMATCH);
return;
}
}
validateUnitPrice(itemHolder);
if (orderHolder.isInvoiceRejected()){
return;
}
validateSalesTax(itemHolder);
if (orderHolder.isInvoiceRejected()){
return;
}
if (poItem.getItemQuantity() != null) {
validateQtyBasedItem(itemHolder);
}else{
validateNonQtyBasedItem(itemHolder);
}
}
protected void validateCatalogNumber(ElectronicInvoiceItemHolder itemHolder){
PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
String invoiceCatalogNumberStripped = itemHolder.getCatalogNumberStripped();
String poCatalogNumberStripped = ElectronicInvoiceUtils.stripSplChars(poItem.getItemCatalogNumber());
/**
* If Catalog number in invoice and po are not empty, create reject reason if it doesn't match
*/
if (StringUtils.isNotBlank(invoiceCatalogNumberStripped) &&
StringUtils.isNotBlank(poCatalogNumberStripped)){
if (!StringUtils.equals(poCatalogNumberStripped, invoiceCatalogNumberStripped)){
String extraDescription = "Invoice Catalog No:" + invoiceCatalogNumberStripped + ", PO Catalog No:" + poCatalogNumberStripped;
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.CATALOG_NUMBER_MISMATCH,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_CATALOG_NUMBER,PurapKeyConstants.ERROR_REJECT_CATALOG_MISMATCH);
}
}else{
/**
* If catalog number is empty in PO/&Invoice, check whether the catalog check is required for the requisition source.
* If exists in param, create reject reason.
* If not exists, continue with UOM and unit price match.
*/
String reqSourceRequiringCatalogMatch = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.REQUISITION_SOURCES_REQUIRING_CATALOG_MATCHING);
String requisitionSourceCodeInPO = orderHolder.getPurchaseOrderDocument().getRequisitionSourceCode();
if (StringUtils.isNotEmpty(reqSourceRequiringCatalogMatch)){
String[] requisitionSourcesFromParam = StringUtils.split(reqSourceRequiringCatalogMatch,';');
if (ArrayUtils.contains(requisitionSourcesFromParam, requisitionSourceCodeInPO)){
String extraDescription = "Invoice Catalog No:" + invoiceCatalogNumberStripped + ", PO Catalog No:" + poItem.getItemCatalogNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.CATALOG_NUMBER_MISMATCH,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_CATALOG_NUMBER,PurapKeyConstants.ERROR_REJECT_CATALOG_MISMATCH);
}
}
}
}
protected void validateQtyBasedItem(ElectronicInvoiceItemHolder itemHolder){
PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
String fileName = itemHolder.getInvoiceOrderHolder().getFileName();
ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
if (KualiDecimal.ZERO.compareTo(poItem.getItemOutstandingEncumberedQuantity()) >= 0) {
//we have no quantity left encumbered on the po item
String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.OUTSTANDING_ENCUMBERED_QTY_AVAILABLE,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY,PurapKeyConstants.ERROR_REJECT_POITEM_OUTSTANDING_QTY);
return;
}
if (itemHolder.getInvoiceItemQuantity() == null){
//we have quantity entered on the PO Item but the Invoice has no quantity
String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_QTY_EMPTY,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY,PurapKeyConstants.ERROR_REJECT_POITEM_INVOICE_QTY_EMPTY);
return;
}else{
if(!itemHolder.getInvoiceOrderHolder().getPurchaseOrderDocument().isReceivingDocumentRequiredIndicator()){
if ((itemHolder.getInvoiceItemQuantity().compareTo(poItem.getItemOutstandingEncumberedQuantity().bigDecimalValue())) > 0) {
//we have more quantity on the e-invoice than left outstanding encumbered on the PO item
String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ITEM_QTY_LESSTHAN_INVOICE_ITEM_QTY,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_QUANTITY,PurapKeyConstants.ERROR_REJECT_POITEM_LESS_OUTSTANDING_QTY);
return;
}
}
}
}
protected void validateNonQtyBasedItem(ElectronicInvoiceItemHolder itemHolder){
PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
String fileName = itemHolder.getInvoiceOrderHolder().getFileName();
ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
if ((KualiDecimal.ZERO.compareTo(poItem.getItemOutstandingEncumberedAmount())) >= 0) {
//we have no dollars left encumbered on the po item
String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.OUTSTANDING_ENCUMBERED_AMT_AVAILABLE,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER,PurapKeyConstants.ERROR_REJECT_POITEM_OUTSTANDING_EMCUMBERED_AMOUNT);
return;
}else{
//we have encumbered dollars left on PO
if (((itemHolder.getInvoiceItemSubTotalAmount().setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)).compareTo(poItem.getItemOutstandingEncumberedAmount().bigDecimalValue())) > 0) {
String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.PO_ITEM_AMT_LESSTHAN_INVOICE_ITEM_AMT,extraDescription,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_LINE_NUMBER,PurapKeyConstants.ERROR_REJECT_POITEM_LESS_OUTSTANDING_EMCUMBERED_AMOUNT);
return;
}
}
}
protected void validateUnitPrice(ElectronicInvoiceItemHolder itemHolder){
PurchaseOrderCostSource costSource = itemHolder.getInvoiceOrderHolder().getPurchaseOrderDocument().getPurchaseOrderCostSource();
PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
String extraDescription = "Invoice Item Line Number:" + itemHolder.getInvoiceItemLineNumber();
BigDecimal actualVariance = itemHolder.getInvoiceItemUnitPrice().subtract(poItem.getItemUnitPrice());
BigDecimal lowerPercentage = null;
if (costSource.getItemUnitPriceLowerVariancePercent() != null){
//Checking for lower variance
lowerPercentage = costSource.getItemUnitPriceLowerVariancePercent();
}
else {
//If the cost source itemUnitPriceLowerVariancePercent is null then
//we'll use the exact match (100%).
lowerPercentage = new BigDecimal(100);
}
BigDecimal lowerAcceptableVariance = (lowerPercentage.divide(new BigDecimal(100))).multiply(poItem.getItemUnitPrice()).negate();
if (lowerAcceptableVariance.compareTo(actualVariance) > 0) {
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_AMT_LESSER_THAN_LOWER_VARIANCE, extraDescription, orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UNIT_PRICE, PurapKeyConstants.ERROR_REJECT_UNITPRICE_LOWERVARIANCE);
}
BigDecimal upperPercentage = null;
if (costSource.getItemUnitPriceUpperVariancePercent() != null){
//Checking for upper variance
upperPercentage = costSource.getItemUnitPriceUpperVariancePercent();
}
else {
//If the cost source itemUnitPriceLowerVariancePercent is null then
//we'll use the exact match (100%).
upperPercentage = new BigDecimal(100);
}
BigDecimal upperAcceptableVariance = (upperPercentage.divide(new BigDecimal(100))).multiply(poItem.getItemUnitPrice());
if (upperAcceptableVariance.compareTo(actualVariance) < 0) {
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_AMT_GREATER_THAN_UPPER_VARIANCE, extraDescription, orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_UNIT_PRICE, PurapKeyConstants.ERROR_REJECT_UNITPRICE_UPPERVARIANCE);
}
}
protected void validateSalesTax(ElectronicInvoiceItemHolder itemHolder){
if (LOG.isInfoEnabled()){
LOG.info("Validating sales tax");
}
ElectronicInvoiceOrderHolder orderHolder = itemHolder.getInvoiceOrderHolder();
PurchaseOrderItem poItem = itemHolder.getPurchaseOrderItem();
KualiDecimal invoiceSalesTaxAmount = new KualiDecimal(itemHolder.getTaxAmount());
boolean enableSalesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
boolean salesTaxUsed = false;
PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
List<PurApItem> items = PurApItemUtils.getAboveTheLineOnly(poDoc.getItems());
for (PurApItem item : items) {
if (item.getItemType().isTaxableIndicator()) {
salesTaxUsed = true;
break;
}
}
boolean useTaxUsed = poDoc.isUseTaxIndicator();
enableSalesTaxInd &= (poItem.getItemType().isTaxableIndicator() && (salesTaxUsed || useTaxUsed));
if (LOG.isInfoEnabled()){
LOG.info("Sales Tax Enable Indicator - " + enableSalesTaxInd);
LOG.info("Invoice item tax amount - " + invoiceSalesTaxAmount);
}
if (!enableSalesTaxInd) {
// if sales tax is disabled, item tax amount shall be zero
if (invoiceSalesTaxAmount.compareTo(KualiDecimal.ZERO) != 0) {
String extraDescription = "Item Tax Amount:" + invoiceSalesTaxAmount;
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.TAX_SUMMARY_AMT_EXISTS, extraDescription, orderHolder.getFileName());
orderHolder.addInvoiceHeaderRejectReason(rejectReason);
}
return;
}
// For reject doc, trans date should be the einvoice processed date.
java.sql.Date transTaxDate = itemHolder.getInvoiceOrderHolder().getInvoiceProcessedDate();
String deliveryPostalCode = poItem.getPurchaseOrder().getDeliveryPostalCode();
KualiDecimal extendedPrice = new KualiDecimal(getExtendedPrice(itemHolder).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR));
KualiDecimal salesTaxAmountCalculated = taxService.getTotalSalesTaxAmount(transTaxDate, deliveryPostalCode, extendedPrice);
KualiDecimal actualVariance = invoiceSalesTaxAmount.subtract(salesTaxAmountCalculated);
if (LOG.isInfoEnabled()){
LOG.info("Sales Tax Upper Variance param - " + upperVariancePercentString);
LOG.info("Sales Tax Lower Variance param - " + lowerVariancePercentString);
LOG.info("Trans date (from invoice/rejectdoc) - " + transTaxDate);
LOG.info("Delivery Postal Code - " + deliveryPostalCode);
LOG.info("Extended price - " + extendedPrice);
LOG.info("Sales Tax amount (from sales tax service) - " + salesTaxAmountCalculated);
}
if (StringUtils.isNotEmpty(upperVariancePercentString)){
KualiDecimal upperVariancePercent = new KualiDecimal(upperVariancePercentString);
BigDecimal upperAcceptableVariance = (upperVariancePercent.divide(new KualiDecimal(100))).multiply(salesTaxAmountCalculated).bigDecimalValue();
if (upperAcceptableVariance.compareTo(actualVariance.bigDecimalValue()) < 0){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_GREATER_THAN_UPPER_VARIANCE,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT,PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_UPPERVARIANCE);
return;
}
}
if (StringUtils.isNotEmpty(lowerVariancePercentString)){
KualiDecimal lowerVariancePercent = new KualiDecimal(lowerVariancePercentString);
BigDecimal lowerAcceptableVariance = (lowerVariancePercent.divide(new KualiDecimal(100))).multiply(salesTaxAmountCalculated).bigDecimalValue().negate();
if (lowerAcceptableVariance.compareTo(BigDecimal.ZERO) >= 0 &&
actualVariance.compareTo(KualiDecimal.ZERO) >= 0){
if (actualVariance.bigDecimalValue().compareTo(lowerAcceptableVariance) > 0){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_LESSER_THAN_LOWER_VARIANCE,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT,PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_LOWERVARIANCE);
}
}else{
if (actualVariance.bigDecimalValue().compareTo(lowerAcceptableVariance) < 0){
ElectronicInvoiceRejectReason rejectReason = createRejectReason(PurapConstants.ElectronicInvoice.SALES_TAX_AMT_LESSER_THAN_LOWER_VARIANCE,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_ITEM_TAX_AMT,PurapKeyConstants.ERROR_REJECT_TAXAMOUNT_LOWERVARIANCE);
}
}
}
}
//Copied from PurApItemBase.calculateExtendedPrice
protected BigDecimal getExtendedPrice(ElectronicInvoiceItemHolder itemHolder){
if (itemHolder.getPurchaseOrderItem().getItemType().isAmountBasedGeneralLedgerIndicator()) {
// SERVICE ITEM: return unit price as extended price
return itemHolder.getUnitPrice();
}
else if (ObjectUtils.isNotNull(itemHolder.getQuantity())) { // qty wont be null since it's defined as a reqd field in xsd
BigDecimal calcExtendedPrice = itemHolder.getUnitPrice().multiply(itemHolder.getQuantity());
// ITEM TYPE (qty driven): return (unitPrice x qty)
return calcExtendedPrice;
}
return BigDecimal.ZERO;
}
@Override
public ElectronicInvoiceRejectReason createRejectReason(String rejectReasonTypeCode, String extraDescription, String fileName) {
ElectronicInvoiceRejectReasonType rejectReasonType = getElectronicInvoiceRejectReasonType(rejectReasonTypeCode);
ElectronicInvoiceRejectReason eInvoiceRejectReason = new ElectronicInvoiceRejectReason();
if (rejectReasonType == null){
throw new NullPointerException("Reject reason type for " + rejectReasonTypeCode + " not available in DB");
}
eInvoiceRejectReason.setInvoiceFileName(fileName);
eInvoiceRejectReason.setInvoiceRejectReasonTypeCode(rejectReasonTypeCode);
if (StringUtils.isNotEmpty(extraDescription)) {
eInvoiceRejectReason.setInvoiceRejectReasonDescription(rejectReasonType.getInvoiceRejectReasonTypeDescription() + " (" + extraDescription + ")");
}
else {
eInvoiceRejectReason.setInvoiceRejectReasonDescription(rejectReasonType.getInvoiceRejectReasonTypeDescription());
}
return eInvoiceRejectReason;
}
@Override
public ElectronicInvoiceRejectReasonType getElectronicInvoiceRejectReasonType(String rejectReasonTypeCode){
if (rejectReasonTypes == null){
rejectReasonTypes = getElectronicInvoiceRejectReasonTypes();
}
return rejectReasonTypes.get(rejectReasonTypeCode);
}
protected Map<String, ElectronicInvoiceRejectReasonType> getElectronicInvoiceRejectReasonTypes(){
Collection<ElectronicInvoiceRejectReasonType> collection = SpringContext.getBean(BusinessObjectService.class).findAll(ElectronicInvoiceRejectReasonType.class);
Map rejectReasonTypesMap = new HashMap<String, ElectronicInvoiceRejectReasonType>();
if (collection != null &&
collection.size() > 0){
ElectronicInvoiceRejectReasonType[] rejectReasonTypesArr = new ElectronicInvoiceRejectReasonType[collection.size()];
collection.toArray(rejectReasonTypesArr);
for (int i = 0; i < rejectReasonTypesArr.length; i++) {
rejectReasonTypesMap.put(rejectReasonTypesArr[i].getInvoiceRejectReasonTypeCode(), rejectReasonTypesArr[i]);
}
}
return rejectReasonTypesMap;
}
public void setVendorService(VendorService vendorService) {
this.vendorService = vendorService;
}
public void setTaxService(TaxService taxService) {
this.taxService = taxService;
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
}