/*
* 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.document.validation.impl;
import java.util.ArrayList;
import java.util.List;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapConstants.ItemFields;
import org.kuali.kfs.module.purap.PurapKeyConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.validation.GenericValidation;
import org.kuali.kfs.sys.document.validation.Validation;
import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
import org.kuali.kfs.sys.document.validation.impl.AccountingLineAmountPositiveValidation;
import org.kuali.kfs.sys.document.validation.impl.AccountingLineDataDictionaryValidation;
import org.kuali.kfs.sys.document.validation.impl.AccountingLineValuesAllowedValidationHutch;
import org.kuali.kfs.sys.document.validation.impl.BusinessObjectDataDictionaryValidation;
import org.kuali.kfs.sys.document.validation.impl.CompositeValidation;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.MessageMap;
import org.kuali.rice.krad.util.ObjectUtils;
public class PaymentRequestProcessItemValidation extends GenericValidation {
private PurapService purapService;
private PurApItem itemForValidation;
private AttributedDocumentEvent event;
private CompositeValidation reviewAccountingLineValidation;
private PaymentRequestDocument preqDocument;
private PurApAccountingLine preqAccountingLine;
public boolean validate(AttributedDocumentEvent event) {
boolean valid = true;
this.event = event;
PaymentRequestDocument paymentRequestDocument = (PaymentRequestDocument)event.getDocument();
PaymentRequestItem preqItem = (PaymentRequestItem) itemForValidation;
valid &= validateEachItem(paymentRequestDocument, preqItem);
return valid;
}
/**
* Calls another validate item method and passes an identifier string from the item.
*
* @param paymentRequestDocument - payment request document.
* @param item
* @return
*/
protected boolean validateEachItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item) {
boolean valid = true;
String identifierString = item.getItemIdentifierString();
valid &= validateItem(paymentRequestDocument, item, identifierString);
return valid;
}
/**
* Performs validation if full document entry not completed and peforms varying item validation.
* Such as, above the line, items without accounts, items with accounts.
*
* @param paymentRequestDocument - payment request document
* @param item - payment request item
* @param identifierString - identifier string used to mark in an error map
* @return
*/
public boolean validateItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) {
boolean valid = true;
// only run item validations if before full entry
if (!purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) {
if (item.getItemType().isLineItemIndicator()) {
valid &= validateAboveTheLineItems(item, identifierString,paymentRequestDocument.isReceivingDocumentRequiredIndicator(),paymentRequestDocument);
}
valid &= validateItemWithoutAccounts(item, identifierString);
}
// always run account validations
valid &= validateItemAccounts(paymentRequestDocument, item, identifierString);
return valid;
}
/**
* Validates above the line items.
*
* @param item - payment request item
* @param identifierString - identifier string used to mark in an error map
* @param paymentRequestDocument, Payment Request Document for items
* @return
*/
protected boolean validateAboveTheLineItems(PaymentRequestItem item, String identifierString, boolean isReceivingDocumentRequiredIndicator, PaymentRequestDocument paymentRequestDocument) {
boolean valid = true;
// Currently Quantity is allowed to be NULL on screen;
// must be either a positive number or NULL for DB
MessageMap errorMap = GlobalVariables.getMessageMap();
errorMap.clearErrorPath();
if (ObjectUtils.isNotNull(item.getItemQuantity())) {
if (item.getItemQuantity().isNegative()) {
// if quantity is negative give an error
valid = false;
errorMap.putError(PurapConstants.ITEM_TAB_ERRORS, PurapKeyConstants.ERROR_ITEM_AMOUNT_BELOW_ZERO, ItemFields.INVOICE_QUANTITY, identifierString);
}
if (!isReceivingDocumentRequiredIndicator){
if (item.getPoOutstandingQuantity().isLessThan(item.getItemQuantity())) {
valid = false;
errorMap.putError(PurapConstants.ITEM_TAB_ERRORS, PurapKeyConstants.ERROR_ITEM_QUANTITY_TOO_MANY, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY);
}
}
}
if (ObjectUtils.isNotNull(item.getExtendedPrice()) && item.getExtendedPrice().isPositive() && ObjectUtils.isNotNull(item.getPoOutstandingQuantity()) && item.getPoOutstandingQuantity().isPositive()) {
// here we must require the user to enter some value for quantity if they want a credit amount associated
if (ObjectUtils.isNull(item.getItemQuantity()) || item.getItemQuantity().isZero()) {
// here we have a user not entering a quantity with an extended amount but the PO has a quantity...require user to
// enter a quantity
valid = false;
errorMap.putError(PurapConstants.ITEM_TAB_ERRORS, PurapKeyConstants.ERROR_ITEM_QUANTITY_REQUIRED, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY);
}
}
//Modified to use the payment request document to not cause unnecessary refetch
// check that non-quantity based items are not trying to pay on a zero encumbrance amount (check only prior to ap approval)
if ((ObjectUtils.isNull(paymentRequestDocument.getPurapDocumentIdentifier())) || (PurapConstants.PaymentRequestStatuses.APPDOC_IN_PROCESS.equals(paymentRequestDocument.getApplicationDocumentStatus()))) {
// RICE20 : needed? : !purapService.isFullDocumentEntryCompleted(item.getPaymentRequest())) {
if ((item.getItemType().isAmountBasedGeneralLedgerIndicator()) && ((item.getExtendedPrice() != null) && item.getExtendedPrice().isNonZero())) {
if (item.getPoOutstandingAmount() == null || item.getPoOutstandingAmount().isZero()) {
valid = false;
errorMap.putError(PurapConstants.ITEM_TAB_ERRORS, PurapKeyConstants.ERROR_ITEM_AMOUNT_ALREADY_PAID, identifierString);
}
}
}
return valid;
}
/**
* Validates that the item must contain at least one account
*
* @param item - payment request item
* @return
*/
public boolean validateItemWithoutAccounts(PaymentRequestItem item, String identifierString) {
boolean valid = true;
if (ObjectUtils.isNotNull(item.getItemUnitPrice()) && (new KualiDecimal(item.getItemUnitPrice())).isNonZero() && item.isAccountListEmpty()) {
valid = false;
GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_INCOMPLETE, identifierString);
}
return valid;
}
/**
* Validates the totals for the item by account, that the total by each accounting line for the item, matches
* the extended price on the item.
*
* @param paymentRequestDocument - payment request document
* @param item - payment request item to validate
* @param identifierString - identifier string used to mark in an error map
* @return
*/
public boolean validateItemAccounts(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) {
boolean valid = true;
List<PurApAccountingLine> accountingLines = item.getSourceAccountingLines();
KualiDecimal itemTotal = item.getTotalAmount();
KualiDecimal accountTotal = KualiDecimal.ZERO;
for (PurApAccountingLine accountingLine : accountingLines) {
if (accountingLine.getAmount().isZero()) {
if (!canApproveAccountingLinesWithZeroAmount()) {
GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_INVALID, itemForValidation.getItemIdentifierString());
valid &= false;
}
}
valid &= reviewAccountingLineValidation(paymentRequestDocument, accountingLine);
accountTotal = accountTotal.add(accountingLine.getAmount());
}
if (purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) {
// check amounts not percent after full entry
if (accountTotal.compareTo(itemTotal) != 0) {
valid = false;
GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_TOTAL, identifierString);
}
}
return valid;
}
public CompositeValidation getReviewAccountingLineValidation() {
return reviewAccountingLineValidation;
}
public void setReviewAccountingLineValidation(CompositeValidation reviewAccountingLineValidation) {
this.reviewAccountingLineValidation = reviewAccountingLineValidation;
}
public PurapService getPurapService() {
return purapService;
}
public void setPurapService(PurapService purapService) {
this.purapService = purapService;
}
public PurApItem getItemForValidation() {
return itemForValidation;
}
public void setItemForValidation(PurApItem itemForValidation) {
this.itemForValidation = itemForValidation;
}
protected boolean reviewAccountingLineValidation(PaymentRequestDocument document, PurApAccountingLine accountingLine){
boolean valid = true;
List<Validation> gauntlet = new ArrayList<Validation>();
this.preqDocument = document;
this.preqAccountingLine = accountingLine;
createGauntlet(reviewAccountingLineValidation);
for (Validation validation : gauntlet) {
valid &= validation.validate(event);
}
return valid;
}
protected void createGauntlet(CompositeValidation validation) {
for (Validation val : validation.getValidations()) {
if (val instanceof CompositeValidation) {
createGauntlet((CompositeValidation)val);
} else if (val instanceof BusinessObjectDataDictionaryValidation) {
addParametersToValidation((BusinessObjectDataDictionaryValidation)val);
} else if (val instanceof AccountingLineAmountPositiveValidation) {
addParametersToValidation((AccountingLineAmountPositiveValidation)val);
} else if (val instanceof AccountingLineDataDictionaryValidation) {
addParametersToValidation((AccountingLineDataDictionaryValidation)val);
} else if (val instanceof AccountingLineValuesAllowedValidationHutch) {
addParametersToValidation((AccountingLineValuesAllowedValidationHutch)val);
} else {
throw new IllegalStateException("Validations in the PaymentRequestProcessItemValidation must contain specific instances of validation");
}
}
}
/**
* checks if an accounting line with zero dollar amount can be approved. This will check
* the system parameter APPROVE_ACCOUNTING_LINES_WITH_ZERO_DOLLAR_AMOUNT_IND and determines if the
* line can be approved or not.
*
* @return true if the system parameter value is Y else returns N.
*/
public boolean canApproveAccountingLinesWithZeroAmount() {
boolean canApproveLine = false;
// get parameter to see if accounting line with zero dollar amount can be approved.
String approveZeroAmountLine = SpringContext.getBean(ParameterService.class).getParameterValueAsString(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.APPROVE_ACCOUNTING_LINES_WITH_ZERO_DOLLAR_AMOUNT_IND);
if ("Y".equalsIgnoreCase(approveZeroAmountLine)) {
return true;
}
return canApproveLine;
}
protected void addParametersToValidation(BusinessObjectDataDictionaryValidation validation) {
validation.setBusinessObjectForValidation(this.preqAccountingLine);
}
protected void addParametersToValidation(AccountingLineAmountPositiveValidation validation) {
validation.setAccountingDocumentForValidation(this.preqDocument);
validation.setAccountingLineForValidation(this.preqAccountingLine);
}
protected void addParametersToValidation(AccountingLineDataDictionaryValidation validation) {
validation.setAccountingLineForValidation(this.preqAccountingLine);
}
protected void addParametersToValidation(AccountingLineValuesAllowedValidationHutch validation) {
validation.setAccountingDocumentForValidation(this.preqDocument);
validation.setAccountingLineForValidation(this.preqAccountingLine);
}
/**
* Gets the event attribute.
* @return Returns the event.
*/
protected AttributedDocumentEvent getEvent() {
return event;
}
/**
* Sets the event attribute value.
* @param event The event to set.
*/
protected void setEvent(AttributedDocumentEvent event) {
this.event = event;
}
/**
* Gets the preqDocument attribute.
* @return Returns the preqDocument.
*/
protected PaymentRequestDocument getPreqDocument() {
return preqDocument;
}
/**
* Sets the preqDocument attribute value.
* @param preqDocument The preqDocument to set.
*/
protected void setPreqDocument(PaymentRequestDocument preqDocument) {
this.preqDocument = preqDocument;
}
/**
* Gets the preqAccountingLine attribute.
* @return Returns the preqAccountingLine.
*/
protected PurApAccountingLine getPreqAccountingLine() {
return preqAccountingLine;
}
/**
* Sets the preqAccountingLine attribute value.
* @param preqAccountingLine The preqAccountingLine to set.
*/
protected void setPreqAccountingLine(PurApAccountingLine preqAccountingLine) {
this.preqAccountingLine = preqAccountingLine;
}
}