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 }