001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.module.purap.document.validation.impl; 017 018 import java.util.ArrayList; 019 import java.util.List; 020 021 import org.kuali.kfs.module.purap.PurapConstants; 022 import org.kuali.kfs.module.purap.PurapKeyConstants; 023 import org.kuali.kfs.module.purap.PurapConstants.ItemFields; 024 import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem; 025 import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine; 026 import org.kuali.kfs.module.purap.businessobject.PurApItem; 027 import org.kuali.kfs.module.purap.document.PaymentRequestDocument; 028 import org.kuali.kfs.module.purap.document.service.PurapService; 029 import org.kuali.kfs.sys.document.validation.GenericValidation; 030 import org.kuali.kfs.sys.document.validation.Validation; 031 import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent; 032 import org.kuali.kfs.sys.document.validation.impl.AccountingLineAmountPositiveValidation; 033 import org.kuali.kfs.sys.document.validation.impl.AccountingLineDataDictionaryValidation; 034 import org.kuali.kfs.sys.document.validation.impl.AccountingLineValueAllowedValidation; 035 import org.kuali.kfs.sys.document.validation.impl.AccountingLineValuesAllowedValidationHutch; 036 import org.kuali.kfs.sys.document.validation.impl.BusinessObjectDataDictionaryValidation; 037 import org.kuali.kfs.sys.document.validation.impl.CompositeValidation; 038 import org.kuali.rice.kns.util.GlobalVariables; 039 import org.kuali.rice.kns.util.KualiDecimal; 040 import org.kuali.rice.kns.util.ObjectUtils; 041 042 public class PaymentRequestProcessItemValidation extends GenericValidation { 043 044 private PurapService purapService; 045 private PurApItem itemForValidation; 046 private AttributedDocumentEvent event; 047 private CompositeValidation reviewAccountingLineValidation; 048 private PaymentRequestDocument preqDocument; 049 private PurApAccountingLine preqAccountingLine; 050 051 public boolean validate(AttributedDocumentEvent event) { 052 boolean valid = true; 053 this.event = event; 054 055 PaymentRequestDocument paymentRequestDocument = (PaymentRequestDocument)event.getDocument(); 056 PaymentRequestItem preqItem = (PaymentRequestItem) itemForValidation; 057 058 valid &= validateEachItem(paymentRequestDocument, preqItem); 059 060 return valid; 061 062 } 063 064 /** 065 * Calls another validate item method and passes an identifier string from the item. 066 * 067 * @param paymentRequestDocument - payment request document. 068 * @param item 069 * @return 070 */ 071 protected boolean validateEachItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item) { 072 boolean valid = true; 073 String identifierString = item.getItemIdentifierString(); 074 valid &= validateItem(paymentRequestDocument, item, identifierString); 075 return valid; 076 } 077 078 /** 079 * Performs validation if full document entry not completed and peforms varying item validation. 080 * Such as, above the line, items without accounts, items with accounts. 081 * 082 * @param paymentRequestDocument - payment request document 083 * @param item - payment request item 084 * @param identifierString - identifier string used to mark in an error map 085 * @return 086 */ 087 public boolean validateItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) { 088 boolean valid = true; 089 // only run item validations if before full entry 090 if (!purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) { 091 if (item.getItemType().isLineItemIndicator()) { 092 valid &= validateAboveTheLineItems(item, identifierString,paymentRequestDocument.isReceivingDocumentRequiredIndicator()); 093 } 094 valid &= validateItemWithoutAccounts(item, identifierString); 095 } 096 // always run account validations 097 valid &= validateItemAccounts(paymentRequestDocument, item, identifierString); 098 return valid; 099 } 100 101 /** 102 * Validates above the line items. 103 * 104 * @param item - payment request item 105 * @param identifierString - identifier string used to mark in an error map 106 * @return 107 */ 108 protected boolean validateAboveTheLineItems(PaymentRequestItem item, String identifierString, boolean isReceivingDocumentRequiredIndicator) { 109 boolean valid = true; 110 // Currently Quantity is allowed to be NULL on screen; 111 // must be either a positive number or NULL for DB 112 if (ObjectUtils.isNotNull(item.getItemQuantity())) { 113 if (item.getItemQuantity().isNegative()) { 114 // if quantity is negative give an error 115 valid = false; 116 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_BELOW_ZERO, ItemFields.INVOICE_QUANTITY, identifierString); 117 } 118 if (!isReceivingDocumentRequiredIndicator){ 119 if (item.getPoOutstandingQuantity().isLessThan(item.getItemQuantity())) { 120 valid = false; 121 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_TOO_MANY, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY); 122 } 123 } 124 } 125 if (ObjectUtils.isNotNull(item.getExtendedPrice()) && item.getExtendedPrice().isPositive() && ObjectUtils.isNotNull(item.getPoOutstandingQuantity()) && item.getPoOutstandingQuantity().isPositive()) { 126 127 // here we must require the user to enter some value for quantity if they want a credit amount associated 128 if (ObjectUtils.isNull(item.getItemQuantity()) || item.getItemQuantity().isZero()) { 129 // here we have a user not entering a quantity with an extended amount but the PO has a quantity...require user to 130 // enter a quantity 131 valid = false; 132 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_REQUIRED, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY); 133 } 134 } 135 136 // check that non-quantity based items are not trying to pay on a zero encumbrance amount (check only prior to ap approval) 137 if ((ObjectUtils.isNull(item.getPaymentRequest().getPurapDocumentIdentifier())) || (PurapConstants.PaymentRequestStatuses.IN_PROCESS.equals(item.getPaymentRequest().getStatusCode()))) { 138 if ((item.getItemType().isAmountBasedGeneralLedgerIndicator()) && ((item.getExtendedPrice() != null) && item.getExtendedPrice().isNonZero())) { 139 if (item.getPoOutstandingAmount() == null || item.getPoOutstandingAmount().isZero()) { 140 valid = false; 141 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_ALREADY_PAID, identifierString); 142 } 143 } 144 } 145 146 return valid; 147 } 148 149 /** 150 * Validates that the item must contain at least one account 151 * 152 * @param item - payment request item 153 * @return 154 */ 155 public boolean validateItemWithoutAccounts(PaymentRequestItem item, String identifierString) { 156 boolean valid = true; 157 if (ObjectUtils.isNotNull(item.getItemUnitPrice()) && (new KualiDecimal(item.getItemUnitPrice())).isNonZero() && item.isAccountListEmpty()) { 158 valid = false; 159 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_INCOMPLETE, identifierString); 160 } 161 return valid; 162 } 163 164 /** 165 * Validates the totals for the item by account, that the total by each accounting line for the item, matches 166 * the extended price on the item. 167 * 168 * @param paymentRequestDocument - payment request document 169 * @param item - payment request item to validate 170 * @param identifierString - identifier string used to mark in an error map 171 * @return 172 */ 173 public boolean validateItemAccounts(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) { 174 boolean valid = true; 175 List<PurApAccountingLine> accountingLines = item.getSourceAccountingLines(); 176 KualiDecimal itemTotal = item.getTotalAmount(); 177 KualiDecimal accountTotal = KualiDecimal.ZERO; 178 for (PurApAccountingLine accountingLine : accountingLines) { 179 valid &= reviewAccountingLineValidation(paymentRequestDocument, accountingLine); 180 accountTotal = accountTotal.add(accountingLine.getAmount()); 181 } 182 if (purapService.isFullDocumentEntryCompleted((PaymentRequestDocument) paymentRequestDocument)) { 183 // check amounts not percent after full entry 184 if (accountTotal.compareTo(itemTotal) != 0) { 185 valid = false; 186 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_TOTAL, identifierString); 187 } 188 } 189 return valid; 190 } 191 192 public CompositeValidation getReviewAccountingLineValidation() { 193 return reviewAccountingLineValidation; 194 } 195 196 public void setReviewAccountingLineValidation(CompositeValidation reviewAccountingLineValidation) { 197 this.reviewAccountingLineValidation = reviewAccountingLineValidation; 198 } 199 200 public PurapService getPurapService() { 201 return purapService; 202 } 203 204 public void setPurapService(PurapService purapService) { 205 this.purapService = purapService; 206 } 207 208 public PurApItem getItemForValidation() { 209 return itemForValidation; 210 } 211 212 public void setItemForValidation(PurApItem itemForValidation) { 213 this.itemForValidation = itemForValidation; 214 } 215 216 protected boolean reviewAccountingLineValidation(PaymentRequestDocument document, PurApAccountingLine accountingLine){ 217 boolean valid = true; 218 List<Validation> gauntlet = new ArrayList<Validation>(); 219 this.preqDocument = document; 220 this.preqAccountingLine = accountingLine; 221 222 createGauntlet(reviewAccountingLineValidation); 223 224 for (Validation validation : gauntlet) { 225 valid &= validation.validate(event); 226 } 227 228 return valid; 229 } 230 231 protected void createGauntlet(CompositeValidation validation) { 232 for (Validation val : validation.getValidations()) { 233 if (val instanceof CompositeValidation) { 234 createGauntlet((CompositeValidation)val); 235 } else if (val instanceof BusinessObjectDataDictionaryValidation) { 236 addParametersToValidation((BusinessObjectDataDictionaryValidation)val); 237 } else if (val instanceof AccountingLineAmountPositiveValidation) { 238 addParametersToValidation((AccountingLineAmountPositiveValidation)val); 239 } else if (val instanceof AccountingLineDataDictionaryValidation) { 240 addParametersToValidation((AccountingLineDataDictionaryValidation)val); 241 } else if (val instanceof AccountingLineValuesAllowedValidationHutch) { 242 addParametersToValidation((AccountingLineValuesAllowedValidationHutch)val); 243 } else { 244 throw new IllegalStateException("Validations in the PaymentRequestProcessItemValidation must contain specific instances of validation"); 245 } 246 } 247 } 248 249 protected void addParametersToValidation(BusinessObjectDataDictionaryValidation validation) { 250 validation.setBusinessObjectForValidation(this.preqAccountingLine); 251 } 252 253 protected void addParametersToValidation(AccountingLineAmountPositiveValidation validation) { 254 validation.setAccountingDocumentForValidation(this.preqDocument); 255 validation.setAccountingLineForValidation(this.preqAccountingLine); 256 } 257 258 protected void addParametersToValidation(AccountingLineDataDictionaryValidation validation) { 259 validation.setAccountingLineForValidation(this.preqAccountingLine); 260 } 261 262 protected void addParametersToValidation(AccountingLineValuesAllowedValidationHutch validation) { 263 validation.setAccountingDocumentForValidation(this.preqDocument); 264 validation.setAccountingLineForValidation(this.preqAccountingLine); 265 } 266 267 /** 268 * Gets the event attribute. 269 * @return Returns the event. 270 */ 271 protected AttributedDocumentEvent getEvent() { 272 return event; 273 } 274 275 /** 276 * Sets the event attribute value. 277 * @param event The event to set. 278 */ 279 protected void setEvent(AttributedDocumentEvent event) { 280 this.event = event; 281 } 282 283 /** 284 * Gets the preqDocument attribute. 285 * @return Returns the preqDocument. 286 */ 287 protected PaymentRequestDocument getPreqDocument() { 288 return preqDocument; 289 } 290 291 /** 292 * Sets the preqDocument attribute value. 293 * @param preqDocument The preqDocument to set. 294 */ 295 protected void setPreqDocument(PaymentRequestDocument preqDocument) { 296 this.preqDocument = preqDocument; 297 } 298 299 /** 300 * Gets the preqAccountingLine attribute. 301 * @return Returns the preqAccountingLine. 302 */ 303 protected PurApAccountingLine getPreqAccountingLine() { 304 return preqAccountingLine; 305 } 306 307 /** 308 * Sets the preqAccountingLine attribute value. 309 * @param preqAccountingLine The preqAccountingLine to set. 310 */ 311 protected void setPreqAccountingLine(PurApAccountingLine preqAccountingLine) { 312 this.preqAccountingLine = preqAccountingLine; 313 } 314 315 }