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.InvoiceItem; 023import org.kuali.ole.module.purap.businessobject.PurApAccountingLine; 024import org.kuali.ole.module.purap.businessobject.PurApItem; 025import org.kuali.ole.module.purap.document.InvoiceDocument; 026import org.kuali.ole.module.purap.document.service.PurapService; 027import org.kuali.ole.select.OleSelectConstant; 028import org.kuali.ole.select.businessobject.OleInvoiceItem; 029import org.kuali.ole.select.document.OleInvoiceDocument; 030import org.kuali.ole.sys.OLEConstants; 031import org.kuali.ole.sys.OLEKeyConstants; 032import org.kuali.ole.sys.context.SpringContext; 033import org.kuali.ole.sys.document.validation.GenericValidation; 034import org.kuali.ole.sys.document.validation.Validation; 035import org.kuali.ole.sys.document.validation.event.AttributedDocumentEvent; 036import org.kuali.ole.sys.document.validation.impl.*; 037import org.kuali.ole.sys.service.impl.OleParameterConstants; 038import org.kuali.ole.vnd.businessobject.VendorCustomerNumber; 039import org.kuali.rice.core.api.util.type.KualiDecimal; 040import org.kuali.rice.coreservice.framework.parameter.ParameterService; 041import org.kuali.rice.krad.service.KRADServiceLocator; 042import org.kuali.rice.krad.util.GlobalVariables; 043import org.kuali.rice.krad.util.KRADConstants; 044import org.kuali.rice.krad.util.ObjectUtils; 045 046import java.math.BigDecimal; 047import java.util.ArrayList; 048import java.util.HashMap; 049import java.util.List; 050import java.util.Map; 051 052public class InvoiceProcessItemValidation extends GenericValidation { 053 054 private PurapService purapService; 055 private PurApItem itemForValidation; 056 private AttributedDocumentEvent event; 057 private CompositeValidation reviewAccountingLineValidation; 058 private InvoiceDocument invoiceDocument; 059 private PurApAccountingLine invoiceAccountingLine; 060 061 @Override 062 public boolean validate(AttributedDocumentEvent event) { 063 boolean valid = true; 064 this.event = event; 065 066 InvoiceDocument invoiceDocument = (InvoiceDocument) event.getDocument(); 067 InvoiceItem invoiceItem = (InvoiceItem) itemForValidation; 068 069 valid &= validateEachItem(invoiceDocument, invoiceItem); 070 071 return valid; 072 073 } 074 075 /** 076 * Calls another validate item method and passes an identifier string from the item. 077 * 078 * @param invoiceDocument - payment request document. 079 * @param item 080 * @return 081 */ 082 protected boolean validateEachItem(InvoiceDocument invoiceDocument, InvoiceItem item) { 083 boolean valid = true; 084 String identifierString = item.getItemIdentifierString(); 085 valid &= validateItem(invoiceDocument, item, identifierString); 086 return valid; 087 } 088 089 /** 090 * Performs validation if full document entry not completed and peforms varying item validation. 091 * Such as, above the line, items without accounts, items with accounts. 092 * 093 * @param invoiceDocument - payment request document 094 * @param item - payment request item 095 * @param identifierString - identifier string used to mark in an error map 096 * @return 097 */ 098 public boolean validateItem(InvoiceDocument invoiceDocument, InvoiceItem item, String identifierString) { 099 boolean valid = true; 100 // only run item validations if before full entry 101 if (!purapService.isFullDocumentEntryCompleted(invoiceDocument)) { 102 if (item.getItemType().isLineItemIndicator()) { 103 valid &= validateAboveTheLineItems(item, identifierString, invoiceDocument.isReceivingDocumentRequiredIndicator()); 104 valid &= validatePOItem((OleInvoiceItem)item); 105 } 106 valid &= validateItemWithoutAccounts(item, identifierString); 107 } 108 // always run account validations 109 valid &= validateProrationType(invoiceDocument); 110 valid &= validateItemAccounts(invoiceDocument, item, identifierString); 111 return valid; 112 } 113 114 private boolean validatePOItem (OleInvoiceItem invoiceItem) { 115 if (invoiceItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) { 116 if(invoiceItem.getPoItemIdentifier() == null || 117 invoiceItem.getPoItemIdentifier().compareTo(new Integer(0)) == 0) { 118 GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, OLEConstants.ITEM_WITHOUT_PO); 119 return false; 120 } 121 } 122 return true; 123 124 } 125 126 protected boolean validateProrationType(InvoiceDocument invoiceDocument) { 127 boolean isValid = true; 128 OleInvoiceDocument document = (OleInvoiceDocument) invoiceDocument; 129 List<OleInvoiceItem> items = document.getItems(); 130 boolean additionalItemPresent = false; 131 boolean canProrate = false; 132 KualiDecimal additionalCharge = KualiDecimal.ZERO; 133 KualiDecimal totalAmt = document.getInvoicedItemTotal() != null ? 134 new KualiDecimal(document.getInvoicedItemTotal()) : KualiDecimal.ZERO; 135 for (OleInvoiceItem invoiceItem : items) { 136 if (invoiceItem.getItemType().isAdditionalChargeIndicator() && invoiceItem.getExtendedPrice() != null && 137 !invoiceItem.getExtendedPrice().isZero()) { 138 additionalCharge = additionalCharge.add(invoiceItem.getExtendedPrice()); 139 additionalItemPresent = true; 140 } 141 if (invoiceItem.getItemType().isQuantityBasedGeneralLedgerIndicator() && invoiceItem.getItemUnitPrice().compareTo(BigDecimal.ZERO) != 0 ) { 142 canProrate = true; 143 } 144 } 145 if (additionalItemPresent && ((document.getProrateBy() == null) || 146 (!document.isProrateDollar() && !document.isProrateManual() && !document.isProrateQty() && !document.isNoProrate()))) { 147 GlobalVariables.getMessageMap().putErrorForSectionId(OleSelectConstant.INVOICE_ADDITIONAL_ITEM_SECTION_ID, 148 OLEKeyConstants.ERROR_REQUIRED, PurapConstants.PRQSDocumentsStrings.PRORATION_TYPE); 149 isValid &= false; 150 151 } 152 if ((totalAmt.isZero() || !canProrate) && document.isProrateDollar() ) { 153 GlobalVariables.getMessageMap().putError(OleSelectConstant.INVOICE_ADDITIONAL_CHARGE_SECTION_ID, 154 OLEKeyConstants.ERROR_PRORATE_DOLLAR_ZERO_ITEM_TOTAL); 155 } 156 if (document.getVendorCustomerNumber() != null && !document.getVendorCustomerNumber().equalsIgnoreCase("")) { 157 Map<String, String> map = new HashMap<String, String>(); 158 if (document.getVendorCustomerNumber() != null && !document.getVendorCustomerNumber().equalsIgnoreCase("")) { 159 map.put(OLEConstants.VENDOR_CUSTOMER_NUMBER, document.getVendorCustomerNumber()); 160 } 161 if (document.getVendorHeaderGeneratedIdentifier() != null && !document.getVendorHeaderGeneratedIdentifier().toString().equalsIgnoreCase("")) { 162 map.put(OLEConstants.VENDOR_HEADER_IDENTIFIER, document.getVendorHeaderGeneratedIdentifier().toString()); 163 } 164 if (document.getVendorDetailAssignedIdentifier() != null && !document.getVendorDetailAssignedIdentifier().toString().equalsIgnoreCase("")) { 165 map.put(OLEConstants.VENDOR_DETAIL_IDENTIFIER, document.getVendorDetailAssignedIdentifier().toString()); 166 } 167 List<VendorCustomerNumber> vendorCustomerNumbers = (List<VendorCustomerNumber>) KRADServiceLocator.getBusinessObjectService().findMatching(VendorCustomerNumber.class, map); 168 if (!(vendorCustomerNumbers != null && vendorCustomerNumbers.size() > 0)) { 169 isValid &= false; 170 GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, OLEConstants.INVALID_ACQUISITION_NUMBER); 171 } 172 } 173 /* if (totalAmt != null && totalAmt.isZero() && !additionalCharge.isZero() && document.isProrateDollar() ) { 174 GlobalVariables.getMessageMap().putError(OleSelectConstant.INVOICE_ADDITIONAL_CHARGE_SECTION_ID, 175 OLEKeyConstants.ERROR_PRORATE_DOLLAR_ZERO_ITEM_TOTAL); 176 isValid &= false; 177 }*/ 178 return isValid; 179 } 180 /** 181 * Validates above the line items. 182 * 183 * @param item - payment request item 184 * @param identifierString - identifier string used to mark in an error map 185 * @return 186 */ 187 protected boolean validateAboveTheLineItems(InvoiceItem item, String identifierString, boolean isReceivingDocumentRequiredIndicator) { 188 boolean valid = true; 189 // Currently Quantity is allowed to be NULL on screen; 190 // must be either a positive number or NULL for DB 191 if (ObjectUtils.isNotNull(item.getItemQuantity())) { 192 /*if (item.getItemQuantity().isNegative()) { 193 // if quantity is negative give an error 194 valid = false; 195 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_BELOW_ZERO, ItemFields.INVOICE_QUANTITY, identifierString); 196 }*/ 197 if (!isReceivingDocumentRequiredIndicator) { 198 /*if (item.getPoOutstandingQuantity().isLessThan(item.getItemQuantity())) { 199 valid = false; 200 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_TOO_MANY, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY); 201 } */ 202 } 203 } 204 // if (ObjectUtils.isNotNull(item.getExtendedPrice()) && item.getExtendedPrice().isPositive() && ObjectUtils.isNotNull(item.getPoOutstandingQuantity()) && item.getPoOutstandingQuantity().isPositive()) { 205 if (ObjectUtils.isNotNull(item.getExtendedPrice()) && item.getExtendedPrice().isPositive()) { 206 207 // here we must require the user to enter some value for quantity if they want a credit amount associated 208 if (ObjectUtils.isNull(item.getItemQuantity()) || item.getItemQuantity().isZero()) { 209 // here we have a user not entering a quantity with an extended amount but the PO has a quantity...require user to 210 // enter a quantity 211 valid = false; 212 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_QUANTITY_REQUIRED, ItemFields.INVOICE_QUANTITY, identifierString, ItemFields.OPEN_QUANTITY); 213 } 214 } 215 216 // check that non-quantity based items are not trying to pay on a zero encumbrance amount (check only prior to ap approval) 217 if ((ObjectUtils.isNull(item.getInvoice().getPurapDocumentIdentifier())) || (PurapConstants.InvoiceStatuses.APPDOC_IN_PROCESS.equals(item.getInvoice().getApplicationDocumentStatus()))) { 218// RICE20 : needed? : !purapService.isFullDocumentEntryCompleted(item.getInvoice())) { 219 if ((item.getItemType().isAmountBasedGeneralLedgerIndicator()) && ((item.getExtendedPrice() != null) && item.getExtendedPrice().isNonZero())) { 220 if (item.getPoOutstandingAmount() == null || item.getPoOutstandingAmount().isZero()) { 221 valid = false; 222 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_AMOUNT_ALREADY_PAID, identifierString); 223 } 224 } 225 } 226 227 return valid; 228 } 229 230 /** 231 * Validates that the item must contain at least one account 232 * 233 * @param item - payment request item 234 * @return 235 */ 236 public boolean validateItemWithoutAccounts(InvoiceItem item, String identifierString) { 237 boolean valid = true; 238 if (ObjectUtils.isNotNull(item.getItemUnitPrice()) && (new KualiDecimal(item.getItemUnitPrice())).isNonZero() && item.isAccountListEmpty()) { 239 valid = false; 240 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_INCOMPLETE, identifierString); 241 } 242 return valid; 243 } 244 245 /** 246 * Validates the totals for the item by account, that the total by each accounting line for the item, matches 247 * the extended price on the item. 248 * 249 * @param invoiceDocument - payment request document 250 * @param item - payment request item to validate 251 * @param identifierString - identifier string used to mark in an error map 252 * @return 253 */ 254 public boolean validateItemAccounts(InvoiceDocument invoiceDocument, InvoiceItem item, String identifierString) { 255 boolean valid = true; 256 List<PurApAccountingLine> accountingLines = item.getSourceAccountingLines(); 257 KualiDecimal itemTotal = item.getTotalAmount(); 258 KualiDecimal accountTotal = KualiDecimal.ZERO; 259 KualiDecimal prorateSurcharge = KualiDecimal.ZERO; 260 OleInvoiceItem invoiceItem = (OleInvoiceItem) item; 261 if (invoiceItem.getItemType().isQuantityBasedGeneralLedgerIndicator() && invoiceItem.getExtendedPrice() != null && invoiceItem.getExtendedPrice().compareTo(KualiDecimal.ZERO) != 0) { 262 if (invoiceItem.getItemSurcharge() != null && invoiceItem.getItemTypeCode().equals("ITEM")) { 263 prorateSurcharge = new KualiDecimal(invoiceItem.getItemSurcharge()).multiply(invoiceItem.getItemQuantity()); 264 } 265 itemTotal = itemTotal.subtract(prorateSurcharge); 266 } 267 for (PurApAccountingLine accountingLine : accountingLines) { 268 if (accountingLine.getAmount().isZero()) { 269 if (!canApproveAccountingLinesWithZeroAmount()) { 270 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_INVALID, itemForValidation.getItemIdentifierString()); 271 valid &= false; 272 } 273 } 274 valid &= reviewAccountingLineValidation(invoiceDocument, accountingLine); 275 accountTotal = accountTotal.add(accountingLine.getAmount()); 276 } 277 if (purapService.isFullDocumentEntryCompleted(invoiceDocument)) { 278 // check amounts not percent after full entry 279 if (accountTotal.compareTo(itemTotal) != 0) { 280 valid = false; 281 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_ITEM_ACCOUNTING_AMOUNT_TOTAL, identifierString); 282 } 283 } 284 return valid; 285 } 286 287 public CompositeValidation getReviewAccountingLineValidation() { 288 return reviewAccountingLineValidation; 289 } 290 291 public void setReviewAccountingLineValidation(CompositeValidation reviewAccountingLineValidation) { 292 this.reviewAccountingLineValidation = reviewAccountingLineValidation; 293 } 294 295 public PurapService getPurapService() { 296 return purapService; 297 } 298 299 public void setPurapService(PurapService purapService) { 300 this.purapService = purapService; 301 } 302 303 public PurApItem getItemForValidation() { 304 return itemForValidation; 305 } 306 307 public void setItemForValidation(PurApItem itemForValidation) { 308 this.itemForValidation = itemForValidation; 309 } 310 311 protected boolean reviewAccountingLineValidation(InvoiceDocument document, PurApAccountingLine accountingLine) { 312 boolean valid = true; 313 List<Validation> gauntlet = new ArrayList<Validation>(); 314 this.invoiceDocument = document; 315 this.invoiceAccountingLine = accountingLine; 316 317 createGauntlet(reviewAccountingLineValidation); 318 319 for (Validation validation : gauntlet) { 320 valid &= validation.validate(event); 321 } 322 323 return valid; 324 } 325 326 protected void createGauntlet(CompositeValidation validation) { 327 for (Validation val : validation.getValidations()) { 328 if (val instanceof CompositeValidation) { 329 createGauntlet((CompositeValidation) val); 330 } else if (val instanceof BusinessObjectDataDictionaryValidation) { 331 addParametersToValidation((BusinessObjectDataDictionaryValidation) val); 332 } else if (val instanceof AccountingLineAmountPositiveValidation) { 333 //addParametersToValidation((AccountingLineAmountPositiveValidation) val); 334 } else if (val instanceof AccountingLineDataDictionaryValidation) { 335 addParametersToValidation((AccountingLineDataDictionaryValidation) val); 336 } else if (val instanceof AccountingLineValuesAllowedValidationHutch) { 337 addParametersToValidation((AccountingLineValuesAllowedValidationHutch) val); 338 } else { 339 throw new IllegalStateException("Validations in the InvoiceProcessItemValidation must contain specific instances of validation"); 340 } 341 } 342 } 343 344 /** 345 * checks if an accounting line with zero dollar amount can be approved. This will check 346 * the system parameter APPROVE_ACCOUNTING_LINES_WITH_ZERO_DOLLAR_AMOUNT_IND and determines if the 347 * line can be approved or not. 348 * 349 * @return true if the system parameter value is Y else returns N. 350 */ 351 public boolean canApproveAccountingLinesWithZeroAmount() { 352 boolean canApproveLine = false; 353 354 // get parameter to see if accounting line with zero dollar amount can be approved. 355 String approveZeroAmountLine = SpringContext.getBean(ParameterService.class).getParameterValueAsString(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.APPROVE_ACCOUNTING_LINES_WITH_ZERO_DOLLAR_AMOUNT_IND); 356 357 if ("Y".equalsIgnoreCase(approveZeroAmountLine)) { 358 return true; 359 } 360 361 return canApproveLine; 362 } 363 364 protected void addParametersToValidation(BusinessObjectDataDictionaryValidation validation) { 365 validation.setBusinessObjectForValidation(this.invoiceAccountingLine); 366 } 367 368 protected void addParametersToValidation(AccountingLineAmountPositiveValidation validation) { 369 validation.setAccountingDocumentForValidation(this.invoiceDocument); 370 validation.setAccountingLineForValidation(this.invoiceAccountingLine); 371 } 372 373 protected void addParametersToValidation(AccountingLineDataDictionaryValidation validation) { 374 validation.setAccountingLineForValidation(this.invoiceAccountingLine); 375 } 376 377 protected void addParametersToValidation(AccountingLineValuesAllowedValidationHutch validation) { 378 validation.setAccountingDocumentForValidation(this.invoiceDocument); 379 validation.setAccountingLineForValidation(this.invoiceAccountingLine); 380 } 381 382 /** 383 * Gets the event attribute. 384 * 385 * @return Returns the event. 386 */ 387 protected AttributedDocumentEvent getEvent() { 388 return event; 389 } 390 391 /** 392 * Sets the event attribute value. 393 * 394 * @param event The event to set. 395 */ 396 protected void setEvent(AttributedDocumentEvent event) { 397 this.event = event; 398 } 399 400 /** 401 * Gets the invoiceDocument attribute. 402 * 403 * @return Returns the invoiceDocument. 404 */ 405 protected InvoiceDocument getPreqDocument() { 406 return invoiceDocument; 407 } 408 409 /** 410 * Sets the invoiceDocument attribute value. 411 * 412 * @param invoiceDocument The invoiceDocument to set. 413 */ 414 protected void setPreqDocument(InvoiceDocument invoiceDocument) { 415 this.invoiceDocument = invoiceDocument; 416 } 417 418 /** 419 * Gets the invoiceAccountingLine attribute. 420 * 421 * @return Returns the invoiceAccountingLine. 422 */ 423 protected PurApAccountingLine getPreqAccountingLine() { 424 return invoiceAccountingLine; 425 } 426 427 /** 428 * Sets the invoiceAccountingLine attribute value. 429 * 430 * @param invoiceAccountingLine The invoiceAccountingLine to set. 431 */ 432 protected void setPreqAccountingLine(PurApAccountingLine invoiceAccountingLine) { 433 this.invoiceAccountingLine = invoiceAccountingLine; 434 } 435 436}