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.ar.document.validation.impl; 017 018 import java.util.HashMap; 019 import java.util.List; 020 import java.util.Map; 021 022 import org.apache.commons.lang.StringUtils; 023 import org.kuali.kfs.gl.service.EntryService; 024 import org.kuali.kfs.module.ar.ArAuthorizationConstants; 025 import org.kuali.kfs.module.ar.ArConstants; 026 import org.kuali.kfs.module.ar.ArKeyConstants; 027 import org.kuali.kfs.module.ar.ArPropertyConstants; 028 import org.kuali.kfs.module.ar.businessobject.CashControlDetail; 029 import org.kuali.kfs.module.ar.businessobject.Customer; 030 import org.kuali.kfs.module.ar.businessobject.PaymentMedium; 031 import org.kuali.kfs.module.ar.document.CashControlDocument; 032 import org.kuali.kfs.module.ar.document.PaymentApplicationDocument; 033 import org.kuali.kfs.module.ar.document.authorization.CashControlDocumentPresentationController; 034 import org.kuali.kfs.module.ar.document.validation.AddCashControlDetailRule; 035 import org.kuali.kfs.module.ar.document.validation.DeleteCashControlDetailRule; 036 import org.kuali.kfs.module.ar.document.validation.GenerateReferenceDocumentRule; 037 import org.kuali.kfs.sys.KFSConstants; 038 import org.kuali.kfs.sys.KFSPropertyConstants; 039 import org.kuali.kfs.sys.businessobject.Bank; 040 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; 041 import org.kuali.kfs.sys.context.SpringContext; 042 import org.kuali.kfs.sys.service.BankService; 043 import org.kuali.rice.kns.document.Document; 044 import org.kuali.rice.kns.rule.event.ApproveDocumentEvent; 045 import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase; 046 import org.kuali.rice.kns.service.BusinessObjectService; 047 import org.kuali.rice.kns.service.DictionaryValidationService; 048 import org.kuali.rice.kns.service.DocumentHelperService; 049 import org.kuali.rice.kns.service.DocumentService; 050 import org.kuali.rice.kns.util.GlobalVariables; 051 import org.kuali.rice.kns.util.MessageMap; 052 import org.kuali.rice.kns.util.ObjectUtils; 053 import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument; 054 055 /** 056 * This class holds the business rules for the AR Cash Control Document 057 */ 058 public class CashControlDocumentRule extends TransactionalDocumentRuleBase implements AddCashControlDetailRule<CashControlDocument>, DeleteCashControlDetailRule<CashControlDocument>, GenerateReferenceDocumentRule<CashControlDocument> { 059 060 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CashControlDocumentRule.class); 061 062 /** 063 * @see org.kuali.rice.kns.rules.TransactionalDocumentRuleBase#processCustomSaveDocumentBusinessRules(Document) 064 */ 065 @Override 066 protected boolean processCustomSaveDocumentBusinessRules(Document document) { 067 068 boolean isValid = super.processCustomSaveDocumentBusinessRules(document); 069 CashControlDocument ccDocument = (CashControlDocument) document; 070 071 ccDocument.refreshReferenceObject("customerPaymentMedium"); 072 ccDocument.refreshReferenceObject("generalLedgerPendingEntries"); 073 074 MessageMap errorMap = GlobalVariables.getMessageMap(); 075 076 if (errorMap.isEmpty()) { 077 isValid &= checkRefDocNumber(ccDocument); 078 isValid &= validateCashControlDetails(ccDocument); 079 } 080 081 return isValid; 082 083 } 084 085 /** 086 * @see org.kuali.rice.kns.rules.TransactionalDocumentRuleBase#processCustomRouteDocumentBusinessRules(Document) 087 */ 088 @Override 089 protected boolean processCustomRouteDocumentBusinessRules(Document document) { 090 091 boolean isValid = super.processCustomRouteDocumentBusinessRules(document); 092 CashControlDocument cashControlDocument = (CashControlDocument) document; 093 094 if (isValid) { 095 isValid &= checkPaymentMedium(cashControlDocument); 096 isValid &= checkRefDocNumber(cashControlDocument); 097 isValid &= validateBankCode(cashControlDocument); 098 isValid &= validateCashControlDetails(cashControlDocument); 099 isValid &= checkCashControlDocumentHasDetails(cashControlDocument); 100 } 101 102 return isValid; 103 104 } 105 106 /** 107 * @see org.kuali.rice.kns.rules.TransactionalDocumentRuleBase#processCustomApproveDocumentBusinessRules(Document) 108 */ 109 @Override 110 protected boolean processCustomApproveDocumentBusinessRules(ApproveDocumentEvent approveEvent) { 111 112 boolean isValid = super.processCustomApproveDocumentBusinessRules(approveEvent); 113 CashControlDocument cashControlDocument = (CashControlDocument) approveEvent.getDocument(); 114 115 cashControlDocument.refreshReferenceObject("customerPaymentMedium"); 116 cashControlDocument.refreshReferenceObject("generalLedgerPendingEntries"); 117 118 isValid &= checkAllAppDocsApproved(cashControlDocument); 119 isValid &= checkGLPEsCreated(cashControlDocument); 120 121 return isValid; 122 123 } 124 125 /** 126 * This method checks the CashControlDetail line amount is not zero or negative. 127 * 128 * @param document the CashControldocument 129 * @param detail the CashControlDetail 130 * @return true is amount is valid, false otherwise 131 */ 132 public boolean checkLineAmount(CashControlDocument document, CashControlDetail detail) { 133 134 boolean isValid = true; 135 136 // line amount cannot be zero 137 if (detail.getFinancialDocumentLineAmount().isZero()) { 138 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.FINANCIAL_DOCUMENT_LINE_AMOUNT, ArKeyConstants.ERROR_LINE_AMOUNT_CANNOT_BE_ZERO); 139 isValid = false; 140 } 141 // line amount cannot be negative 142 if (detail.getFinancialDocumentLineAmount().isNegative()) { 143 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.FINANCIAL_DOCUMENT_LINE_AMOUNT, ArKeyConstants.ERROR_LINE_AMOUNT_CANNOT_BE_NEGATIVE); 144 isValid = false; 145 } 146 return isValid; 147 148 } 149 150 /** 151 * This method checks if the CashControlDocument has any details to be processed. 152 * 153 * @param cashControlDocument the CashControlDocument 154 * @return true if it has details, false otherwise 155 */ 156 public boolean checkCashControlDocumentHasDetails(CashControlDocument cashControlDocument) { 157 158 boolean isValid = true; 159 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 160 161 if (cashControlDocument.getCashControlDetails().isEmpty()) { 162 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CASH_CONTROL_DETAILS, ArKeyConstants.ERROR_NO_LINES_TO_PROCESS); 163 } 164 165 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 166 167 return isValid; 168 169 } 170 171 /** 172 * This method checks that payment medium has a valid value 173 * 174 * @param document 175 * @return true if valid, false otherwise 176 */ 177 public boolean checkPaymentMedium(CashControlDocument document) { 178 179 boolean isValid = true; 180 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 181 String paymentMediumCode = document.getCustomerPaymentMediumCode(); 182 183 Map<String, String> criteria = new HashMap<String, String>(); 184 criteria.put("customerPaymentMediumCode", paymentMediumCode); 185 186 PaymentMedium paymentMedium = (PaymentMedium) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(PaymentMedium.class, criteria); 187 188 if (paymentMedium == null) { 189 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_PAYMENT_MEDIUM_CODE, ArKeyConstants.ERROR_PAYMENT_MEDIUM_IS_NOT_VALID); 190 isValid = false; 191 } 192 193 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 194 return isValid; 195 196 } 197 198 /** 199 * This method checks that reference document number is not null when payment medium is Cash. 200 * 201 * @param document CashControlDocument 202 * @return true if valid, false otherwise 203 */ 204 public boolean checkRefDocNumber(CashControlDocument document) { 205 206 boolean isValid = true; 207 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 208 String paymentMedium = document.getCustomerPaymentMediumCode(); 209 if (ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(paymentMedium)) { 210 String refDocNumber = document.getReferenceFinancialDocumentNumber(); 211 try { 212 Long.parseLong(refDocNumber); 213 if (StringUtils.isBlank(refDocNumber)) { 214 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_CANNOT_BE_NULL_FOR_PAYMENT_MEDIUM_CASH); 215 isValid = false; 216 } 217 else { 218 boolean docExists = SpringContext.getBean(DocumentService.class).documentExists(refDocNumber); 219 if (!docExists) { 220 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_MUST_BE_VALID_FOR_PAYMENT_MEDIUM_CASH); 221 isValid = false; 222 } 223 } 224 } 225 catch (NumberFormatException nfe) { 226 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_MUST_BE_VALID_FOR_PAYMENT_MEDIUM_CASH); 227 isValid = false; 228 } 229 230 } 231 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 232 return isValid; 233 234 } 235 236 /** 237 * This method checks that the GLPEs have been created 238 * 239 * @param document CashControlDocument 240 * @return true if not null, false otherwise 241 */ 242 public boolean checkGLPEsCreated(CashControlDocument cashControlDocument) { 243 244 boolean isValid = true; 245 List<GeneralLedgerPendingEntry> glpes = cashControlDocument.getGeneralLedgerPendingEntries(); 246 247 Integer totalGLRecordsCreated = 0; 248 249 if (glpes == null || glpes.isEmpty()) { 250 totalGLRecordsCreated = cashControlDocument.getGeneralLedgerEntriesPostedCount(); 251 } 252 253 // if payment medium is not Cash the general ledger pending entries must not be empty; if payment medium is Cash then a Cash 254 // Receipt Document must be created prior to creating the Cash Control document and it's number should be set in Reference 255 // Document number 256 if (!ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(cashControlDocument.getCustomerPaymentMediumCode()) && ((glpes == null || glpes.isEmpty()) && totalGLRecordsCreated.intValue() == 0)) { 257 GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_GLPES_NOT_CREATED); 258 isValid = false; 259 } 260 261 return isValid; 262 263 } 264 265 /** 266 * @see org.kuali.kfs.module.ar.document.validation.AddCashControlDetailRule#processAddCashControlDetailBusinessRules(org.kuali.rice.kns.document.TransactionalDocument, 267 * org.kuali.kfs.module.ar.businessobject.CashControlDetail) 268 */ 269 public boolean processAddCashControlDetailBusinessRules(CashControlDocument transactionalDocument, CashControlDetail cashControlDetail) { 270 271 boolean success = true; 272 273 success &= checkGLPEsNotGenerated(transactionalDocument); 274 if (success) { 275 GlobalVariables.getMessageMap().removeFromErrorPath(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX); 276 success &= validateBankCode(transactionalDocument); 277 GlobalVariables.getMessageMap().addToErrorPath(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX); 278 279 success &= validateCashControlDetail(transactionalDocument, cashControlDetail); 280 } 281 return success; 282 283 } 284 285 /** 286 * This method validates CashControlDetail 287 * 288 * @param document CashControlDocument 289 * @param cashControlDetail CashControlDetail 290 * @return true if CashControlDetail is valid, false otherwise 291 */ 292 protected boolean validateCashControlDetail(CashControlDocument document, CashControlDetail cashControlDetail) { 293 294 MessageMap errorMap = GlobalVariables.getMessageMap(); 295 296 boolean isValid = true; 297 298 int originalErrorCount = errorMap.getErrorCount(); 299 // call the DD validation which checks basic data integrity 300 SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(cashControlDetail); 301 isValid = (errorMap.getErrorCount() == originalErrorCount); 302 303 // validate customer number and line amount 304 if (isValid) { 305 String customerNumber = cashControlDetail.getCustomerNumber(); 306 // if customer number is not empty check that it is valid 307 if (customerNumber != null && !customerNumber.equals("")) { 308 isValid &= checkCustomerNumber(customerNumber); 309 } 310 // check if line amount is valid 311 isValid &= checkLineAmount(document, cashControlDetail); 312 } 313 314 return isValid; 315 316 } 317 318 /** 319 * This method validates cash control document's details 320 * 321 * @param cashControlDocument CashControldocument 322 * @return true if valid, false otherwise 323 */ 324 public boolean validateCashControlDetails(CashControlDocument cashControlDocument) { 325 326 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 327 boolean isValid = true; 328 329 for (int i = 0; i < cashControlDocument.getCashControlDetails().size(); i++) { 330 331 CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(i); 332 String propertyName = KFSPropertyConstants.CASH_CONTROL_DETAIL + "[" + i + "]"; 333 GlobalVariables.getMessageMap().addToErrorPath(propertyName); 334 335 isValid &= validateCashControlDetail(cashControlDocument, cashControlDetail); 336 337 GlobalVariables.getMessageMap().removeFromErrorPath(propertyName); 338 } 339 340 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 341 return isValid; 342 343 } 344 345 /** 346 * This method checks that the customer number is valid and not an inactive customer when it is not empty 347 * 348 * @param cashControlDetail 349 * @return true if valid, false otherwise 350 */ 351 protected boolean checkCustomerNumber(String customerNumber) { 352 boolean isValid = true; 353 354 if (customerNumber != null && !customerNumber.equals("")) { 355 356 Map<String, String> criteria = new HashMap<String, String>(); 357 criteria.put("customerNumber", customerNumber); 358 359 Customer customer = (Customer) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Customer.class, criteria); 360 361 if (customer == null) { 362 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_NUMBER, ArKeyConstants.ERROR_CUSTOMER_NUMBER_IS_NOT_VALID); 363 isValid = false; 364 } 365 } 366 367 return isValid; 368 } 369 370 /** 371 * This method checks if GLPEs have been already generated 372 * 373 * @param cashControlDocument the cash control document 374 * @return true if it was not generated, false otherwise 375 */ 376 public boolean checkGLPEsNotGenerated(CashControlDocument cashControlDocument) { 377 378 boolean success = true; 379 List<GeneralLedgerPendingEntry> glpes = cashControlDocument.getGeneralLedgerPendingEntries(); 380 381 if (glpes != null && !glpes.isEmpty()) { 382 success = false; 383 GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_DELETE_ADD_APP_DOCS_NOT_ALLOWED_AFTER_GLPES_GEN); 384 } 385 return success; 386 387 } 388 389 /** 390 * This method checks if all application documents are in approved or in final state 391 * 392 * @param cashControlDocument 393 * @return true if all application documents approved/final, false otherwise 394 */ 395 public boolean checkAllAppDocsApproved(CashControlDocument cashControlDocument) { 396 397 boolean allAppDocsApproved = true; 398 399 for (int i = 0; i < cashControlDocument.getCashControlDetails().size(); i++) { 400 401 CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(i); 402 PaymentApplicationDocument applicationDocument = cashControlDetail.getReferenceFinancialDocument(); 403 KualiWorkflowDocument workflowDocument = applicationDocument.getDocumentHeader().getWorkflowDocument(); 404 405 if (!(workflowDocument.stateIsApproved() || workflowDocument.stateIsFinal())) { 406 allAppDocsApproved = false; 407 408 String propertyName = KFSPropertyConstants.CASH_CONTROL_DETAIL + "[" + i + "]"; 409 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 410 GlobalVariables.getMessageMap().addToErrorPath(propertyName); 411 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.APPLICATION_DOC_STATUS, ArKeyConstants.ERROR_ALL_APPLICATION_DOCS_MUST_BE_APPROVED); 412 GlobalVariables.getMessageMap().removeFromErrorPath(propertyName); 413 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); 414 415 break; 416 } 417 418 } 419 420 return allAppDocsApproved; 421 422 } 423 424 /** 425 * @see org.kuali.kfs.module.ar.document.validation.DeleteCashControlDetailRule#processDeleteCashControlDetailBusinessRules(org.kuali.rice.kns.document.TransactionalDocument, 426 * org.kuali.kfs.module.ar.businessobject.CashControlDetail) 427 */ 428 public boolean processDeleteCashControlDetailBusinessRules(CashControlDocument transactionalDocument, CashControlDetail cashControlDetail) { 429 430 boolean success = true; 431 success &= checkGLPEsNotGenerated(transactionalDocument); 432 return success; 433 434 } 435 436 /** 437 * @see org.kuali.kfs.module.ar.document.validation.GenerateReferenceDocumentRule#processGenerateReferenceDocumentBusinessRules(org.kuali.rice.kns.document.TransactionalDocument) 438 */ 439 public boolean processGenerateReferenceDocumentBusinessRules(CashControlDocument transactionalDocument) { 440 441 boolean success = true; 442 success &= checkPaymentMedium(transactionalDocument); 443 if (success) { 444 success &= checkGLPEsNotGenerated(transactionalDocument); 445 } 446 return success; 447 448 } 449 450 // validate bankCode 451 public boolean validateBankCode(CashControlDocument document) { 452 boolean isValid = true; 453 454 // if the EDIT_BANK_CODE isnt enabled, then dont bother checking it, return with success 455 CashControlDocumentPresentationController ccPC = (CashControlDocumentPresentationController) SpringContext.getBean(DocumentHelperService.class).getDocumentPresentationController(document); 456 if (!ccPC.getEditModes(document).contains(ArAuthorizationConstants.CashControlDocumentEditMode.EDIT_BANK_CODE)) { 457 return isValid; 458 } 459 460 // otherwise, make sure it exists and is valid 461 String bankCode = document.getBankCode(); 462 if (StringUtils.isNotBlank(bankCode)) { 463 Bank bank = SpringContext.getBean(BankService.class).getByPrimaryId(bankCode); 464 if (ObjectUtils.isNull(bank)) { 465 isValid = false; 466 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_INVALID_BANK_CODE); 467 } 468 else { 469 // make sure the bank is eligible for deposit activity 470 if (!bank.isBankDepositIndicator()) { 471 isValid = false; 472 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_BANK_NOT_ELIGIBLE_FOR_DEPOSIT_ACTIVITY); 473 } 474 } 475 } 476 else { 477 if (SpringContext.getBean(BankService.class).isBankSpecificationEnabled()) { 478 isValid = false; 479 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_BANK_CODE_REQUIRED); 480 } 481 } 482 483 return isValid; 484 } 485 486 }