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 }