001/* 002 * Copyright 2009 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 */ 016package org.kuali.ole.module.purap.document.validation.impl; 017 018import org.kuali.ole.module.purap.PurapConstants; 019import org.kuali.ole.module.purap.PurapConstants.ItemFields; 020import org.kuali.ole.module.purap.PurapKeyConstants; 021import org.kuali.ole.module.purap.PurapParameterConstants; 022import org.kuali.ole.module.purap.businessobject.PaymentRequestItem; 023import org.kuali.ole.module.purap.businessobject.PurApAccountingLine; 024import org.kuali.ole.module.purap.businessobject.PurApItem; 025import org.kuali.ole.module.purap.document.PaymentRequestDocument; 026import org.kuali.ole.module.purap.document.service.PurapService; 027import org.kuali.ole.select.businessobject.OlePaymentRequestItem; 028import org.kuali.ole.sys.context.SpringContext; 029import org.kuali.ole.sys.document.validation.GenericValidation; 030import org.kuali.ole.sys.document.validation.Validation; 031import org.kuali.ole.sys.document.validation.event.AttributedDocumentEvent; 032import org.kuali.ole.sys.document.validation.impl.*; 033import org.kuali.ole.sys.service.impl.OleParameterConstants; 034import org.kuali.rice.core.api.util.type.KualiDecimal; 035import org.kuali.rice.coreservice.framework.parameter.ParameterService; 036import org.kuali.rice.krad.util.GlobalVariables; 037import org.kuali.rice.krad.util.ObjectUtils; 038 039import java.util.ArrayList; 040import java.util.List; 041 042public 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 @Override 052 public boolean validate(AttributedDocumentEvent event) { 053 boolean valid = true; 054 this.event = event; 055 056 PaymentRequestDocument paymentRequestDocument = (PaymentRequestDocument) event.getDocument(); 057 PaymentRequestItem preqItem = (PaymentRequestItem) itemForValidation; 058 059 valid &= validateEachItem(paymentRequestDocument, preqItem); 060 061 return valid; 062 063 } 064 065 /** 066 * Calls another validate item method and passes an identifier string from the item. 067 * 068 * @param paymentRequestDocument - payment request document. 069 * @param item 070 * @return 071 */ 072 protected boolean validateEachItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item) { 073 boolean valid = true; 074 String identifierString = item.getItemIdentifierString(); 075 valid &= validateItem(paymentRequestDocument, item, identifierString); 076 return valid; 077 } 078 079 /** 080 * Performs validation if full document entry not completed and peforms varying item validation. 081 * Such as, above the line, items without accounts, items with accounts. 082 * 083 * @param paymentRequestDocument - payment request document 084 * @param item - payment request item 085 * @param identifierString - identifier string used to mark in an error map 086 * @return 087 */ 088 public boolean validateItem(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) { 089 boolean valid = true; 090 // only run item validations if before full entry 091 if (!purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) { 092 if (item.getItemType().isLineItemIndicator()) { 093 valid &= validateAboveTheLineItems(item, identifierString, paymentRequestDocument.isReceivingDocumentRequiredIndicator()); 094 } 095 valid &= validateItemWithoutAccounts(item, identifierString); 096 } 097 // always run account validations 098 valid &= validateItemAccounts(paymentRequestDocument, item, identifierString); 099 return valid; 100 } 101 102 /** 103 * Validates above the line items. 104 * 105 * @param item - payment request item 106 * @param identifierString - identifier string used to mark in an error map 107 * @return 108 */ 109 protected boolean validateAboveTheLineItems(PaymentRequestItem item, String identifierString, boolean isReceivingDocumentRequiredIndicator) { 110 boolean valid = true; 111 // Currently Quantity is allowed to be NULL on screen; 112 // must be either a positive number or NULL for DB 113 if (ObjectUtils.isNotNull(item.getItemQuantity())) { 114 if (item.getItemQuantity().isNegative()) { 115 // if quantity is negative give an error 116 valid = false; 117 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_BELOW_ZERO, ItemFields.INVOICE_QUANTITY, identifierString); 118 } 119 if (!isReceivingDocumentRequiredIndicator) { 120 if (item.getPoOutstandingQuantity().isLessThan(item.getItemQuantity())) { 121 valid = false; 122 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_TOO_MANY, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY); 123 } 124 } 125 } 126 if (ObjectUtils.isNotNull(item.getExtendedPrice()) && item.getExtendedPrice().isPositive() && ObjectUtils.isNotNull(item.getPoOutstandingQuantity()) && item.getPoOutstandingQuantity().isPositive()) { 127 128 // here we must require the user to enter some value for quantity if they want a credit amount associated 129 if (ObjectUtils.isNull(item.getItemQuantity()) || item.getItemQuantity().isZero()) { 130 // here we have a user not entering a quantity with an extended amount but the PO has a quantity...require user to 131 // enter a quantity 132 valid = false; 133 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_REQUIRED, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY); 134 } 135 } 136 137 // check that non-quantity based items are not trying to pay on a zero encumbrance amount (check only prior to ap approval) 138 if ((ObjectUtils.isNull(item.getPaymentRequest().getPurapDocumentIdentifier())) || (PurapConstants.PaymentRequestStatuses.APPDOC_IN_PROCESS.equals(item.getPaymentRequest().getApplicationDocumentStatus()))) { 139// RICE20 : needed? : !purapService.isFullDocumentEntryCompleted(item.getPaymentRequest())) { 140 if ((item.getItemType().isAmountBasedGeneralLedgerIndicator()) && ((item.getExtendedPrice() != null) && item.getExtendedPrice().isNonZero())) { 141 if (item.getPoOutstandingAmount() == null || item.getPoOutstandingAmount().isZero()) { 142 valid = false; 143 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_ALREADY_PAID, identifierString); 144 } 145 } 146 } 147 148 return valid; 149 } 150 151 /** 152 * Validates that the item must contain at least one account 153 * 154 * @param item - payment request item 155 * @return 156 */ 157 public boolean validateItemWithoutAccounts(PaymentRequestItem item, String identifierString) { 158 boolean valid = true; 159 if (ObjectUtils.isNotNull(item.getItemUnitPrice()) && (new KualiDecimal(item.getItemUnitPrice())).isNonZero() && item.isAccountListEmpty()) { 160 valid = false; 161 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_INCOMPLETE, identifierString); 162 } 163 return valid; 164 } 165 166 /** 167 * Validates the totals for the item by account, that the total by each accounting line for the item, matches 168 * the extended price on the item. 169 * 170 * @param paymentRequestDocument - payment request document 171 * @param item - payment request item to validate 172 * @param identifierString - identifier string used to mark in an error map 173 * @return 174 */ 175 public boolean validateItemAccounts(PaymentRequestDocument paymentRequestDocument, PaymentRequestItem item, String identifierString) { 176 boolean valid = true; 177 List<PurApAccountingLine> accountingLines = item.getSourceAccountingLines(); 178 KualiDecimal itemTotal = item.getTotalAmount(); 179 KualiDecimal accountTotal = KualiDecimal.ZERO; 180 KualiDecimal prorateSurcharge = KualiDecimal.ZERO; 181 OlePaymentRequestItem preqItem = (OlePaymentRequestItem) item; 182 if (preqItem.getItemType().isQuantityBasedGeneralLedgerIndicator() && preqItem.getExtendedPrice() != null && preqItem.getExtendedPrice().compareTo(KualiDecimal.ZERO) != 0) { 183 if (preqItem.getItemSurcharge() != null && preqItem.getItemTypeCode().equals("ITEM")) { 184 prorateSurcharge = new KualiDecimal(preqItem.getItemSurcharge()).multiply(preqItem.getItemQuantity()); 185 } 186 itemTotal = itemTotal.subtract(prorateSurcharge); 187 } 188 for (PurApAccountingLine accountingLine : accountingLines) { 189 if (accountingLine.getAmount().isZero()) { 190 if (!canApproveAccountingLinesWithZeroAmount()) { 191 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_INVALID, itemForValidation.getItemIdentifierString()); 192 valid &= false; 193 } 194 } 195 valid &= reviewAccountingLineValidation(paymentRequestDocument, accountingLine); 196 accountTotal = accountTotal.add(accountingLine.getAmount()); 197 } 198 if (purapService.isFullDocumentEntryCompleted(paymentRequestDocument)) { 199 // check amounts not percent after full entry 200 if (accountTotal.compareTo(itemTotal) != 0) { 201 valid = false; 202 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_TOTAL, identifierString); 203 } 204 } 205 return valid; 206 } 207 208 public CompositeValidation getReviewAccountingLineValidation() { 209 return reviewAccountingLineValidation; 210 } 211 212 public void setReviewAccountingLineValidation(CompositeValidation reviewAccountingLineValidation) { 213 this.reviewAccountingLineValidation = reviewAccountingLineValidation; 214 } 215 216 public PurapService getPurapService() { 217 return purapService; 218 } 219 220 public void setPurapService(PurapService purapService) { 221 this.purapService = purapService; 222 } 223 224 public PurApItem getItemForValidation() { 225 return itemForValidation; 226 } 227 228 public void setItemForValidation(PurApItem itemForValidation) { 229 this.itemForValidation = itemForValidation; 230 } 231 232 protected boolean reviewAccountingLineValidation(PaymentRequestDocument document, PurApAccountingLine accountingLine) { 233 boolean valid = true; 234 List<Validation> gauntlet = new ArrayList<Validation>(); 235 this.preqDocument = document; 236 this.preqAccountingLine = accountingLine; 237 238 createGauntlet(reviewAccountingLineValidation); 239 240 for (Validation validation : gauntlet) { 241 valid &= validation.validate(event); 242 } 243 244 return valid; 245 } 246 247 protected void createGauntlet(CompositeValidation validation) { 248 for (Validation val : validation.getValidations()) { 249 if (val instanceof CompositeValidation) { 250 createGauntlet((CompositeValidation) val); 251 } else if (val instanceof BusinessObjectDataDictionaryValidation) { 252 addParametersToValidation((BusinessObjectDataDictionaryValidation) val); 253 } else if (val instanceof AccountingLineAmountPositiveValidation) { 254 addParametersToValidation((AccountingLineAmountPositiveValidation) val); 255 } else if (val instanceof AccountingLineDataDictionaryValidation) { 256 addParametersToValidation((AccountingLineDataDictionaryValidation) val); 257 } else if (val instanceof AccountingLineValuesAllowedValidationHutch) { 258 addParametersToValidation((AccountingLineValuesAllowedValidationHutch) val); 259 } else { 260 throw new IllegalStateException("Validations in the PaymentRequestProcessItemValidation must contain specific instances of validation"); 261 } 262 } 263 } 264 265 /** 266 * checks if an accounting line with zero dollar amount can be approved. This will check 267 * the system parameter APPROVE_ACCOUNTING_LINES_WITH_ZERO_DOLLAR_AMOUNT_IND and determines if the 268 * line can be approved or not. 269 * 270 * @return true if the system parameter value is Y else returns N. 271 */ 272 public boolean canApproveAccountingLinesWithZeroAmount() { 273 boolean canApproveLine = false; 274 275 // get parameter to see if accounting line with zero dollar amount can be approved. 276 String approveZeroAmountLine = SpringContext.getBean(ParameterService.class).getParameterValueAsString(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.APPROVE_ACCOUNTING_LINES_WITH_ZERO_DOLLAR_AMOUNT_IND); 277 278 if ("Y".equalsIgnoreCase(approveZeroAmountLine)) { 279 return true; 280 } 281 282 return canApproveLine; 283 } 284 285 protected void addParametersToValidation(BusinessObjectDataDictionaryValidation validation) { 286 validation.setBusinessObjectForValidation(this.preqAccountingLine); 287 } 288 289 protected void addParametersToValidation(AccountingLineAmountPositiveValidation validation) { 290 validation.setAccountingDocumentForValidation(this.preqDocument); 291 validation.setAccountingLineForValidation(this.preqAccountingLine); 292 } 293 294 protected void addParametersToValidation(AccountingLineDataDictionaryValidation validation) { 295 validation.setAccountingLineForValidation(this.preqAccountingLine); 296 } 297 298 protected void addParametersToValidation(AccountingLineValuesAllowedValidationHutch validation) { 299 validation.setAccountingDocumentForValidation(this.preqDocument); 300 validation.setAccountingLineForValidation(this.preqAccountingLine); 301 } 302 303 /** 304 * Gets the event attribute. 305 * 306 * @return Returns the event. 307 */ 308 protected AttributedDocumentEvent getEvent() { 309 return event; 310 } 311 312 /** 313 * Sets the event attribute value. 314 * 315 * @param event The event to set. 316 */ 317 protected void setEvent(AttributedDocumentEvent event) { 318 this.event = event; 319 } 320 321 /** 322 * Gets the preqDocument attribute. 323 * 324 * @return Returns the preqDocument. 325 */ 326 protected PaymentRequestDocument getPreqDocument() { 327 return preqDocument; 328 } 329 330 /** 331 * Sets the preqDocument attribute value. 332 * 333 * @param preqDocument The preqDocument to set. 334 */ 335 protected void setPreqDocument(PaymentRequestDocument preqDocument) { 336 this.preqDocument = preqDocument; 337 } 338 339 /** 340 * Gets the preqAccountingLine attribute. 341 * 342 * @return Returns the preqAccountingLine. 343 */ 344 protected PurApAccountingLine getPreqAccountingLine() { 345 return preqAccountingLine; 346 } 347 348 /** 349 * Sets the preqAccountingLine attribute value. 350 * 351 * @param preqAccountingLine The preqAccountingLine to set. 352 */ 353 protected void setPreqAccountingLine(PurApAccountingLine preqAccountingLine) { 354 this.preqAccountingLine = preqAccountingLine; 355 } 356 357}