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 }