/* * 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.ar.document.validation.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.module.ar.ArAuthorizationConstants; import org.kuali.kfs.module.ar.ArConstants; import org.kuali.kfs.module.ar.ArKeyConstants; import org.kuali.kfs.module.ar.ArPropertyConstants; import org.kuali.kfs.module.ar.businessobject.CashControlDetail; import org.kuali.kfs.module.ar.businessobject.Customer; import org.kuali.kfs.module.ar.businessobject.PaymentMedium; import org.kuali.kfs.module.ar.document.CashControlDocument; import org.kuali.kfs.module.ar.document.PaymentApplicationDocument; import org.kuali.kfs.module.ar.document.authorization.CashControlDocumentPresentationController; import org.kuali.kfs.module.ar.document.validation.AddCashControlDetailRule; import org.kuali.kfs.module.ar.document.validation.DeleteCashControlDetailRule; import org.kuali.kfs.module.ar.document.validation.GenerateReferenceDocumentRule; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.Bank; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.service.BankService; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.kns.service.DictionaryValidationService; import org.kuali.rice.kns.service.DocumentHelperService; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase; import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.MessageMap; import org.kuali.rice.krad.util.ObjectUtils; /** * This class holds the business rules for the AR Cash Control Document */ public class CashControlDocumentRule extends TransactionalDocumentRuleBase implements AddCashControlDetailRule<CashControlDocument>, DeleteCashControlDetailRule<CashControlDocument>, GenerateReferenceDocumentRule<CashControlDocument> { protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CashControlDocumentRule.class); /** * @see org.kuali.rice.krad.rules.TransactionalDocumentRuleBase#processCustomSaveDocumentBusinessRules(Document) */ @Override protected boolean processCustomSaveDocumentBusinessRules(Document document) { boolean isValid = super.processCustomSaveDocumentBusinessRules(document); CashControlDocument ccDocument = (CashControlDocument) document; ccDocument.refreshReferenceObject(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_PAYMENT_MEDIUM); ccDocument.refreshReferenceObject(KFSPropertyConstants.GENERAL_LEDGER_PENDING_ENTRIES); MessageMap errorMap = GlobalVariables.getMessageMap(); if (errorMap.hasErrors()) { isValid &= checkRefDocNumber(ccDocument); isValid &= validateCashControlDetails(ccDocument); } return isValid; } /** * @see org.kuali.rice.krad.rules.TransactionalDocumentRuleBase#processCustomRouteDocumentBusinessRules(Document) */ @Override protected boolean processCustomRouteDocumentBusinessRules(Document document) { boolean isValid = super.processCustomRouteDocumentBusinessRules(document); CashControlDocument cashControlDocument = (CashControlDocument) document; if (isValid) { isValid &= checkPaymentMedium(cashControlDocument); isValid &= checkRefDocNumber(cashControlDocument); isValid &= validateBankCode(cashControlDocument); isValid &= validateCashControlDetails(cashControlDocument); isValid &= checkCashControlDocumentHasDetails(cashControlDocument); } return isValid; } /** * @see org.kuali.rice.krad.rules.TransactionalDocumentRuleBase#processCustomApproveDocumentBusinessRules(Document) */ @Override protected boolean processCustomApproveDocumentBusinessRules(ApproveDocumentEvent approveEvent) { boolean isValid = super.processCustomApproveDocumentBusinessRules(approveEvent); CashControlDocument cashControlDocument = (CashControlDocument) approveEvent.getDocument(); cashControlDocument.refreshReferenceObject(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_PAYMENT_MEDIUM); cashControlDocument.refreshReferenceObject(KFSPropertyConstants.GENERAL_LEDGER_PENDING_ENTRIES); isValid &= checkAllAppDocsApproved(cashControlDocument); isValid &= checkGLPEsCreated(cashControlDocument); return isValid; } /** * This method checks the CashControlDetail line amount is not zero or negative. * * @param document the CashControldocument * @param detail the CashControlDetail * @return true is amount is valid, false otherwise */ public boolean checkLineAmount(CashControlDocument document, CashControlDetail detail) { boolean isValid = true; // line amount cannot be zero if (detail.getFinancialDocumentLineAmount().isZero()) { GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.FINANCIAL_DOCUMENT_LINE_AMOUNT, ArKeyConstants.ERROR_LINE_AMOUNT_CANNOT_BE_ZERO); isValid = false; } // line amount cannot be negative if (detail.getFinancialDocumentLineAmount().isNegative()) { GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.FINANCIAL_DOCUMENT_LINE_AMOUNT, ArKeyConstants.ERROR_LINE_AMOUNT_CANNOT_BE_NEGATIVE); isValid = false; } return isValid; } /** * This method checks if the CashControlDocument has any details to be processed. * * @param cashControlDocument the CashControlDocument * @return true if it has details, false otherwise */ public boolean checkCashControlDocumentHasDetails(CashControlDocument cashControlDocument) { boolean isValid = true; GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); if (cashControlDocument.getCashControlDetails().isEmpty()) { GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CASH_CONTROL_DETAILS, ArKeyConstants.ERROR_NO_LINES_TO_PROCESS); } GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); return isValid; } /** * This method checks that payment medium has a valid value * * @param document * @return true if valid, false otherwise */ public boolean checkPaymentMedium(CashControlDocument document) { boolean isValid = true; GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); String paymentMediumCode = document.getCustomerPaymentMediumCode(); Map<String, String> criteria = new HashMap<String, String>(); criteria.put("customerPaymentMediumCode", paymentMediumCode); PaymentMedium paymentMedium = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(PaymentMedium.class, criteria); if (paymentMedium == null) { GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_PAYMENT_MEDIUM_CODE, ArKeyConstants.ERROR_PAYMENT_MEDIUM_IS_NOT_VALID); isValid = false; } GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); return isValid; } /** * This method checks that reference document number is not null when payment medium is Cash. * * @param document CashControlDocument * @return true if valid, false otherwise */ public boolean checkRefDocNumber(CashControlDocument document) { boolean isValid = true; GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); String paymentMedium = document.getCustomerPaymentMediumCode(); if (ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(paymentMedium)) { String refDocNumber = document.getReferenceFinancialDocumentNumber(); try { Long.parseLong(refDocNumber); if (StringUtils.isBlank(refDocNumber)) { GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_CANNOT_BE_NULL_FOR_PAYMENT_MEDIUM_CASH); isValid = false; } else { boolean docExists = SpringContext.getBean(DocumentService.class).documentExists(refDocNumber); if (!docExists) { GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_MUST_BE_VALID_FOR_PAYMENT_MEDIUM_CASH); isValid = false; } } } catch (NumberFormatException nfe) { GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_MUST_BE_VALID_FOR_PAYMENT_MEDIUM_CASH); isValid = false; } } GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); return isValid; } /** * This method checks that the GLPEs have been created * * @param document CashControlDocument * @return true if not null, false otherwise */ public boolean checkGLPEsCreated(CashControlDocument cashControlDocument) { boolean isValid = true; List<GeneralLedgerPendingEntry> glpes = cashControlDocument.getGeneralLedgerPendingEntries(); Integer totalGLRecordsCreated = 0; if (glpes == null || glpes.isEmpty()) { totalGLRecordsCreated = cashControlDocument.getGeneralLedgerEntriesPostedCount(); } // if payment medium is not Cash the general ledger pending entries must not be empty; if payment medium is Cash then a Cash // Receipt Document must be created prior to creating the Cash Control document and it's number should be set in Reference // Document number if (!ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(cashControlDocument.getCustomerPaymentMediumCode()) && ((glpes == null || glpes.isEmpty()) && totalGLRecordsCreated.intValue() == 0)) { GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_GLPES_NOT_CREATED); isValid = false; } return isValid; } /** * @see org.kuali.kfs.module.ar.document.validation.AddCashControlDetailRule#processAddCashControlDetailBusinessRules(org.kuali.rice.krad.document.TransactionalDocument, * org.kuali.kfs.module.ar.businessobject.CashControlDetail) */ @Override public boolean processAddCashControlDetailBusinessRules(CashControlDocument transactionalDocument, CashControlDetail cashControlDetail) { boolean success = true; success &= checkGLPEsNotGenerated(transactionalDocument); if (success) { GlobalVariables.getMessageMap().removeFromErrorPath(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX); success &= validateBankCode(transactionalDocument); GlobalVariables.getMessageMap().addToErrorPath(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX); success &= validateCashControlDetail(transactionalDocument, cashControlDetail); } return success; } /** * This method validates CashControlDetail * * @param document CashControlDocument * @param cashControlDetail CashControlDetail * @return true if CashControlDetail is valid, false otherwise */ protected boolean validateCashControlDetail(CashControlDocument document, CashControlDetail cashControlDetail) { MessageMap errorMap = GlobalVariables.getMessageMap(); boolean isValid = true; int originalErrorCount = errorMap.getErrorCount(); // call the DD validation which checks basic data integrity SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(cashControlDetail); isValid = (errorMap.getErrorCount() == originalErrorCount); // validate customer number and line amount if (isValid) { String customerNumber = cashControlDetail.getCustomerNumber(); // if customer number is not empty check that it is valid if (customerNumber != null && !customerNumber.equals("")) { isValid &= checkCustomerNumber(customerNumber); } // check if line amount is valid isValid &= checkLineAmount(document, cashControlDetail); } return isValid; } /** * This method validates cash control document's details * * @param cashControlDocument CashControldocument * @return true if valid, false otherwise */ public boolean validateCashControlDetails(CashControlDocument cashControlDocument) { GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); boolean isValid = true; for (int i = 0; i < cashControlDocument.getCashControlDetails().size(); i++) { CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(i); String propertyName = KFSPropertyConstants.CASH_CONTROL_DETAIL + "[" + i + "]"; GlobalVariables.getMessageMap().addToErrorPath(propertyName); isValid &= validateCashControlDetail(cashControlDocument, cashControlDetail); GlobalVariables.getMessageMap().removeFromErrorPath(propertyName); } GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); return isValid; } /** * This method checks that the customer number is valid and not an inactive customer when it is not empty * * @param cashControlDetail * @return true if valid, false otherwise */ protected boolean checkCustomerNumber(String customerNumber) { boolean isValid = true; if (customerNumber != null && !customerNumber.equals("")) { Map<String, String> criteria = new HashMap<String, String>(); criteria.put(ArPropertyConstants.CustomerFields.CUSTOMER_NUMBER, customerNumber); Customer customer = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Customer.class, criteria); if (customer == null) { GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerFields.CUSTOMER_NUMBER, ArKeyConstants.ERROR_CUSTOMER_NUMBER_IS_NOT_VALID); isValid = false; } } return isValid; } /** * This method checks if GLPEs have been already generated * * @param cashControlDocument the cash control document * @return true if it was not generated, false otherwise */ public boolean checkGLPEsNotGenerated(CashControlDocument cashControlDocument) { boolean success = true; List<GeneralLedgerPendingEntry> glpes = cashControlDocument.getGeneralLedgerPendingEntries(); if (glpes != null && !glpes.isEmpty()) { success = false; GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_DELETE_ADD_APP_DOCS_NOT_ALLOWED_AFTER_GLPES_GEN); } return success; } /** * This method checks if all application documents are in approved or in final state * * @param cashControlDocument * @return true if all application documents approved/final, false otherwise */ public boolean checkAllAppDocsApproved(CashControlDocument cashControlDocument) { boolean allAppDocsApproved = true; for (int i = 0; i < cashControlDocument.getCashControlDetails().size(); i++) { CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(i); PaymentApplicationDocument applicationDocument = cashControlDetail.getReferenceFinancialDocument(); WorkflowDocument workflowDocument = applicationDocument.getDocumentHeader().getWorkflowDocument(); if (!(workflowDocument.isApproved() || workflowDocument.isFinal())) { allAppDocsApproved = false; String propertyName = KFSPropertyConstants.CASH_CONTROL_DETAIL + "[" + i + "]"; GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); GlobalVariables.getMessageMap().addToErrorPath(propertyName); GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.APPLICATION_DOC_STATUS, ArKeyConstants.ERROR_ALL_APPLICATION_DOCS_MUST_BE_APPROVED); GlobalVariables.getMessageMap().removeFromErrorPath(propertyName); GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME); break; } } return allAppDocsApproved; } /** * @see org.kuali.kfs.module.ar.document.validation.DeleteCashControlDetailRule#processDeleteCashControlDetailBusinessRules(org.kuali.rice.krad.document.TransactionalDocument, * org.kuali.kfs.module.ar.businessobject.CashControlDetail) */ @Override public boolean processDeleteCashControlDetailBusinessRules(CashControlDocument transactionalDocument, CashControlDetail cashControlDetail) { boolean success = true; success &= checkGLPEsNotGenerated(transactionalDocument); return success; } /** * @see org.kuali.kfs.module.ar.document.validation.GenerateReferenceDocumentRule#processGenerateReferenceDocumentBusinessRules(org.kuali.rice.krad.document.TransactionalDocument) */ @Override public boolean processGenerateReferenceDocumentBusinessRules(CashControlDocument transactionalDocument) { boolean success = true; success &= checkPaymentMedium(transactionalDocument); if (success) { success &= checkGLPEsNotGenerated(transactionalDocument); } return success; } /** * validate bankCode * * @param document * @return */ public boolean validateBankCode(CashControlDocument document) { boolean isValid = true; // if the EDIT_BANK_CODE isnt enabled, then dont bother checking it, return with success CashControlDocumentPresentationController ccPC = (CashControlDocumentPresentationController) SpringContext.getBean(DocumentHelperService.class).getDocumentPresentationController(document); if (!ccPC.getEditModes(document).contains(ArAuthorizationConstants.CashControlDocumentEditMode.EDIT_BANK_CODE)) { return isValid; } // otherwise, make sure it exists and is valid String bankCode = document.getBankCode(); if (StringUtils.isNotBlank(bankCode)) { Bank bank = SpringContext.getBean(BankService.class).getByPrimaryId(bankCode); if (ObjectUtils.isNull(bank)) { isValid = false; GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_INVALID_BANK_CODE); } else { // make sure the bank is eligible for deposit activity if (!bank.isBankDepositIndicator()) { isValid = false; GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_BANK_NOT_ELIGIBLE_FOR_DEPOSIT_ACTIVITY); } } } else { if (SpringContext.getBean(BankService.class).isBankSpecificationEnabled()) { isValid = false; GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_BANK_CODE_REQUIRED); } } return isValid; } }