/* * 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.coa.document.validation.impl; import java.util.List; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.AccountGlobalDetail; import org.kuali.kfs.coa.businessobject.SubObjectCodeGlobal; import org.kuali.kfs.coa.businessobject.SubObjectCodeGlobalDetail; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; /** * * This class implements the business rules specific to the {@link SubObjCdGlobal} Maintenance Document. */ public class SubObjCdGlobalRule extends GlobalDocumentRuleBase { protected SubObjectCodeGlobal subObjCdGlobal; /** * This method sets the convenience objects like subObjCdGlobal and all the detail objects, so you have short and easy handles to the new and * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load * all sub-objects from the DB by their primary keys, if available. This also loops through each detail item (SubObjCdGlobalDetail and AccountGlobalDetail) * are refreshed * * @param document - the maintenanceDocument being evaluated */ @Override public void setupConvenienceObjects() { // setup subObjCdGlobal convenience objects, // make sure all possible sub-objects are populated subObjCdGlobal = (SubObjectCodeGlobal) super.getNewBo(); // forces refreshes on all the sub-objects in the lists for (SubObjectCodeGlobalDetail objectCodeGlobalDetail : subObjCdGlobal.getSubObjCdGlobalDetails()) { objectCodeGlobalDetail.refreshNonUpdateableReferences(); } for (AccountGlobalDetail accountGlobalDetail : subObjCdGlobal.getAccountGlobalDetails()) { accountGlobalDetail.refreshNonUpdateableReferences(); } } /** * This performs rules checks on document approve * <ul> * <li>{@link SubObjCdGlobalRule#checkSimpleRulesAllLines()}</li> * </ul> * This rule fails on business rule failures * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) { boolean success = true; setupConvenienceObjects(); // check simple rules success &= checkSimpleRulesAllLines(); success &= checkOnlyOneChartErrorWrapper(subObjCdGlobal.getAccountGlobalDetails()); return success; } /** * This performs rules checks on document route * <ul> * <li>{@link SubObjCdGlobalRule#checkSimpleRulesAllLines()}</li> * </ul> * This rule fails on business rule failures * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { boolean success = true; setupConvenienceObjects(); // check simple rules success &= checkSimpleRulesAllLines(); success &= checkAccountDetails(subObjCdGlobal.getAccountGlobalDetails()); return success; } /** * This performs rules checks on document save * <ul> * <li>{@link SubObjCdGlobalRule#checkSimpleRulesAllLines()}</li> * </ul> * This rule does not fail on business rule failures * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { setupConvenienceObjects(); // check simple rules checkSimpleRulesAllLines(); return true; } /** * Before adding either a {@link AccountGlobalDetail} or {@link SubObjCdGlobalDetail} this checks to make sure * that the account and chart are filled in, in the case of SubObjCdGlobalDetail it also checks * that the object code and fiscal year are filled in * If any of these fail, it fails to add the new line to the collection * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject) */ public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) { boolean success = true; if (bo instanceof AccountGlobalDetail) { AccountGlobalDetail detail = (AccountGlobalDetail) bo; // make sure that both primary keys are available for this object if (!checkEmptyValue(detail.getAccountNumber())) { // put an error about accountnumber GlobalVariables.getMessageMap().putError("accountNumber", KFSKeyConstants.ERROR_REQUIRED, "Account Number"); success &= false; } if (!checkEmptyValue(detail.getChartOfAccountsCode())) { // put an error about chart code GlobalVariables.getMessageMap().putError("chartOfAccountsCode", KFSKeyConstants.ERROR_REQUIRED, "Chart of Accounts Code"); success &= false; } success &= checkAccountDetails(detail); } else if (bo instanceof SubObjectCodeGlobalDetail) { SubObjectCodeGlobalDetail detail = (SubObjectCodeGlobalDetail) bo; if (!checkEmptyValue(detail.getChartOfAccountsCode())) { // put an error about accountnumber GlobalVariables.getMessageMap().putError("chartOfAccountsCode", KFSKeyConstants.ERROR_REQUIRED, "Chart of Accounts Code"); success &= false; } if (!checkEmptyValue(detail.getFinancialObjectCode())) { // put an error about financial object code GlobalVariables.getMessageMap().putError("financialObjectCode", KFSKeyConstants.ERROR_REQUIRED, "Financial Object Code"); success &= false; } if (!checkEmptyValue(detail.getUniversityFiscalYear())) { // put an error about financial object code GlobalVariables.getMessageMap().putError("universityFiscalYear", KFSKeyConstants.ERROR_REQUIRED, "University Fiscal Year"); success &= false; } success &= checkSubObjectCodeDetails(detail); } return success; } /** * * This calls the {@link SubObjCdGlobalRule#checkAccountDetails(AccountGlobalDetail)} on each AccountGlobalDetail as well as calling * {@link SubObjCdGlobalRule#checkOnlyOneChartErrorWrapper(List)} to ensure there is just one chart * @param details * @return false if any of the detail objects fail they're sub-rule */ public boolean checkAccountDetails(List<AccountGlobalDetail> details) { boolean success = true; // check if there are any accounts if (details.size() == 0) { putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + "accountGlobalDetails.accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_NO_ACCOUNTS); success = false; } else { // check each account int index = 0; for (AccountGlobalDetail dtl : details) { String errorPath = MAINTAINABLE_ERROR_PREFIX + "accountGlobalDetails[" + index + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); success &= checkAccountDetails(dtl); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); index++; } success &= checkOnlyOneChartErrorWrapper(details); } return success; } /** * * This checks that if the account and chart are entered that the account associated with the AccountGlobalDetail is valid * @param dtl - the AccountGlobalDetail we are dealing with * @return false if any of the fields are found to be invalid */ public boolean checkAccountDetails(AccountGlobalDetail dtl) { boolean success = true; int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount(); getDictionaryValidationService().validateBusinessObject(dtl); if (StringUtils.isNotBlank(dtl.getAccountNumber()) && StringUtils.isNotBlank(dtl.getChartOfAccountsCode())) { dtl.refreshReferenceObject("account"); if (ObjectUtils.isNull(dtl.getAccount())) { GlobalVariables.getMessageMap().putError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT, new String[] { dtl.getChartOfAccountsCode(), dtl.getAccountNumber() }); } } success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount; return success; } /** * * This checks that if the object code, chart code, and fiscal year are entered it is a valid Object Code, chart, and Fiscal Year * associated with this SubObjectCode * @param dtl - the SubObjCdGlobalDetail we are checking * @return false if any of the fields are found to be invalid */ public boolean checkSubObjectCodeDetails(SubObjectCodeGlobalDetail dtl) { boolean success = true; int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount(); getDictionaryValidationService().validateBusinessObject(dtl); if (StringUtils.isNotBlank(dtl.getFinancialObjectCode()) && StringUtils.isNotBlank(dtl.getChartOfAccountsCode()) && dtl.getUniversityFiscalYear() > 0) { dtl.refreshReferenceObject("financialObject"); dtl.refreshReferenceObject("universityFiscal"); dtl.refreshReferenceObject("chartOfAccounts"); if (ObjectUtils.isNull(dtl.getChartOfAccounts()) || ObjectUtils.isNull(dtl.getUniversityFiscal()) || ObjectUtils.isNull(dtl.getFinancialObject())) { GlobalVariables.getMessageMap().putError("financialObjectCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_INVALID_OBJECT_CODE, new String[] { dtl.getFinancialObjectCode(), dtl.getChartOfAccountsCode(), dtl.getUniversityFiscalYear().toString() }); } } success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount; return success; } /** * This method checks the simple rules for all lines at once and gets called on save, submit, etc. but not on add * <ul> * <li>{@link SubObjCdGlobalRule#checkForSubObjCdGlobalDetails(List)}</li> * <li>{@link SubObjCdGlobalRule#checkForAccountGlobalDetails(List)}</li> * <li>{@link SubObjCdGlobalRule#checkFiscalYearAllLines(SubObjCdGlobal)}</li> * <li>{@link SubObjCdGlobalRule#checkChartAllLines(SubObjCdGlobal)}</li> * </ul> * @return */ protected boolean checkSimpleRulesAllLines() { boolean success = true; // check if there are any object codes and accounts, if either fails this should fail if (!checkForSubObjCdGlobalDetails(subObjCdGlobal.getSubObjCdGlobalDetails()) && !checkForAccountGlobalDetails(subObjCdGlobal.getAccountGlobalDetails())) { success = false; } else { // check object codes success &= checkFiscalYearAllLines(subObjCdGlobal); // check chart code success &= checkChartAllLines(subObjCdGlobal); } return success; } /** * * This checks that the SubObjCdGlobalDetail list isn't empty or null * @param subObjCdGlobalDetails * @return false if the list is null or empty */ protected boolean checkForSubObjCdGlobalDetails(List<SubObjectCodeGlobalDetail> subObjCdGlobalDetails) { if (subObjCdGlobalDetails == null || subObjCdGlobalDetails.size() == 0) { putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_NO_OBJECT_CODE); return false; } return true; } /** * * This checks that the AccountGlobalDetail list isn't empty or null * @param acctChangeDetails * @return false if the list is null or empty */ protected boolean checkForAccountGlobalDetails(List<AccountGlobalDetail> acctChangeDetails) { if (acctChangeDetails == null || acctChangeDetails.size() == 0) { putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.ACCOUNT_CHANGE_DETAILS + "." + KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_NO_ACCOUNT); return false; } return true; } /** * * This checks that the fiscal year is the same on the doc and all SubObjCdGlobalDetails * @param socChangeDocument * @return false if the fiscal year is not the same on the doc and any of the SubObjCdGlobalDetails */ protected boolean checkFiscalYearAllLines(SubObjectCodeGlobal socChangeDocument) { boolean success = true; int i = 0; for (SubObjectCodeGlobalDetail subObjCdGlobal : socChangeDocument.getSubObjCdGlobalDetails()) { // check fiscal year first success &= checkFiscalYear(socChangeDocument, subObjCdGlobal, i, false); // increment counter for sub object changes list i++; } return success; } /** * * This checks that the chart is the same on the document, SubObjCdGlobalDetails and AccountGlobalDetails * @param socChangeDocument * @return false if the chart is missing or not the same on the doc, or the detail lists */ protected boolean checkChartAllLines(SubObjectCodeGlobal socChangeDocument) { boolean success = true; int i = 0; for (SubObjectCodeGlobalDetail subObjCdGlobal : socChangeDocument.getSubObjCdGlobalDetails()) { // check chart success &= checkChartOnSubObjCodeDetails(socChangeDocument, subObjCdGlobal, i, false); // increment counter for sub object changes list i++; } // check each account change i = 0; for (AccountGlobalDetail acctChangeDetail : socChangeDocument.getAccountGlobalDetails()) { // check chart success &= checkChartOnAccountDetails(socChangeDocument, acctChangeDetail, i, false); // increment counter for account changes list i++; } return success; } /** * This checks to make sure that the fiscal year on the {@link SubObjCdGlobalDetail} is not empty and * the document's fiscal year matches the detail's fiscal year * * @param socChangeDocument * @return false if the fiscal year is missing or is not the same between the doc and the detail */ protected boolean checkFiscalYear(SubObjectCodeGlobal socChangeDocument, SubObjectCodeGlobalDetail socChangeDetail, int lineNum, boolean add) { boolean success = true; String errorPath = KFSConstants.EMPTY_STRING; // first must have an actual fiscal year if (ObjectUtils.isNull(socChangeDetail.getUniversityFiscal())) { if (add) { errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "universityFiscalYear"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_FISCAL_YEAR_MUST_EXIST); } else { errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "universityFiscalYear"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_FISCAL_YEAR_MUST_EXIST); } success &= false; return success; } // the two fiscal years from the document and detail must match if (!socChangeDocument.getUniversityFiscal().equals(socChangeDetail.getUniversityFiscal())) { if (add) { errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "universityFiscalYear"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_FISCAL_YEAR_MUST_BE_SAME); } else { errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "universityFiscalYear"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_FISCAL_YEAR_MUST_BE_SAME); } success &= false; return success; } return success; } /** * * This checks to make sure that the chart of accounts on the {@link SubObjCdGlobalDetail} is not empty and * the document's chart matches the detail's chart * @param socChangeDocument * @param socChangeDetail * @param lineNum * @param add * @return false if the chart is missing or is not the same between the doc and the detail */ protected boolean checkChartOnSubObjCodeDetails(SubObjectCodeGlobal socChangeDocument, SubObjectCodeGlobalDetail socChangeDetail, int lineNum, boolean add) { boolean success = true; String errorPath = KFSConstants.EMPTY_STRING; if (StringUtils.isBlank(socChangeDetail.getChartOfAccountsCode())) { return success; // just return, the existence check will balk at empty details } // first must have an actual fiscal year if (socChangeDetail.getChartOfAccounts() == null) { if (add) { errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "chartOfAccountsCode"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_EXIST); } else { errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_EXIST); } success &= false; return success; } // the two fiscal years from the document and detail must match if (!socChangeDocument.getChartOfAccounts().equals(socChangeDetail.getChartOfAccounts())) { if (add) { errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "chartOfAccountsCode"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_BE_SAME); } else { errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_BE_SAME); } success &= false; return success; } return success; } /** * * This checks that the chart of accounts on the {@link AccountGlobalDetail} is not empty and matches * the document's chart matches the detail's chart * @param socChangeDocument * @param acctDetail * @param lineNum * @param add * @return false if the chart is missing or is not the same between the doc and the detail */ protected boolean checkChartOnAccountDetails(SubObjectCodeGlobal socChangeDocument, AccountGlobalDetail acctDetail, int lineNum, boolean add) { boolean success = true; String errorPath = KFSConstants.EMPTY_STRING; // first must have an actual fiscal year if (acctDetail.getChartOfAccounts() == null) { if (add) { errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "chartOfAccountsCode"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_EXIST); } else { errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_EXIST); } success &= false; return success; } // the two fiscal years from the document and detail must match if (!socChangeDocument.getChartOfAccounts().equals(acctDetail.getChartOfAccounts())) { if (add) { errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.ACCOUNT_CHANGE_DETAILS + "." + "chartOfAccountsCode"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_BE_SAME); } else { errorPath = KFSPropertyConstants.ACCOUNT_CHANGE_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode"; putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_BE_SAME); } success &= false; return success; } return success; } }