/* * 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.bc.document.validation.impl; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.A21SubAccount; import org.kuali.kfs.coa.businessobject.Account; import org.kuali.kfs.coa.businessobject.ObjectCode; import org.kuali.kfs.coa.businessobject.SubAccount; import org.kuali.kfs.coa.businessobject.SubObjectCode; import org.kuali.kfs.fp.service.FiscalYearFunctionControlService; import org.kuali.kfs.module.bc.BCConstants; import org.kuali.kfs.module.bc.BCKeyConstants; import org.kuali.kfs.module.bc.BCPropertyConstants; import org.kuali.kfs.module.bc.BCConstants.AccountSalarySettingOnlyCause; import org.kuali.kfs.module.bc.BCConstants.MonthSpreadDeleteType; import org.kuali.kfs.module.bc.businessobject.BudgetConstructionMonthly; import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionGeneralLedger; import org.kuali.kfs.module.bc.document.BudgetConstructionDocument; import org.kuali.kfs.module.bc.document.service.BenefitsCalculationService; import org.kuali.kfs.module.bc.document.service.BudgetDocumentService; import org.kuali.kfs.module.bc.document.service.BudgetParameterService; import org.kuali.kfs.module.bc.document.service.SalarySettingService; import org.kuali.kfs.module.bc.document.validation.AddBudgetConstructionDocumentRule; import org.kuali.kfs.module.bc.document.validation.AddPendingBudgetGeneralLedgerLineRule; import org.kuali.kfs.module.bc.document.validation.DeleteMonthlySpreadRule; import org.kuali.kfs.module.bc.document.validation.DeletePendingBudgetGeneralLedgerLineRule; import org.kuali.kfs.module.bc.document.validation.SaveMonthlyBudgetRule; import org.kuali.kfs.module.bc.util.BudgetParameterFinder; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.service.AccountingLineRuleHelperService; import org.kuali.rice.core.api.util.type.KualiInteger; import org.kuali.rice.core.api.util.type.TypeUtils; import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase; import org.kuali.rice.kns.service.DataDictionaryService; import org.kuali.rice.kns.service.DictionaryValidationService; import org.kuali.rice.kns.util.KNSGlobalVariables; import org.kuali.rice.krad.datadictionary.DataDictionary; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.exception.InfrastructureException; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.PersistenceService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.MessageMap; import org.kuali.rice.krad.util.ObjectUtils; public class BudgetConstructionDocumentRules extends TransactionalDocumentRuleBase implements AddBudgetConstructionDocumentRule<BudgetConstructionDocument>, AddPendingBudgetGeneralLedgerLineRule<BudgetConstructionDocument, PendingBudgetConstructionGeneralLedger>, DeletePendingBudgetGeneralLedgerLineRule<BudgetConstructionDocument, PendingBudgetConstructionGeneralLedger>, DeleteMonthlySpreadRule<BudgetConstructionDocument>, SaveMonthlyBudgetRule<BudgetConstructionDocument, BudgetConstructionMonthly> { protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BudgetConstructionDocumentRules.class); // some services used here - other service refs are from parent classes // if this class is extended we may need to create protected getters protected static BudgetParameterService budgetParameterService = SpringContext.getBean(BudgetParameterService.class); protected static AccountingLineRuleHelperService accountingLineRuleHelper = SpringContext.getBean(AccountingLineRuleHelperService.class); protected static DataDictionaryService dataDictionaryService = SpringContext.getBean(DataDictionaryService.class); protected static SalarySettingService salarySettingService = SpringContext.getBean(SalarySettingService.class); protected static BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class); protected static FiscalYearFunctionControlService fiscalYearFunctionControlService = SpringContext.getBean(FiscalYearFunctionControlService.class); protected Collection<String> revenueObjectTypesParamValues = BudgetParameterFinder.getRevenueObjectTypes(); protected Collection<String> expenditureObjectTypesParamValues = BudgetParameterFinder.getExpenditureObjectTypes(); protected Collection<String> budgetAggregationCodesParamValues = BudgetParameterFinder.getBudgetAggregationCodes(); protected Collection<String> fringeBenefitDesignatorCodesParamValues = BudgetParameterFinder.getFringeBenefitDesignatorCodes(); protected Collection<String> salarySettingFundGroupsParamValues = BudgetParameterFinder.getSalarySettingFundGroups(); protected Collection<String> salarySettingSubFundGroupsParamValues = BudgetParameterFinder.getSalarySettingSubFundGroups(); // this field is highlighted for any errors found on an existing line protected static final String TARGET_ERROR_PROPERTY_NAME = KFSPropertyConstants.ACCOUNT_LINE_ANNUAL_BALANCE_AMOUNT; public BudgetConstructionDocumentRules() { super(); } /** * @see org.kuali.kfs.module.bc.document.validation.AddBudgetConstructionDocumentRule#processAddBudgetConstructionDocumentRules(org.kuali.kfs.module.bc.document.BudgetConstructionDocument) */ @Override public boolean processAddBudgetConstructionDocumentRules(BudgetConstructionDocument budgetConstructionDocument) { LOG.debug("processAddBudgetConstructionDocumentRules(Document) - start"); MessageMap errors = GlobalVariables.getMessageMap(); boolean isValid = true; // validate primitives for required field and formatting checks int originalErrorCount = errors.getErrorCount(); SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(budgetConstructionDocument); // check to see if any errors were reported int currentErrorCount = errors.getErrorCount(); isValid &= (currentErrorCount == originalErrorCount); if (!isValid) { return isValid; } // can't create BC documents when in system view only mode // let the user know this up front if (!fiscalYearFunctionControlService.isBudgetUpdateAllowed(budgetConstructionDocument.getUniversityFiscalYear())) { errors.putError(KFSPropertyConstants.ACCOUNT_NUMBER, BCKeyConstants.MESSAGE_BUDGET_SYSTEM_VIEW_ONLY); isValid &= false; } // check existence of account first DataDictionary dd = dataDictionaryService.getDataDictionary(); String pkeyValue = budgetConstructionDocument.getChartOfAccountsCode() + "-" + budgetConstructionDocument.getAccountNumber(); isValid &= isValidAccount(budgetConstructionDocument.getAccount(), pkeyValue, dd, KFSPropertyConstants.ACCOUNT_NUMBER); if (isValid) { // run the rules checks preventing BC document creation - assumes account exists isValid &= this.isBudgetAllowed(budgetConstructionDocument, KFSPropertyConstants.ACCOUNT_NUMBER, errors, true, true); } if (!isValid) { // tell the user we can't create a new BC document along with the error reasons KNSGlobalVariables.getMessageList().add(BCKeyConstants.MESSAGE_BUDGET_NOCREATE_DOCUMENT); } LOG.debug("processAddBudgetConstructionDocumentRules(Document) - end"); return isValid; } /** * Runs business rules prior to saving Budget Document proper. This is different than saving typical KFS documents in that the * document is not saved to the user's inbox. Saved Budget Documents must meet the same state requirements as the typical KFS * routed document, so required field checks must be done. Budget Documents can be opened by a user in edit mode multiple times * and while in edit mode documents can be pushed down the review hierarchy, monthly budgets and appointment funding updated, * benefits calculated, etc. Each of these operations require the document's data be in a consistent state with respect to * business rules before the operation be performed. * * @see org.kuali.rice.krad.rules.DocumentRuleBase#processSaveDocument(org.kuali.rice.krad.document.Document) */ @Override public boolean processSaveDocument(Document document) { LOG.debug("processSaveDocument(Document) - start"); boolean isValid = true; // run through the attributes recursively and check dd stuff isValid &= isDocumentAttributesValid(document, true); if (isValid) { isValid &= processSaveBudgetDocumentRules((BudgetConstructionDocument) document, MonthSpreadDeleteType.NONE); } // no custom save rules since we are overriding and doing what we want here already LOG.debug("processSaveDocument(Document) - end"); return isValid; } @Override public boolean processDeleteMonthlySpreadRules(BudgetConstructionDocument budgetConstructionDocument, MonthSpreadDeleteType monthSpreadDeleteType) { LOG.debug("processDeleteRevenueMonthlySpreadRules(Document) - start"); boolean isValid = true; // run through the attributes recursively and check dd stuff isValid &= isDocumentAttributesValid(budgetConstructionDocument, true); if (isValid) { isValid &= processSaveBudgetDocumentRules(budgetConstructionDocument, monthSpreadDeleteType); } // no custom save rules since we are overriding and doing what we want here already LOG.debug("processDeleteRevenueMonthlySpreadRules(Document) - end"); return isValid; } /** * Iterates through existing revenue and expenditure lines to do validation, ri checks on object/sub-object code and request * amount referential integrity checks against appointment funding and monthly detail amounts. Checks are performed when the * request amount has been updated, since initial add action, the last save event or since opening the document, whatever is * latest. * * @see org.kuali.module.budget.rule.SaveBudgetDocumentRule#processSaveBudgetDocumentRules(D) */ public boolean processSaveBudgetDocumentRules(BudgetConstructionDocument budgetConstructionDocument, MonthSpreadDeleteType monthSpreadDeleteType) { MessageMap errors = GlobalVariables.getMessageMap(); boolean doRevMonthRICheck = true; boolean doExpMonthRICheck = true; boolean isValid = true; int originalErrorCount; int currentErrorCount; // refresh only the doc refs we need List refreshFields = Collections.unmodifiableList(Arrays.asList(new String[] { KFSPropertyConstants.ACCOUNT, KFSPropertyConstants.SUB_ACCOUNT })); SpringContext.getBean(PersistenceService.class).retrieveReferenceObjects(budgetConstructionDocument, refreshFields); errors.addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME); if (monthSpreadDeleteType == MonthSpreadDeleteType.REVENUE) { doRevMonthRICheck = false; doExpMonthRICheck = true; } else { if (monthSpreadDeleteType == MonthSpreadDeleteType.EXPENDITURE) { doRevMonthRICheck = true; doExpMonthRICheck = false; } } // iterate and validate revenue lines isValid &= this.checkPendingBudgetConstructionGeneralLedgerLines(budgetConstructionDocument, errors, true, doRevMonthRICheck); // iterate and validate expenditure lines isValid &= this.checkPendingBudgetConstructionGeneralLedgerLines(budgetConstructionDocument, errors, false, doExpMonthRICheck); errors.removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME); return isValid; } /** * Checks a new PBGL line. Comprehensive checks are done. * * @param budgetConstructionDocument * @param pendingBudgetConstructionGeneralLedger * @param isRevenue * @return */ @Override public boolean processAddPendingBudgetGeneralLedgerLineRules(BudgetConstructionDocument budgetConstructionDocument, PendingBudgetConstructionGeneralLedger pendingBudgetConstructionGeneralLedger, boolean isRevenue) { LOG.debug("processAddPendingBudgetGeneralLedgerLineRules() start"); // List refreshFields; MessageMap errors = GlobalVariables.getMessageMap(); boolean isValid = true; int originalErrorCount = errors.getErrorCount(); // validate primitives for required field and formatting checks SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(pendingBudgetConstructionGeneralLedger); // check to see if any errors were reported int currentErrorCount = errors.getErrorCount(); isValid &= (currentErrorCount == originalErrorCount); if (isValid) { // refresh only the doc refs we need List refreshFields = Collections.unmodifiableList(Arrays.asList(new String[] { KFSPropertyConstants.ACCOUNT, KFSPropertyConstants.SUB_ACCOUNT })); SpringContext.getBean(PersistenceService.class).retrieveReferenceObjects(budgetConstructionDocument, refreshFields); // budgetConstructionDocument.getSubAccount().refreshReferenceObject(KFSPropertyConstants.A21_SUB_ACCOUNT); isValid &= this.checkPendingBudgetConstructionGeneralLedgerLine(budgetConstructionDocument, pendingBudgetConstructionGeneralLedger, errors, isRevenue, true); if (isValid) { // line checks ok - does line already exist in target revenue or expenditure list isValid &= isNewLineUnique(budgetConstructionDocument, pendingBudgetConstructionGeneralLedger, errors, isRevenue); } } if (!isValid) { LOG.info("business rule checks failed in processAddPendingBudgetGeneralLedgerLineRules in BudgetConstructionRules"); } LOG.debug("processAddPendingBudgetGeneralLedgerLineRules() end"); return isValid; } /** * Runs rules for deleting an existing revenue or expenditure line. * * @param budgetConstructionDocument * @param pendingBudgetConstructionGeneralLedger * @param isRevenue * @return */ @Override public boolean processDeletePendingBudgetGeneralLedgerLineRules(BudgetConstructionDocument budgetConstructionDocument, PendingBudgetConstructionGeneralLedger pendingBudgetConstructionGeneralLedger, boolean isRevenue) { LOG.debug("processDeletePendingBudgetGeneralLedgerLineRules() start"); MessageMap errors = GlobalVariables.getMessageMap(); boolean isValid = true; // no delete allowed if base exists, the delete button shouldn't even exist in this case, but checking anyway if (pendingBudgetConstructionGeneralLedger.getFinancialBeginningBalanceLineAmount().isZero()) { isValid &= true; } else { isValid &= false; String pkeyVal = pendingBudgetConstructionGeneralLedger.getFinancialObjectCode() + "," + pendingBudgetConstructionGeneralLedger.getFinancialSubObjectCode(); GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_LINE_ANNUAL_BALANCE_AMOUNT, BCKeyConstants.ERROR_NO_DELETE_ALLOWED_WITH_BASE, pkeyVal); } if (!isRevenue) { // no lines using fringe benefit target object codes allowed to be manually deleted by user // the lines are created by benefits calculation process // again the delete button shouldn't even exist isValid &= isNotFringeBenefitObject(fringeBenefitDesignatorCodesParamValues, pendingBudgetConstructionGeneralLedger, errors, false); // no deletion if salary setting option is turned on // and the line is a salary detail line and detail recs exist if (!SpringContext.getBean(SalarySettingService.class).isSalarySettingDisabled()) { if (pendingBudgetConstructionGeneralLedger.getLaborObject() != null) { if (pendingBudgetConstructionGeneralLedger.getLaborObject().isDetailPositionRequiredIndicator()) { if (pendingBudgetConstructionGeneralLedger.isPendingBudgetConstructionAppointmentFundingExists()) { isValid &= false; String pkeyVal = pendingBudgetConstructionGeneralLedger.getFinancialObjectCode() + "," + pendingBudgetConstructionGeneralLedger.getFinancialSubObjectCode(); GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_LINE_ANNUAL_BALANCE_AMOUNT, BCKeyConstants.ERROR_NO_DELETE_ALLOWED_SALARY_DETAIL, pkeyVal); } } } } if (!SpringContext.getBean(BenefitsCalculationService.class).isBenefitsCalculationDisabled()) { // benefits calc is turned on, if the line is valid to remove and the request is not zero, set to calc benefits if (isValid && pendingBudgetConstructionGeneralLedger.getPositionObjectBenefit() != null && !pendingBudgetConstructionGeneralLedger.getPositionObjectBenefit().isEmpty()) { budgetConstructionDocument.setBenefitsCalcNeeded(true); // test if the line has monthly budgets // this assumes business rule of non-zero monthly budget not allowed to sum to a zero annual amount // that is, if annual amount is zero, the monthly record contains all zeros if (pendingBudgetConstructionGeneralLedger.getBudgetConstructionMonthly() != null && !pendingBudgetConstructionGeneralLedger.getBudgetConstructionMonthly().isEmpty()) { budgetConstructionDocument.setMonthlyBenefitsCalcNeeded(true); } } } } LOG.debug("processDeletePendingBudgetGeneralLedgerLineRules() end"); return isValid; } /** * @see org.kuali.kfs.module.bc.document.validation.SaveMonthlyBudgetRule#processSaveMonthlyBudgetRules(org.kuali.kfs.module.bc.document.BudgetConstructionDocument, * org.kuali.kfs.module.bc.businessobject.BudgetConstructionMonthly) */ @Override public boolean processSaveMonthlyBudgetRules(BudgetConstructionDocument budgetConstructionDocument, BudgetConstructionMonthly budgetConstructionMonthly) { LOG.debug("processSaveMonthlyBudgetRules() start"); budgetConstructionMonthly.refreshNonUpdateableReferences(); PendingBudgetConstructionGeneralLedger pbgl = budgetConstructionMonthly.getPendingBudgetConstructionGeneralLedger(); MessageMap errors = GlobalVariables.getMessageMap(); boolean isValid = true; int originalErrorCount = errors.getErrorCount(); // validate primitives for required field and formatting checks SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(budgetConstructionMonthly); // check to see if any errors were reported int currentErrorCount = errors.getErrorCount(); isValid &= (currentErrorCount == originalErrorCount); // Check special cleanup mode case and berate user on save of anything. // The user should delete the row, which bypasses this rule. if (!budgetConstructionDocument.isBudgetableDocument()) { isValid &= Boolean.FALSE; errors.putError(BCPropertyConstants.FINANCIAL_DOCUMENT_MONTH1_LINE_AMOUNT, BCKeyConstants.ERROR_BUDGET_DOCUMENT_NOT_BUDGETABLE, budgetConstructionDocument.getAccountNumber() + ";" + budgetConstructionDocument.getSubAccountNumber()); } DataDictionary dd = dataDictionaryService.getDataDictionary(); if (isValid) { ObjectCode objectCode = budgetConstructionMonthly.getFinancialObject(); isValid &= isValidObjectCode(objectCode, budgetConstructionMonthly.getFinancialObjectCode(), dd, KFSPropertyConstants.FINANCIAL_OBJECT_CODE); if (StringUtils.isNotBlank(budgetConstructionMonthly.getFinancialSubObjectCode()) && !budgetConstructionMonthly.getFinancialSubObjectCode().equalsIgnoreCase(KFSConstants.getDashFinancialSubObjectCode())){ SubObjectCode subObjectCode = budgetConstructionMonthly.getFinancialSubObject(); isValid &= isValidSubObjectCode(subObjectCode, budgetConstructionMonthly.getFinancialSubObjectCode(), dd, KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE); } } if (isValid) { KualiInteger monthlyTotal = budgetConstructionMonthly.getFinancialDocumentMonthTotalLineAmount(); if (!salarySettingService.isSalarySettingDisabled()) { if (pbgl.getLaborObject() != null && pbgl.getLaborObject().isDetailPositionRequiredIndicator()) { // no request amount overrides allowed for salary setting detail lines if (!monthlyTotal.equals(pbgl.getAccountLineAnnualBalanceAmount())) { isValid &= false; errors.putError(BCPropertyConstants.FINANCIAL_DOCUMENT_MONTH1_LINE_AMOUNT, BCKeyConstants.ERROR_MONTHLY_DETAIL_SALARY_OVERIDE, budgetConstructionMonthly.getFinancialObjectCode(), monthlyTotal.toString(), pbgl.getAccountLineAnnualBalanceAmount().toString()); } } } // check for monthly total adding to zero (makes no sense) if (monthlyTotal.isZero()) { boolean nonZeroMonthlyExists = false; nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth1LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth2LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth3LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth4LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth5LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth6LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth7LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth8LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth9LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth10LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth11LineAmount().isNonZero(); nonZeroMonthlyExists |= budgetConstructionMonthly.getFinancialDocumentMonth12LineAmount().isNonZero(); if (nonZeroMonthlyExists) { isValid &= false; errors.putError(BCPropertyConstants.FINANCIAL_DOCUMENT_MONTH1_LINE_AMOUNT, BCKeyConstants.ERROR_MONTHLY_TOTAL_ZERO); } } } else { LOG.info("business rule checks failed in processSaveMonthlyBudgetRules in BudgetConstructionDocumentRules"); } LOG.debug("processSaveMonthlyBudgetRules() end"); return isValid; } /** * Iterates existing revenue or expenditure lines. Checks if request amount is non-zero or has changed and runs business rules * on the line. * * @param budgetConstructionDocument * @param errors * @param isRevenue * @return */ protected boolean checkPendingBudgetConstructionGeneralLedgerLines(BudgetConstructionDocument budgetConstructionDocument, MessageMap errors, boolean isRevenue, boolean doMonthRICheck) { boolean isValid = true; boolean isReqAmountValid; int originalErrorCount; int currentErrorCount; List<PendingBudgetConstructionGeneralLedger> pendingBudgetConstructionGeneralLedgerLines; String linesErrorPath; if (isRevenue) { pendingBudgetConstructionGeneralLedgerLines = budgetConstructionDocument.getPendingBudgetConstructionGeneralLedgerRevenueLines(); linesErrorPath = BCPropertyConstants.PENDING_BUDGET_CONSTRUCTION_GENERAL_LEDGER_REVENUE_LINES; } else { pendingBudgetConstructionGeneralLedgerLines = budgetConstructionDocument.getPendingBudgetConstructionGeneralLedgerExpenditureLines(); linesErrorPath = BCPropertyConstants.PENDING_BUDGET_CONSTRUCTION_GENERAL_LEDGER_EXPENDITURE_LINES; } // iterate revenue or expenditure lines Integer index = 0; for (Iterator iter = pendingBudgetConstructionGeneralLedgerLines.iterator(); iter.hasNext(); index++) { PendingBudgetConstructionGeneralLedger element = (PendingBudgetConstructionGeneralLedger) iter.next(); errors.addToErrorPath(linesErrorPath + "[" + index + "]"); originalErrorCount = errors.getErrorCount(); // run dd required field and format checks on request amount only, since only it can be changed by user // no sanity checks on hiddens and readonly field params validatePrimitiveFromDescriptor(element, TARGET_ERROR_PROPERTY_NAME, "", true); // check to see if any errors were reported currentErrorCount = errors.getErrorCount(); isReqAmountValid = (currentErrorCount == originalErrorCount); isValid &= isReqAmountValid; // test for new errors from this point - if none, test if benefits calc required originalErrorCount = errors.getErrorCount(); // has the request amount changed? boolean isRequestAmountChanged = (isReqAmountValid && (!element.getAccountLineAnnualBalanceAmount().equals(element.getPersistedAccountLineAnnualBalanceAmount()))); // only do checks if request amount is non-zero and not equal to currently persisted amount // or the document is not budgetable and the request is non-zero if (isReqAmountValid && element.getAccountLineAnnualBalanceAmount().isNonZero()) { boolean isSalaryFringeLine = false; if (!isRevenue && fringeBenefitDesignatorCodesParamValues != null && element.getLaborObject() != null) { isSalaryFringeLine = fringeBenefitDesignatorCodesParamValues.contains(element.getLaborObject().getFinancialObjectFringeOrSalaryCode()); } boolean is2PLG = !isRevenue && element.getFinancialObjectCode().contentEquals(KFSConstants.BudgetConstructionConstants.OBJECT_CODE_2PLG); boolean isCleanupModeActionForceCheck = budgetConstructionDocument.isCleanupModeActionForceCheck(); // Request notZero, do checks if user enters a change to a request amount or // (We are in cleanupMode and the current action (save or close-save) forces a cleanup mode check and // not 2PLG line and not salary fringe line) // This allows the user to use quick salary setting, monthly edit, global month delete to do cleanup work and // to print out values or push/pull before cleanup. if (isRequestAmountChanged || (doMonthRICheck && !is2PLG && !isSalaryFringeLine) || (!budgetConstructionDocument.isBudgetableDocument() && isCleanupModeActionForceCheck && !is2PLG && !isSalaryFringeLine)) { isValid &= this.checkPendingBudgetConstructionGeneralLedgerLine(budgetConstructionDocument, element, errors, isRevenue, false); } } // Do RI type checks for request amount against monthly and salary setting detail if persisted amount changes // or a 2plg exists and the line is a salary setting detail line // Also tests if the line is has benefits associate and flags that a benefits calculation needs done. // Benefits calc is then called in the form action after successful rules check and save boolean forceTwoPlugRICheck = (budgetConstructionDocument.isContainsTwoPlug() && (element.getLaborObject() != null && element.getLaborObject().isDetailPositionRequiredIndicator())); // force monthly RI check if 2PLG and if request amount changes AND not a detail salary setting line boolean forceMonthlyRICheck = (budgetConstructionDocument.isContainsTwoPlug() && (element.getLaborObject() == null || !element.getLaborObject().isDetailPositionRequiredIndicator())); if (isReqAmountValid && (isRequestAmountChanged || forceTwoPlugRICheck)) { // check monthly for all rows if (doMonthRICheck || forceMonthlyRICheck) { if (element.getBudgetConstructionMonthly() != null && !element.getBudgetConstructionMonthly().isEmpty()) { BudgetConstructionMonthly budgetConstructionMonthly = element.getBudgetConstructionMonthly().get(0); if (budgetConstructionMonthly != null) { KualiInteger monthSum = KualiInteger.ZERO; monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth1LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth2LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth3LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth4LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth5LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth6LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth7LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth8LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth9LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth10LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth11LineAmount()); monthSum = monthSum.add(budgetConstructionMonthly.getFinancialDocumentMonth12LineAmount()); if (!monthSum.equals(element.getAccountLineAnnualBalanceAmount())) { isValid &= false; String pkeyVal = element.getFinancialObjectCode() + "," + element.getFinancialSubObjectCode(); GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_LINE_ANNUAL_BALANCE_AMOUNT, BCKeyConstants.ERROR_MONTHLY_SUM_REQUEST_NOT_EQUAL, pkeyVal, monthSum.toString(), element.getAccountLineAnnualBalanceAmount().toString()); } } } } // check salary setting detail sum if expenditure line is a ss detail line // and salary setting option is turned on if (!SpringContext.getBean(SalarySettingService.class).isSalarySettingDisabled()) { if (element.getLaborObject() != null) { if (element.getLaborObject().isDetailPositionRequiredIndicator()) { // sum the detail lines and compare against the accounting line request amount KualiInteger salarySum = KualiInteger.ZERO; // if salary setting detail exists, sum it otherwise default to zero if (element.isPendingBudgetConstructionAppointmentFundingExists()) { // run reportquery to get the salary request sum salarySum = SpringContext.getBean(BudgetDocumentService.class).getPendingBudgetConstructionAppointmentFundingRequestSum(element); } if (!salarySum.equals(element.getAccountLineAnnualBalanceAmount())) { isValid &= false; String pkeyVal = element.getFinancialObjectCode() + "," + element.getFinancialSubObjectCode(); GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_LINE_ANNUAL_BALANCE_AMOUNT, BCKeyConstants.ERROR_SALARY_SUM_REQUEST_NOT_EQUAL, pkeyVal, salarySum.toString(), element.getAccountLineAnnualBalanceAmount().toString()); } } } } // only do benefits calc needed test if the user changed something - not if forcing the RI check if (isReqAmountValid && !element.getAccountLineAnnualBalanceAmount().equals(element.getPersistedAccountLineAnnualBalanceAmount())) { // if benefits calculation is turned on, // check if the line is benefits related and call for calculation after save if (!SpringContext.getBean(BenefitsCalculationService.class).isBenefitsCalculationDisabled()) { // retest for added errors since testing this line started - if none, test if benefits calc required currentErrorCount = errors.getErrorCount(); isReqAmountValid = (currentErrorCount == originalErrorCount); if (isReqAmountValid && element.getPositionObjectBenefit() != null && !element.getPositionObjectBenefit().isEmpty()) { budgetConstructionDocument.setBenefitsCalcNeeded(true); } } } } errors.removeFromErrorPath(linesErrorPath + "[" + index + "]"); } return isValid; } /** * Checks a PBGL line. Assumes the line has been checked against the dd for formatting and if required * * @param budgetConstructionDocument * @param pendingBudgetConstructionGeneralLedger * @return */ protected boolean checkPendingBudgetConstructionGeneralLedgerLine(BudgetConstructionDocument budgetConstructionDocument, PendingBudgetConstructionGeneralLedger pendingBudgetConstructionGeneralLedger, MessageMap errors, boolean isRevenue, boolean isAdd) { LOG.debug("checkPendingBudgetConstructionGeneralLedgerLine() start"); boolean isValid = true; // now make sure all the necessary business objects are fully populated // this refreshes any refs not done by populate for display purposes auto-update="none" pendingBudgetConstructionGeneralLedger.refreshNonUpdateableReferences(); isValid &= validatePBGLLine(pendingBudgetConstructionGeneralLedger, isAdd); if (isValid) { // all lines must have objects defined with financialBudgetAggregation = 'O'; isValid &= isBudgetAggregationAllowed(budgetAggregationCodesParamValues, pendingBudgetConstructionGeneralLedger, errors, isAdd); isValid &= this.isBudgetAllowed(budgetConstructionDocument, KFSPropertyConstants.FINANCIAL_OBJECT_CODE, errors, isAdd, false); // revenue specific checks if (isRevenue) { // no revenue lines in CnG accounts or SDCI isValid &= isNotSalarySettingOnly(salarySettingFundGroupsParamValues, salarySettingSubFundGroupsParamValues, budgetConstructionDocument, pendingBudgetConstructionGeneralLedger, errors, isRevenue, isAdd); // line must use matching revenue object type isValid &= isObjectTypeAllowed(revenueObjectTypesParamValues, pendingBudgetConstructionGeneralLedger, errors, isRevenue, isAdd); } else { // expenditure specific checks // line must use matching expenditure object type isValid &= isObjectTypeAllowed(expenditureObjectTypesParamValues, pendingBudgetConstructionGeneralLedger, errors, isRevenue, isAdd); // no lines using labor objects in non-wage accounts isValid &= isNonWagesAccountNotLaborObject(budgetConstructionDocument, pendingBudgetConstructionGeneralLedger, errors, isAdd); // only lines using detail labor objects allowed in fund group CG and sfund group SDCI isValid &= isNotSalarySettingOnly(salarySettingFundGroupsParamValues, salarySettingSubFundGroupsParamValues, budgetConstructionDocument, pendingBudgetConstructionGeneralLedger, errors, isRevenue, isAdd); // no lines using fringe benefit target object codes allowed to be manually added by user // the lines are created by benefits calculation process isValid &= isNotFringeBenefitObject(fringeBenefitDesignatorCodesParamValues, pendingBudgetConstructionGeneralLedger, errors, isAdd); } } if (!isValid) { LOG.info("business rule checks failed in checkPendingBudgetConstructionGeneralLedgerLine in BudgetConstructionRules"); } LOG.debug("checkPendingBudgetConstructionGeneralLedgerLine() end"); return isValid; } protected boolean validatePBGLLine(PendingBudgetConstructionGeneralLedger pendingBudgetConstructionGeneralLedger, boolean isAdd) { if (pendingBudgetConstructionGeneralLedger == null) { throw new IllegalStateException(getKualiConfigurationService().getPropertyValueAsString(KFSKeyConstants.ERROR_DOCUMENT_NULL_ACCOUNTING_LINE)); } // grab the service instance that will be needed by all the validate methods DataDictionary dd = dataDictionaryService.getDataDictionary(); // retrieve each pbgl line object and validate boolean valid = true; // object code is required ObjectCode objectCode = pendingBudgetConstructionGeneralLedger.getFinancialObject(); // this code calls a local version (not AccountingLineRuleHelper) of isValidObjectCode to add the bad value to the error // message if (isAdd) { valid &= isValidObjectCode(objectCode, pendingBudgetConstructionGeneralLedger.getFinancialObjectCode(), dd, KFSConstants.FINANCIAL_OBJECT_CODE_PROPERTY_NAME); } else { valid &= isValidObjectCode(objectCode, pendingBudgetConstructionGeneralLedger.getFinancialObjectCode(), dd, TARGET_ERROR_PROPERTY_NAME); } // sub object is not required if (StringUtils.isNotBlank(pendingBudgetConstructionGeneralLedger.getFinancialSubObjectCode()) && !pendingBudgetConstructionGeneralLedger.getFinancialSubObjectCode().equalsIgnoreCase(KFSConstants.getDashFinancialSubObjectCode())) { SubObjectCode subObjectCode = pendingBudgetConstructionGeneralLedger.getFinancialSubObject(); // this code calls a local version (not AccountingLineRuleHelper) of isValidSubObjectCode to add the bad value to the // error message if (isAdd) { valid &= isValidSubObjectCode(subObjectCode, pendingBudgetConstructionGeneralLedger.getFinancialSubObjectCode(), dd, KFSConstants.FINANCIAL_SUB_OBJECT_CODE_PROPERTY_NAME); } else { valid &= isValidSubObjectCode(subObjectCode, pendingBudgetConstructionGeneralLedger.getFinancialSubObjectCode(), dd, TARGET_ERROR_PROPERTY_NAME); } } return valid; } /** * Validates a single primitive in a BO * * @param object * @param attributeName * @param errorPrefix * @param validateRequired */ protected void validatePrimitiveFromDescriptor(Object object, String attributeName, String errorPrefix, boolean validateRequired) { try { PropertyDescriptor attributeDescriptor = PropertyUtils.getPropertyDescriptor(object, attributeName); validatePrimitiveFromDescriptor(object.getClass().getName(), object, attributeDescriptor, "", true); } catch (NoSuchMethodException e) { throw new InfrastructureException("unable to find propertyDescriptor for property '" + attributeName + "'", e); } catch (IllegalAccessException e) { throw new InfrastructureException("unable to access propertyDescriptor for property '" + attributeName + "'", e); } catch (InvocationTargetException e) { throw new InfrastructureException("unable to invoke methods for property '" + attributeName + "'", e); } } /** * Validates a primitive in a BO * * @param entryName * @param object * @param propertyDescriptor * @param errorPrefix * @param validateRequired */ protected void validatePrimitiveFromDescriptor(String entryName, Object object, PropertyDescriptor propertyDescriptor, String errorPrefix, boolean validateRequired) { // validate the primitive attributes if defined in the dictionary if (null != propertyDescriptor && dataDictionaryService.isAttributeDefined(entryName, propertyDescriptor.getName())) { Object value = ObjectUtils.getPropertyValue(object, propertyDescriptor.getName()); Class propertyType = propertyDescriptor.getPropertyType(); if (TypeUtils.isStringClass(propertyType) || TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) || TypeUtils.isTemporalClass(propertyType)) { // check value format against dictionary if (value != null && StringUtils.isNotBlank(value.toString())) { if (!TypeUtils.isTemporalClass(propertyType)) { SpringContext.getBean(DictionaryValidationService.class).validate( object, entryName, propertyDescriptor.getName(), false); } } else if (validateRequired) { SpringContext.getBean(DictionaryValidationService.class).validate( object, entryName, propertyDescriptor.getName(), true); } } } } protected boolean isObjectTypeAllowed(Collection<String> paramValues, PendingBudgetConstructionGeneralLedger accountingLine, MessageMap errors, boolean isRevenue, boolean isAdd) { boolean isAllowed = true; if (paramValues != null) { if (!paramValues.contains(accountingLine.getFinancialObject().getFinancialObjectTypeCode())) { isAllowed = false; String targetErrorProperty; if (isAdd) { targetErrorProperty = KFSPropertyConstants.FINANCIAL_OBJECT_CODE; } else { targetErrorProperty = TARGET_ERROR_PROPERTY_NAME; } if (isRevenue) { this.putError(errors, targetErrorProperty, BCKeyConstants.ERROR_BUDGET_OBJECT_TYPE_INVALID_REVENUE, isAdd, accountingLine.getFinancialObjectCode(),accountingLine.getFinancialObject().getFinancialObjectTypeCode()); } else { this.putError(errors, targetErrorProperty, BCKeyConstants.ERROR_BUDGET_OBJECT_TYPE_INVALID_EXPENSE, isAdd, accountingLine.getFinancialObjectCode(),accountingLine.getFinancialObject().getFinancialObjectTypeCode()); } } } else { isAllowed = false; } return isAllowed; } protected boolean isBudgetAggregationAllowed(Collection<String> paramValues, PendingBudgetConstructionGeneralLedger accountingLine, MessageMap errors, boolean isAdd) { boolean isAllowed = true; if (paramValues != null) { if (!paramValues.contains(accountingLine.getFinancialObject().getFinancialBudgetAggregationCd())) { isAllowed = false; this.putError(errors, KFSPropertyConstants.FINANCIAL_OBJECT_CODE, KFSKeyConstants.ERROR_DOCUMENT_INCORRECT_OBJ_CODE_WITH_BUDGET_AGGREGATION, isAdd, accountingLine.getFinancialObjectCode(), accountingLine.getFinancialObject().getFinancialBudgetAggregationCd()); } } else { isAllowed = false; } return isAllowed; } protected boolean isNewLineUnique(BudgetConstructionDocument budgetConstructionDocument, PendingBudgetConstructionGeneralLedger newLine, MessageMap errors, boolean isRevenue) { boolean isUnique = true; List<PendingBudgetConstructionGeneralLedger> existingLines; if (isRevenue) { existingLines = budgetConstructionDocument.getPendingBudgetConstructionGeneralLedgerRevenueLines(); } else { existingLines = budgetConstructionDocument.getPendingBudgetConstructionGeneralLedgerExpenditureLines(); } if (BudgetConstructionRuleUtil.hasExistingPBGLLine(existingLines, newLine)) { isUnique = false; errors.putError(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, BCKeyConstants.ERROR_BUDGET_LINE_EXISTS, newLine.getFinancialObjectCode() + "," + newLine.getFinancialSubObjectCode()); } return isUnique; } protected boolean isNonWagesAccountNotLaborObject(BudgetConstructionDocument budgetConstructionDocument, PendingBudgetConstructionGeneralLedger accountingLine, MessageMap errors, boolean isAdd) { boolean isAllowed = true; if (budgetConstructionDocument.getAccount().getSubFundGroup() == null || !budgetConstructionDocument.getAccount().getSubFundGroup().isSubFundGroupWagesIndicator()) { if (accountingLine.getLaborObject() != null) { isAllowed = false; this.putError(errors, KFSPropertyConstants.FINANCIAL_OBJECT_CODE, BCKeyConstants.ERROR_LABOR_OBJECT_IN_NOWAGES_ACCOUNT, isAdd, accountingLine.getFinancialObjectCode()); } } return isAllowed; } protected boolean isNotFringeBenefitObject(Collection<String> paramValues, PendingBudgetConstructionGeneralLedger accountingLine, MessageMap errors, boolean isAdd) { boolean isAllowed = true; if (paramValues != null) { if (accountingLine.getLaborObject() != null) { if (paramValues.contains(accountingLine.getLaborObject().getFinancialObjectFringeOrSalaryCode())) { isAllowed = false; this.putError(errors, KFSPropertyConstants.FINANCIAL_OBJECT_CODE, BCKeyConstants.ERROR_FRINGE_BENEFIT_OBJECT_NOT_ALLOWED, isAdd, accountingLine.getFinancialObjectCode()); } } } else { isAllowed = false; } return isAllowed; } protected boolean isNotSalarySettingOnly(Collection<String> fundGroupParamValues, Collection<String> subfundGroupParamValues, BudgetConstructionDocument budgetConstructionDocument, PendingBudgetConstructionGeneralLedger accountingLine, MessageMap errors, boolean isRevenue, boolean isAdd) { boolean isAllowed = true; // check if account belongs to a fund or subfund that only allows salary setting lines AccountSalarySettingOnlyCause retVal = budgetParameterService.isSalarySettingOnlyAccount(budgetConstructionDocument); if (retVal != AccountSalarySettingOnlyCause.MISSING_PARAM) { if (retVal != AccountSalarySettingOnlyCause.NONE) { // the line must use an object that is a detail salary labor object if (isRevenue || accountingLine.getLaborObject() == null || !accountingLine.getLaborObject().isDetailPositionRequiredIndicator()) { isAllowed = false; if (retVal == AccountSalarySettingOnlyCause.FUND || retVal == AccountSalarySettingOnlyCause.FUND_AND_SUBFUND) { this.putError(errors, KFSPropertyConstants.FINANCIAL_OBJECT_CODE, BCKeyConstants.ERROR_SALARY_SETTING_OBJECT_ONLY, isAdd, "fund " + budgetConstructionDocument.getAccount().getSubFundGroup().getFundGroupCode()); } if (retVal == AccountSalarySettingOnlyCause.SUBFUND || retVal == AccountSalarySettingOnlyCause.FUND_AND_SUBFUND) { this.putError(errors, KFSPropertyConstants.FINANCIAL_OBJECT_CODE, BCKeyConstants.ERROR_SALARY_SETTING_OBJECT_ONLY, isAdd, "subfund " + budgetConstructionDocument.getAccount().getSubFundGroup().getSubFundGroupCode()); } } } } else { // missing system parameter this.putError(errors, KFSPropertyConstants.FINANCIAL_OBJECT_CODE, BCKeyConstants.ERROR_SALARY_SETTING_OBJECT_ONLY_NO_PARAMETER, isAdd, budgetConstructionDocument.getAccount().getSubFundGroup().getFundGroupCode() + "," + budgetConstructionDocument.getAccount().getSubFundGroup().getSubFundGroupCode()); isAllowed = false; } return isAllowed; } /** * runs rule checks that don't allow a budget * * @param budgetConstructionDocument * @param propertyName * @param errors * @param isAdd * @param isDocumentAdd * @return */ protected boolean isBudgetAllowed(BudgetConstructionDocument budgetConstructionDocument, String propertyName, MessageMap errors, boolean isAdd, boolean isDocumentAdd) { boolean isAllowed = true; SimpleDateFormat tdf = new SimpleDateFormat("MM/dd/yyyy hh:mm a"); // is account closed? if (!budgetConstructionDocument.getAccount().isActive()) { isAllowed = false; this.putError(errors, propertyName, KFSKeyConstants.ERROR_CLOSED, isAdd, "account: " + budgetConstructionDocument.getAccountNumber()); } // is account expiration no budget allowed, currently < 1/1/(byfy-2)? Calendar expDate = BudgetConstructionRuleUtil.getNoBudgetAllowedExpireDate(budgetConstructionDocument.getUniversityFiscalYear()); if (budgetConstructionDocument.getAccount().isExpired(expDate)) { isAllowed = false; this.putError(errors, propertyName, BCKeyConstants.ERROR_NO_BUDGET_ALLOWED, isAdd, budgetConstructionDocument.getAccountNumber(), tdf.format(budgetConstructionDocument.getAccount().getAccountExpirationDate())); } // is account a cash control account if (budgetConstructionDocument.getAccount().getBudgetRecordingLevelCode().equalsIgnoreCase(BCConstants.BUDGET_RECORDING_LEVEL_N)) { isAllowed = false; this.putError(errors, propertyName, BCKeyConstants.ERROR_BUDGET_RECORDING_LEVEL_NOT_ALLOWED, isAdd, budgetConstructionDocument.getAccountNumber(), BCConstants.BUDGET_RECORDING_LEVEL_N); } // grab the service instance that will be needed by all the validate methods DataDictionary dd = dataDictionaryService.getDataDictionary(); if (StringUtils.isNotBlank(budgetConstructionDocument.getSubAccountNumber()) && !budgetConstructionDocument.getSubAccountNumber().equalsIgnoreCase(KFSConstants.getDashSubAccountNumber())) { SubAccount subAccount = budgetConstructionDocument.getSubAccount(); // is subacct inactive or not exist? // this code calls a local version (not AccountingLineRuleHelper) of isValidSubAccount // to add the bad value to the error message if (isAdd) { if (isDocumentAdd) { isAllowed &= this.isValidSubAccount(subAccount, budgetConstructionDocument.getSubAccountNumber(), dd, KFSPropertyConstants.SUB_ACCOUNT_NUMBER); } else { isAllowed &= this.isValidSubAccount(subAccount, budgetConstructionDocument.getSubAccountNumber(), dd, propertyName); } } else { isAllowed &= this.isValidSubAccount(subAccount, budgetConstructionDocument.getSubAccountNumber(), dd, TARGET_ERROR_PROPERTY_NAME); } // is subacct type cost share? // this hack is here since kuldev is missing one to one instances // and the RI ojb mapping produces an error when attempting to test if the // A21SubAccount attached to the document's SubAccount is null Map<String, Object> searchCriteria = new HashMap<String, Object>(); searchCriteria.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, budgetConstructionDocument.getChartOfAccountsCode()); searchCriteria.put(KFSPropertyConstants.ACCOUNT_NUMBER, budgetConstructionDocument.getAccountNumber()); searchCriteria.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, budgetConstructionDocument.getSubAccountNumber()); A21SubAccount a21SubAccount = businessObjectService.findByPrimaryKey(A21SubAccount.class, searchCriteria); if (ObjectUtils.isNotNull(a21SubAccount)) { if (a21SubAccount.getSubAccountTypeCode().equalsIgnoreCase(KFSConstants.SubAccountType.COST_SHARE)) { isAllowed = false; this.putError(errors, KFSPropertyConstants.SUB_ACCOUNT_NUMBER, BCKeyConstants.ERROR_SUB_ACCOUNT_TYPE_NOT_ALLOWED, isAdd, budgetConstructionDocument.getSubAccountNumber(), KFSConstants.SubAccountType.COST_SHARE); } } } return isAllowed; } public boolean isValidAccount(Account account, String value, DataDictionary dataDictionary, String errorPropertyName) { String label = dataDictionary.getBusinessObjectEntry(Account.class.getName()).getAttributeDefinition(KFSConstants.ACCOUNT_NUMBER_PROPERTY_NAME).getShortLabel(); // make sure it exists if (ObjectUtils.isNull(account)) { GlobalVariables.getMessageMap().putError(errorPropertyName, KFSKeyConstants.ERROR_EXISTENCE, label + ":" + value); return false; } return true; } public boolean isValidSubAccount(SubAccount subAccount, String value, DataDictionary dataDictionary, String errorPropertyName) { String label = dataDictionary.getBusinessObjectEntry(SubAccount.class.getName()).getAttributeDefinition(KFSConstants.SUB_ACCOUNT_NUMBER_PROPERTY_NAME).getShortLabel(); // make sure it exists if (ObjectUtils.isNull(subAccount)) { GlobalVariables.getMessageMap().putError(errorPropertyName, KFSKeyConstants.ERROR_EXISTENCE, label + ":" + value); return false; } // check to make sure it is active if (!subAccount.isActive()) { GlobalVariables.getMessageMap().putError(errorPropertyName, KFSKeyConstants.ERROR_DOCUMENT_SUB_ACCOUNT_INACTIVE, label + ":" + value); return false; } return true; } /** * Runs existence and active tests on the SubObjectCode reference This method is differenct than the one in * AccountingLineRuleHelper in that it adds the bad value to the errormessage This method signature should probably be added to * AccountingLineRuleHelper * * @param subObjectCode * @param value * @param dataDictionary * @param errorPropertyName * @return */ public boolean isValidSubObjectCode(SubObjectCode subObjectCode, String value, DataDictionary dataDictionary, String errorPropertyName) { String label = dataDictionary.getBusinessObjectEntry(SubObjectCode.class.getName()).getAttributeDefinition(KFSConstants.FINANCIAL_SUB_OBJECT_CODE_PROPERTY_NAME).getShortLabel(); // make sure it exists if (ObjectUtils.isNull(subObjectCode)) { GlobalVariables.getMessageMap().putError(errorPropertyName, KFSKeyConstants.ERROR_EXISTENCE, label + ":" + value); return false; } // check active flag if (!subObjectCode.isActive()) { GlobalVariables.getMessageMap().putError(errorPropertyName, KFSKeyConstants.ERROR_INACTIVE, label + ":" + value); return false; } return true; } /** * Runs existence and active tests on the ObjectCode reference This method is differenct than the one in * AccountingLineRuleHelper in that it adds the bad value to the errormessage This method signature should probably be added to * AccountingLineRuleHelper * * @param objectCode * @param value * @param dataDictionary * @param errorPropertyName * @return */ public boolean isValidObjectCode(ObjectCode objectCode, String value, DataDictionary dataDictionary, String errorPropertyName) { String label = dataDictionary.getBusinessObjectEntry(ObjectCode.class.getName()).getAttributeDefinition(KFSConstants.FINANCIAL_OBJECT_CODE_PROPERTY_NAME).getShortLabel(); // make sure it exists if (ObjectUtils.isNull(objectCode)) { GlobalVariables.getMessageMap().putError(errorPropertyName, KFSKeyConstants.ERROR_EXISTENCE, label + ":" + value); return false; } // check active status if (!objectCode.isFinancialObjectActiveCode()) { GlobalVariables.getMessageMap().putError(errorPropertyName, KFSKeyConstants.ERROR_INACTIVE, label + ":" + value); return false; } return true; } /** * puts error to errormap for propertyName if isAdd, otherwise the property name is replaced with value of * TARGET_ERROR_PROPERTY_NAME * * @param propertyName * @param errorKey * @param isAdd * @param errorParameters */ protected void putError(MessageMap errors, String propertyName, String errorKey, boolean isAdd, String... errorParameters) { if (isAdd) { errors.putError(propertyName, errorKey, errorParameters); } else { errors.putError(TARGET_ERROR_PROPERTY_NAME, errorKey, errorParameters); } } }