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.ld.document.validation.impl; 017 018 import java.util.ArrayList; 019 import java.util.HashMap; 020 import java.util.List; 021 import java.util.Map; 022 import java.util.Set; 023 import java.util.Map.Entry; 024 025 import org.apache.commons.lang.StringUtils; 026 import org.kuali.kfs.module.ld.LaborKeyConstants; 027 import org.kuali.kfs.module.ld.LaborPropertyConstants; 028 import org.kuali.kfs.module.ld.businessobject.ExpenseTransferAccountingLine; 029 import org.kuali.kfs.module.ld.businessobject.ExpenseTransferSourceAccountingLine; 030 import org.kuali.kfs.module.ld.businessobject.LedgerBalance; 031 import org.kuali.kfs.module.ld.document.LaborExpenseTransferDocumentBase; 032 import org.kuali.kfs.sys.KFSConstants; 033 import org.kuali.kfs.sys.KFSPropertyConstants; 034 import org.kuali.kfs.sys.ObjectUtil; 035 import org.kuali.kfs.sys.businessobject.SystemOptions; 036 import org.kuali.kfs.sys.context.SpringContext; 037 import org.kuali.kfs.sys.document.validation.GenericValidation; 038 import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent; 039 import org.kuali.kfs.sys.service.OptionsService; 040 import org.kuali.rice.kns.document.Document; 041 import org.kuali.rice.kns.service.BusinessObjectService; 042 import org.kuali.rice.kns.util.GlobalVariables; 043 import org.kuali.rice.kns.util.KualiDecimal; 044 import org.kuali.kfs.module.ld.document.BenefitExpenseTransferDocument; 045 import org.kuali.kfs.module.ld.util.ConsolidationUtil; 046 /** 047 * check to ensure totals of accounting lines in source and target sections match by pay FY + pay period 048 * 049 * @param accountingDocument the given document 050 * @return true if the given accounting lines in source and target match by pay fy and pp 051 */ 052 public class LaborExpenseTransferValidTransferAmountValidation extends GenericValidation { 053 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborExpenseTransferValidTransferAmountValidation.class); 054 055 private Document documentForValidation; 056 057 /** 058 * Validates before the document routes 059 * @see org.kuali.kfs.validation.Validation#validate(java.lang.Object[]) 060 */ 061 public boolean validate(AttributedDocumentEvent event) { 062 boolean result = true; 063 064 Document documentForValidation = getDocumentForValidation(); 065 066 LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) documentForValidation; 067 068 List sourceLines = expenseTransferDocument.getSourceAccountingLines(); 069 070 Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap = this.getAccountingLineGroupMap(sourceLines, ExpenseTransferSourceAccountingLine.class); 071 072 boolean isValidTransferAmount = isValidTransferAmount(accountingLineGroupMap); 073 if (!isValidTransferAmount) { 074 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES, LaborKeyConstants.ERROR_TRANSFER_AMOUNT_EXCEED_MAXIMUM); 075 return false; 076 } 077 078 return result; 079 } 080 081 /** 082 * determine whether the amount to be tranferred is only up to the amount in ledger balance for a given pay period 083 * 084 * @param accountingDocument the given accounting document 085 * @return true if the amount to be tranferred is only up to the amount in ledger balance for a given pay period; otherwise, 086 * false 087 */ 088 protected boolean isValidTransferAmount(Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap) { 089 Set<Entry<String, ExpenseTransferAccountingLine>> entrySet = accountingLineGroupMap.entrySet(); 090 091 for (Entry<String, ExpenseTransferAccountingLine> entry : entrySet) { 092 ExpenseTransferAccountingLine accountingLine = entry.getValue(); 093 Map<String, Object> fieldValues = this.buildFieldValueMap(accountingLine); 094 095 KualiDecimal balanceAmount = getBalanceAmount(fieldValues, accountingLine.getPayrollEndDateFiscalPeriodCode()); 096 KualiDecimal transferAmount = accountingLine.getAmount(); 097 098 // the tranferred amount cannot greater than the balance amount 099 if (balanceAmount.abs().isLessThan(transferAmount.abs())) { 100 return false; 101 } 102 103 // a positive amount cannot be transferred if the balance amount is negative 104 if (balanceAmount.isNegative() && transferAmount.isPositive()) { 105 return false; 106 } 107 } 108 return true; 109 } 110 /** 111 * build the field-value maps throught the given accouting line 112 * 113 * @param accountingLine the given accounting line 114 * @return the field-value maps built from the given accouting line 115 */ 116 protected Map<String, Object> buildFieldValueMap(ExpenseTransferAccountingLine accountingLine) { 117 Map<String, Object> fieldValues = new HashMap<String, Object>(); 118 119 fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, accountingLine.getPayrollEndDateFiscalYear()); 120 fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, accountingLine.getChartOfAccountsCode()); 121 fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountingLine.getAccountNumber()); 122 123 String subAccountNumber = accountingLine.getSubAccountNumber(); 124 subAccountNumber = StringUtils.isBlank(subAccountNumber) ? KFSConstants.getDashSubAccountNumber() : subAccountNumber; 125 fieldValues.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, subAccountNumber); 126 127 fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, accountingLine.getBalanceTypeCode()); 128 fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, accountingLine.getFinancialObjectCode()); 129 130 SystemOptions options = SpringContext.getBean(OptionsService.class).getOptions(accountingLine.getPayrollEndDateFiscalYear()); 131 fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE, options.getFinObjTypeExpenditureexpCd()); 132 133 String subObjectCode = accountingLine.getFinancialSubObjectCode(); 134 subObjectCode = StringUtils.isBlank(subObjectCode) ? KFSConstants.getDashFinancialSubObjectCode() : subObjectCode; 135 fieldValues.put(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE, subObjectCode); 136 137 fieldValues.put(KFSPropertyConstants.EMPLID, accountingLine.getEmplid()); 138 fieldValues.put(KFSPropertyConstants.POSITION_NUMBER, accountingLine.getPositionNumber()); 139 140 Document documentForValidation = getDocumentForValidation(); 141 if (documentForValidation instanceof BenefitExpenseTransferDocument) { 142 fieldValues.remove(KFSPropertyConstants.EMPLID); 143 fieldValues.remove(KFSPropertyConstants.POSITION_NUMBER); 144 } 145 return fieldValues; 146 } 147 148 /** 149 * Groups the accounting lines by the specified key fields 150 * 151 * @param accountingLines the given accounting lines that are stored in a list 152 * @param clazz the class type of given accounting lines 153 * @return the accounting line groups 154 */ 155 protected Map<String, ExpenseTransferAccountingLine> getAccountingLineGroupMap(List<ExpenseTransferAccountingLine> accountingLines, Class clazz) { 156 Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap = new HashMap<String, ExpenseTransferAccountingLine>(); 157 158 for (ExpenseTransferAccountingLine accountingLine : accountingLines) { 159 String stringKey = ObjectUtil.buildPropertyMap(accountingLine, defaultKeyOfExpenseTransferAccountingLine()).toString(); 160 ExpenseTransferAccountingLine line = null; 161 162 if (accountingLineGroupMap.containsKey(stringKey)) { 163 line = accountingLineGroupMap.get(stringKey); 164 KualiDecimal amount = line.getAmount(); 165 line.setAmount(amount.add(accountingLine.getAmount())); 166 } 167 else { 168 try { 169 line = (ExpenseTransferAccountingLine) clazz.newInstance(); 170 ObjectUtil.buildObject(line, accountingLine); 171 accountingLineGroupMap.put(stringKey, line); 172 } 173 catch (Exception e) { 174 LOG.error("Cannot create a new instance of ExpenseTransferAccountingLine" + e); 175 } 176 } 177 } 178 return accountingLineGroupMap; 179 } 180 181 /** 182 * Gets the default key of ExpenseTransferAccountingLine 183 * 184 * @return the default key of ExpenseTransferAccountingLine 185 */ 186 protected List<String> defaultKeyOfExpenseTransferAccountingLine() { 187 List<String> defaultKey = new ArrayList<String>(); 188 189 defaultKey.add(KFSPropertyConstants.POSTING_YEAR); 190 defaultKey.add(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE); 191 defaultKey.add(KFSPropertyConstants.ACCOUNT_NUMBER); 192 defaultKey.add(KFSPropertyConstants.SUB_ACCOUNT_NUMBER); 193 194 defaultKey.add(KFSPropertyConstants.BALANCE_TYPE_CODE); 195 defaultKey.add(KFSPropertyConstants.FINANCIAL_OBJECT_CODE); 196 defaultKey.add(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE); 197 198 defaultKey.add(KFSPropertyConstants.EMPLID); 199 defaultKey.add(KFSPropertyConstants.POSITION_NUMBER); 200 201 defaultKey.add(LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_YEAR); 202 defaultKey.add(LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_PERIOD_CODE); 203 204 return defaultKey; 205 } 206 207 /** 208 * get the amount for a given period from a ledger balance that has the given values for specified fileds 209 * 210 * @param fieldValues the given fields and their values 211 * @param periodCode the given period 212 * @return the amount for a given period from the qualified ledger balance 213 */ 214 protected KualiDecimal getBalanceAmount(Map<String, Object> fieldValues, String periodCode) { 215 if (periodCode == null) { 216 return KualiDecimal.ZERO; 217 } 218 219 fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, KFSConstants.BALANCE_TYPE_ACTUAL); 220 KualiDecimal actualBalanceAmount = this.getBalanceAmountOfGivenPeriod(fieldValues, periodCode); 221 222 fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, KFSConstants.BALANCE_TYPE_A21); 223 KualiDecimal effortBalanceAmount = this.getBalanceAmountOfGivenPeriod(fieldValues, periodCode); 224 225 return actualBalanceAmount.add(effortBalanceAmount); 226 } 227 228 /** 229 * Gets the balance amount of a given period 230 * 231 * @param fieldValues 232 * @param periodCode 233 * @return 234 */ 235 protected KualiDecimal getBalanceAmountOfGivenPeriod(Map<String, Object> fieldValues, String periodCode) { 236 KualiDecimal balanceAmount = KualiDecimal.ZERO; 237 List<LedgerBalance> ledgerBalances = (List<LedgerBalance>) SpringContext.getBean(BusinessObjectService.class).findMatching(LedgerBalance.class, fieldValues); 238 239 LedgerBalance summaryBalance = new LedgerBalance(); 240 for(LedgerBalance balance : ledgerBalances) { 241 ConsolidationUtil.sumLedgerBalances(summaryBalance, balance); 242 } 243 244 return summaryBalance.getAmount(periodCode); 245 } 246 247 /** 248 * Gets the documentForValidation attribute. 249 * @return Returns the documentForValidation. 250 */ 251 public Document getDocumentForValidation() { 252 return documentForValidation; 253 } 254 255 /** 256 * Sets the accountingDocumentForValidation attribute value. 257 * @param documentForValidation The documentForValidation to set. 258 */ 259 public void setDocumentForValidation(Document documentForValidation) { 260 this.documentForValidation = documentForValidation; 261 } 262 }