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.document.LaborExpenseTransferDocumentBase;
033 import org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument;
034 import org.kuali.kfs.sys.KFSKeyConstants;
035 import org.kuali.kfs.sys.KFSPropertyConstants;
036 import org.kuali.kfs.sys.ObjectUtil;
037 import org.kuali.kfs.sys.businessobject.AccountingLine;
038 import org.kuali.kfs.sys.document.AccountingDocument;
039 import org.kuali.kfs.sys.document.validation.GenericValidation;
040 import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
041 import org.kuali.rice.kns.document.Document;
042 import org.kuali.rice.kns.util.GlobalVariables;
043 import org.kuali.rice.kns.util.KualiDecimal;
044
045 /**
046 * check to ensure totals of accounting lines in source and target sections match by pay FY + pay period
047 *
048 * @param accountingDocument the given document
049 * @return true if the given accounting lines in source and target match by pay fy and pp
050 */
051 public class LaborExpenseTransferAccountingLineTotalsMatchByPayFYAndPayPeriodValidation extends GenericValidation {
052 private Document documentForValidation;
053
054 /**
055 * Validates before the document routes
056 * @see org.kuali.kfs.validation.Validation#validate(java.lang.Object[])
057 */
058 public boolean validate(AttributedDocumentEvent event) {
059 boolean result = true;
060
061 Document documentForValidation = getDocumentForValidation();
062
063 LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) documentForValidation;
064
065 List sourceLines = expenseTransferDocument.getSourceAccountingLines();
066 List targetLines = expenseTransferDocument.getTargetAccountingLines();
067
068 // check to ensure totals of accounting lines in source and target sections match
069 if (!isAccountingLineTotalsMatchByPayFYAndPayPeriod(sourceLines, targetLines)) {
070 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES, LaborKeyConstants.ACCOUNTING_LINE_TOTALS_BY_PAYFY_PAYPERIOD_MISMATCH_ERROR);
071 return false;
072 }
073
074 return result;
075 }
076
077 /**
078 * This method calls other methods to check if all source and target accounting lines match between each set by pay fiscal year
079 * and pay period, returning true if the totals match, false otherwise.
080 *
081 * @param sourceLines
082 * @param targetLines
083 * @return
084 */
085 public boolean isAccountingLineTotalsMatchByPayFYAndPayPeriod(List sourceLines, List targetLines) {
086 boolean isValid = true;
087
088 // sum source lines by pay fy and pay period, store in map by key PayFY+PayPeriod
089 Map sourceLinesMap = sumAccountingLineAmountsByPayFYAndPayPeriod(sourceLines);
090
091 // sum source lines by pay fy and pay period, store in map by key PayFY+PayPeriod
092 Map targetLinesMap = sumAccountingLineAmountsByPayFYAndPayPeriod(targetLines);
093
094 // if totals don't match by PayFY+PayPeriod categories, then add error message
095 if (compareAccountingLineTotalsByPayFYAndPayPeriod(sourceLinesMap, targetLinesMap) == false) {
096 isValid = false;
097 }
098
099 return isValid;
100 }
101
102 /**
103 * This method sums the totals of each accounting line, making an entry in a map for each unique pay fiscal year and pay period.
104 *
105 * @param accountingLines
106 * @return
107 */
108 protected Map sumAccountingLineAmountsByPayFYAndPayPeriod(List accountingLines) {
109
110 ExpenseTransferAccountingLine line = null;
111 KualiDecimal linesAmount = KualiDecimal.ZERO;
112 Map linesMap = new HashMap();
113 String payFYPeriodKey = null;
114
115 // go through source lines adding amounts to appropriate place in map
116 for (Iterator i = accountingLines.iterator(); i.hasNext();) {
117 // initialize
118 line = (ExpenseTransferAccountingLine) i.next();
119 linesAmount = KualiDecimal.ZERO;
120
121 // create hash key
122 payFYPeriodKey = createPayFYPeriodKey(line.getPayrollEndDateFiscalYear(), line.getPayrollEndDateFiscalPeriodCode());
123
124 // if entry exists, pull from hash
125 if (linesMap.containsKey(payFYPeriodKey)) {
126 linesAmount = (KualiDecimal) linesMap.get(payFYPeriodKey);
127 }
128
129 // update and store
130 linesAmount = linesAmount.add(line.getAmount());
131 linesMap.put(payFYPeriodKey, linesAmount);
132 }
133
134 return linesMap;
135 }
136
137 /**
138 * This method returns a String that is a concatenation of pay fiscal year and pay period code.
139 *
140 * @param payFiscalYear
141 * @param payPeriodCode
142 * @return
143 */
144 protected String createPayFYPeriodKey(Integer payFiscalYear, String payPeriodCode) {
145
146 StringBuffer payFYPeriodKey = new StringBuffer();
147
148 payFYPeriodKey.append(payFiscalYear);
149 payFYPeriodKey.append(payPeriodCode);
150
151 return payFYPeriodKey.toString();
152 }
153
154
155 /**
156 * This method checks that the total amount of labor ledger accounting lines in the document's FROM section is equal to the
157 * total amount on the labor ledger accounting lines TO section for each unique combination of pay fiscal year and pay period. A
158 * value of true is returned if all amounts for each unique combination between source and target accounting lines match, false
159 * otherwise.
160 *
161 * @param sourceLinesMap
162 * @param targetLinesMap
163 * @return
164 */
165 protected boolean compareAccountingLineTotalsByPayFYAndPayPeriod(Map sourceLinesMap, Map targetLinesMap) {
166
167 boolean isValid = true;
168 Map.Entry entry = null;
169 String currentKey = null;
170 KualiDecimal sourceLinesAmount = KualiDecimal.ZERO;
171 KualiDecimal targetLinesAmount = KualiDecimal.ZERO;
172
173
174 // Loop through source lines comparing against target lines
175 for (Iterator i = sourceLinesMap.entrySet().iterator(); i.hasNext() && isValid;) {
176 // initialize
177 entry = (Map.Entry) i.next();
178 currentKey = (String) entry.getKey();
179 sourceLinesAmount = (KualiDecimal) entry.getValue();
180
181 if (targetLinesMap.containsKey(currentKey)) {
182 targetLinesAmount = (KualiDecimal) targetLinesMap.get(currentKey);
183
184 // return false if the matching key values do not total each other
185 if (sourceLinesAmount.compareTo(targetLinesAmount) != 0) {
186 isValid = false;
187 }
188
189 }
190 else {
191 isValid = false;
192 }
193 }
194
195 /*
196 * Now loop through target lines comparing against source lines. This finds missing entries from either direction (source or
197 * target)
198 */
199 for (Iterator i = targetLinesMap.entrySet().iterator(); i.hasNext() && isValid;) {
200 // initialize
201 entry = (Map.Entry) i.next();
202 currentKey = (String) entry.getKey();
203 targetLinesAmount = (KualiDecimal) entry.getValue();
204
205 if (sourceLinesMap.containsKey(currentKey)) {
206 sourceLinesAmount = (KualiDecimal) sourceLinesMap.get(currentKey);
207
208 // return false if the matching key values do not total each other
209 if (targetLinesAmount.compareTo(sourceLinesAmount) != 0) {
210 isValid = false;
211 }
212
213 }
214 else {
215 isValid = false;
216 }
217 }
218 return isValid;
219 }
220
221 /**
222 * Gets the documentForValidation attribute.
223 * @return Returns the documentForValidation.
224 */
225 public Document getDocumentForValidation() {
226 return documentForValidation;
227 }
228
229 /**
230 * Sets the documentForValidation attribute value.
231 * @param documentForValidation The documentForValidation to set.
232 */
233 public void setDocumentForValidation(Document documentForValidation) {
234 this.documentForValidation = documentForValidation;
235 }
236 }