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