001/* 002 * Copyright 2007 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 */ 016package org.kuali.ole.gl.document; 017 018import java.math.BigDecimal; 019import java.text.SimpleDateFormat; 020import java.util.Collection; 021import java.util.Date; 022import java.util.List; 023 024import org.apache.commons.lang.StringUtils; 025import org.kuali.ole.gl.businessobject.CorrectionChange; 026import org.kuali.ole.gl.businessobject.CorrectionChangeGroup; 027import org.kuali.ole.gl.businessobject.CorrectionCriteria; 028import org.kuali.ole.gl.businessobject.OriginEntryFull; 029import org.kuali.ole.gl.businessobject.OriginEntryStatistics; 030import org.kuali.ole.gl.businessobject.options.OriginEntryFieldFinder; 031import org.kuali.ole.sys.OLEConstants; 032import org.kuali.ole.sys.context.SpringContext; 033import org.kuali.rice.core.api.util.type.KualiDecimal; 034import org.kuali.rice.coreservice.framework.parameter.ParameterService; 035 036/** 037 * This class provides utility methods for the correction document 038 */ 039public class CorrectionDocumentUtils { 040 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CorrectionDocumentUtils.class); 041 public static final int DEFAULT_RECORD_COUNT_FUNCTIONALITY_LIMIT = 1000; 042 043 /** 044 * The GLCP document will always be on restricted functionality mode, regardless of input group size 045 */ 046 public static final int RECORD_COUNT_FUNCTIONALITY_LIMIT_IS_NONE = 0; 047 048 /** 049 * The GLCP document will never be on restricted functionality mode, regardless of input group size 050 */ 051 public static final int RECORD_COUNT_FUNCTIONALITY_LIMIT_IS_UNLIMITED = -1; 052 053 public static final int DEFAULT_RECORDS_PER_PAGE = 10; 054 055 /** 056 * This method returns the limit for record count functionality 057 * 058 * @return limit for record count functionality 059 */ 060 public static int getRecordCountFunctionalityLimit() { 061 String limitString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(GeneralLedgerCorrectionProcessDocument.class, OLEConstants.GeneralLedgerCorrectionProcessApplicationParameterKeys.RECORD_COUNT_FUNCTIONALITY_LIMIT); 062 if (limitString != null) { 063 return Integer.valueOf(limitString); 064 } 065 066 return DEFAULT_RECORD_COUNT_FUNCTIONALITY_LIMIT; 067 } 068 069 /** 070 * This method returns the number of records per page 071 * 072 * @return number of records per page 073 */ 074 public static int getRecordsPerPage() { 075 String limitString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(GeneralLedgerCorrectionProcessDocument.class, OLEConstants.GeneralLedgerCorrectionProcessApplicationParameterKeys.RECORDS_PER_PAGE); 076 if (limitString != null) { 077 return Integer.valueOf(limitString); 078 } 079 return DEFAULT_RECORDS_PER_PAGE; 080 } 081 082 /** 083 * This method returns true if input group size is greater than or equal to record count functionality limit 084 * 085 * @param inputGroupSize size of input groups 086 * @param recordCountFunctionalityLimit limit for record count functionality 087 * @return true if input group size is greater than or equal to record count functionality limit 088 */ 089 public static boolean isRestrictedFunctionalityMode(int inputGroupSize, int recordCountFunctionalityLimit) { 090 return (recordCountFunctionalityLimit != CorrectionDocumentUtils.RECORD_COUNT_FUNCTIONALITY_LIMIT_IS_UNLIMITED && inputGroupSize >= recordCountFunctionalityLimit) || recordCountFunctionalityLimit == CorrectionDocumentUtils.RECORD_COUNT_FUNCTIONALITY_LIMIT_IS_NONE; 091 } 092 093 /** 094 * When a correction criterion is about to be added to a group, this will check if it is valid, meaning that the field name is 095 * not blank 096 * 097 * @param correctionCriteria validated correction criteria 098 * @return true if correction criteria is valid for adding 099 */ 100 public static boolean validCorrectionCriteriaForAdding(CorrectionCriteria correctionCriteria) { 101 String fieldName = correctionCriteria.getCorrectionFieldName(); 102 if (StringUtils.isBlank(fieldName)) { 103 return false; 104 } 105 return true; 106 } 107 108 /** 109 * When a document is about to be saved, this will check if it is valid, meaning that the field name and value are both blank 110 * 111 * @param correctionCriteria validated correction criteria 112 * @return true if correction criteria is valid for saving 113 */ 114 public static boolean validCorrectionCriteriaForSaving(CorrectionCriteria correctionCriteria) { 115 return correctionCriteria == null || (StringUtils.isBlank(correctionCriteria.getCorrectionFieldName()) && StringUtils.isBlank(correctionCriteria.getCorrectionFieldValue())); 116 } 117 118 /** 119 * When a correction change is about to be added to a group, this will check if it is valid, meaning that the field name is not 120 * blank 121 * 122 * @param correctionChange validated correction change 123 * @return true is correction change is valid for adding 124 */ 125 public static boolean validCorrectionChangeForAdding(CorrectionChange correctionChange) { 126 String fieldName = correctionChange.getCorrectionFieldName(); 127 if (StringUtils.isBlank(fieldName)) { 128 return false; 129 } 130 return true; 131 } 132 133 /** 134 * When a document is about to be saved, this will check if it is valid, meaning that the field name and value are both blank 135 * 136 * @param correctionCriteria validated correction criteria 137 * @return true if correction change is valid for saving (i.e. correction change is null or correction field name and field 138 * value are blank) 139 */ 140 public static boolean validCorrectionChangeForSaving(CorrectionChange correctionChange) { 141 return correctionChange == null || (StringUtils.isBlank(correctionChange.getCorrectionFieldName()) && StringUtils.isBlank(correctionChange.getCorrectionFieldValue())); 142 } 143 144 /** 145 * Sets all origin entries' entry IDs to null within the collection. 146 * 147 * @param originEntries collection of origin entries 148 */ 149 public static void setAllEntryIdsToNull(Collection<OriginEntryFull> originEntries) { 150 for (OriginEntryFull entry : originEntries) { 151 entry.setEntryId(null); 152 } 153 } 154 155 /** 156 * Sets all origin entries' entry IDs to be sequential starting from 0 in the collection 157 * 158 * @param originEntries collection of origin entries 159 */ 160 public static void setSequentialEntryIds(Collection<OriginEntryFull> originEntries) { 161 int index = 0; 162 for (OriginEntryFull entry : originEntries) { 163 entry.setEntryId(new Integer(index)); 164 index++; 165 } 166 } 167 168 /** 169 * Returns whether an origin entry matches the passed in criteria. If both the criteria and actual value are both String types 170 * and are empty, null, or whitespace only, then they will match. 171 * 172 * @param cc correction criteria to test against origin entry 173 * @param oe origin entry to test 174 * @return true if origin entry matches the passed in criteria 175 */ 176 public static boolean entryMatchesCriteria(CorrectionCriteria cc, OriginEntryFull oe) { 177 OriginEntryFieldFinder oeff = new OriginEntryFieldFinder(); 178 Object fieldActualValue = oe.getFieldValue(cc.getCorrectionFieldName()); 179 String fieldTestValue = StringUtils.isBlank(cc.getCorrectionFieldValue()) ? "" : cc.getCorrectionFieldValue(); 180 String fieldType = oeff.getFieldType(cc.getCorrectionFieldName()); 181 182 String fieldActualValueString = convertToString(fieldActualValue, fieldType); 183 184 if ("String".equals(fieldType) || "sw".equals(cc.getCorrectionOperatorCode()) || "ew".equals(cc.getCorrectionOperatorCode()) || "ct".equals(cc.getCorrectionOperatorCode())) { 185 return compareStringData(cc, fieldTestValue, fieldActualValueString); 186 } 187 int compareTo = 0; 188 try { 189 if (fieldActualValue == null) { 190 return false; 191 } 192 if ("Integer".equals(fieldType)) { 193 compareTo = ((Integer) fieldActualValue).compareTo(Integer.parseInt(fieldTestValue)); 194 } 195 if ("KualiDecimal".equals(fieldType)) { 196 compareTo = ((KualiDecimal) fieldActualValue).compareTo(new KualiDecimal(Double.parseDouble(fieldTestValue))); 197 } 198 if ("BigDecimal".equals(fieldType)) { 199 compareTo = ((BigDecimal) fieldActualValue).compareTo(new BigDecimal(Double.parseDouble(fieldTestValue))); 200 201 } 202 if ("Date".equals(fieldType)) { 203 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 204 compareTo = ((Date) fieldActualValue).compareTo(df.parse(fieldTestValue)); 205 } 206 } 207 catch (Exception e) { 208 // any exception while parsing data return false 209 return false; 210 } 211 return compareTo(compareTo, cc.getCorrectionOperatorCode()); 212 } 213 214 215 /** 216 * Compares string data 217 * 218 * @param cc criteria 219 * @param fieldTestValue test value 220 * @param fieldActualValueString actual value 221 * @return flag true if matches with criteria 222 */ 223 public static boolean compareStringData(CorrectionCriteria cc, String fieldTestValue, String fieldActualValueString) { 224 if ("eq".equals(cc.getCorrectionOperatorCode())) { 225 return fieldActualValueString.equals(fieldTestValue); 226 } 227 else if ("ne".equals(cc.getCorrectionOperatorCode())) { 228 return (!fieldActualValueString.equals(fieldTestValue)); 229 } 230 else if ("sw".equals(cc.getCorrectionOperatorCode())) { 231 return fieldActualValueString.startsWith(fieldTestValue); 232 } 233 else if ("ew".equals(cc.getCorrectionOperatorCode())) { 234 return fieldActualValueString.endsWith(fieldTestValue); 235 } 236 else if ("ct".equals(cc.getCorrectionOperatorCode())) { 237 return (fieldActualValueString.indexOf(fieldTestValue) > -1); 238 } 239 else if ("lt".equals(cc.getCorrectionOperatorCode())) { 240 return (fieldActualValueString.compareTo(fieldTestValue) < 0); 241 } 242 else if ("le".equals(cc.getCorrectionOperatorCode())) { 243 return (fieldActualValueString.compareTo(fieldTestValue) <= 0); 244 } 245 else if ("gt".equals(cc.getCorrectionOperatorCode())) { 246 return (fieldActualValueString.compareTo(fieldTestValue) > 0); 247 } 248 else if ("ge".equals(cc.getCorrectionOperatorCode())) { 249 return (fieldActualValueString.compareTo(fieldTestValue) >= 0); 250 } 251 throw new IllegalArgumentException("Unknown operator: " + cc.getCorrectionOperatorCode()); 252 } 253 254 /** 255 * Returns true is compared indicator matches 256 * 257 * @param compareTo 258 * @param operatorCode 259 * @return 260 */ 261 public static boolean compareTo(int compareTo, String operatorCode) { 262 if ("eq".equals(operatorCode)) { 263 return (compareTo == 0); 264 } 265 else if ("ne".equals(operatorCode)) { 266 return (compareTo != 0); 267 } 268 else if ("lt".equals(operatorCode)) { 269 return (compareTo < 0); 270 } 271 else if ("le".equals(operatorCode)) { 272 return (compareTo <= 0); 273 } 274 else if ("gt".equals(operatorCode)) { 275 return (compareTo > 0); 276 } 277 else if ("ge".equals(operatorCode)) { 278 return (compareTo >= 0); 279 } 280 throw new IllegalArgumentException("Unknown operator: " + operatorCode); 281 } 282 283 /** 284 * Converts the value into a string, with the appropriate formatting 285 * 286 * @param fieldActualValue actual field value 287 * @param fieldType field type (i.e. "String", "Integer", "Date") 288 * @return String object value as a string 289 */ 290 public static String convertToString(Object fieldActualValue, String fieldType) { 291 if (fieldActualValue == null) { 292 return ""; 293 } 294 if ("String".equals(fieldType)) { 295 return (String) fieldActualValue; 296 } 297 else if ("Integer".equals(fieldType)) { 298 Integer i = (Integer) fieldActualValue; 299 return i.toString(); 300 } 301 else if ("KualiDecimal".equals(fieldType)) { 302 KualiDecimal kd = (KualiDecimal) fieldActualValue; 303 return kd.toString(); 304 } 305 else if ("BigDecimal".equals(fieldType)) { 306 BigDecimal bd = (BigDecimal) fieldActualValue; 307 return bd.toString(); 308 } 309 else if ("Date".equals(fieldType)) { 310 Date d = (Date) fieldActualValue; 311 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 312 return df.format(d); 313 } 314 return ""; 315 } 316 317 /** 318 * Applies a list of change criteria groups to an origin entry. Note that the returned value, if not null, is a reference to the 319 * same instance as the origin entry passed in (i.e. intentional side effect) 320 * 321 * @param entry origin entry 322 * @param matchCriteriaOnly if true and no criteria match, then this method will return null 323 * @param changeCriteriaGroups list of change criteria groups to apply 324 * @return the passed in entry instance, or null (see above) 325 */ 326 public static OriginEntryFull applyCriteriaToEntry(OriginEntryFull entry, boolean matchCriteriaOnly, List<CorrectionChangeGroup> changeCriteriaGroups) { 327 if (matchCriteriaOnly && !doesEntryMatchAnyCriteriaGroups(entry, changeCriteriaGroups)) { 328 return null; 329 } 330 331 for (CorrectionChangeGroup ccg : changeCriteriaGroups) { 332 int matches = 0; 333 for (CorrectionCriteria cc : ccg.getCorrectionCriteria()) { 334 if (entryMatchesCriteria(cc, entry)) { 335 matches++; 336 } 337 } 338 339 // If they all match, change it 340 if (matches == ccg.getCorrectionCriteria().size()) { 341 for (CorrectionChange change : ccg.getCorrectionChange()) { 342 // Change the row 343 entry.setFieldValue(change.getCorrectionFieldName(), change.getCorrectionFieldValue()); 344 } 345 } 346 } 347 return entry; 348 } 349 350 /** 351 * Returns whether the entry matches any of the criteria groups 352 * 353 * @param entry origin entry 354 * @param groups collection of correction change group 355 * @return true if origin entry matches any of the criteria groups 356 */ 357 public static boolean doesEntryMatchAnyCriteriaGroups(OriginEntryFull entry, Collection<CorrectionChangeGroup> groups) { 358 boolean anyGroupMatch = false; 359 for (CorrectionChangeGroup ccg : groups) { 360 int matches = 0; 361 for (CorrectionCriteria cc : ccg.getCorrectionCriteria()) { 362 if (CorrectionDocumentUtils.entryMatchesCriteria(cc, entry)) { 363 matches++; 364 } 365 } 366 367 // If they all match, change it 368 if (matches == ccg.getCorrectionCriteria().size()) { 369 anyGroupMatch = true; 370 break; 371 } 372 } 373 return anyGroupMatch; 374 } 375 376 /** 377 * Computes the statistics (credit amount, debit amount, row count) of a collection of origin entries. 378 * 379 * @param entries list of orgin entry entries 380 * @return {@link OriginEntryStatistics} statistics (credit amount, debit amount, row count) of a collection of origin entries. 381 */ 382 public static OriginEntryStatistics getStatistics(Collection<OriginEntryFull> entries) { 383 OriginEntryStatistics oes = new OriginEntryStatistics(); 384 385 for (OriginEntryFull oe : entries) { 386 updateStatisticsWithEntry(oe, oes); 387 } 388 return oes; 389 } 390 391 /** 392 * Returns whether the origin entry represents a debit 393 * 394 * @param oe origin entry 395 * @return true if origin entry represents a debit 396 */ 397 public static boolean isDebit(OriginEntryFull oe) { 398 return (OLEConstants.GL_DEBIT_CODE.equals(oe.getTransactionDebitCreditCode())); 399 } 400 401 /** 402 * Returns whether the origin entry represents a budget 403 * 404 * @param oe origin entry 405 * @return true if origin entry represents a budget 406 */ 407 public static boolean isBudget(OriginEntryFull oe) { 408 return OLEConstants.GL_BUDGET_CODE.equals(oe.getTransactionDebitCreditCode()); 409 } 410 411 /** 412 * Returns whether the origin entry represents a credit 413 * 414 * @param oe origin entry 415 * @return true if origin entry represents a credit 416 */ 417 public static boolean isCredit(OriginEntryFull oe) { 418 return OLEConstants.GL_CREDIT_CODE.equals(oe.getTransactionDebitCreditCode()); 419 } 420 421 /** 422 * Given an instance of statistics, it adds information from the passed in entry to the statistics 423 * 424 * @param entry origin entry 425 * @param statistics adds statistics from the passed in origin entry to the passed in statistics 426 */ 427 public static void updateStatisticsWithEntry(OriginEntryFull entry, OriginEntryStatistics statistics) { 428 statistics.incrementCount(); 429 if (isDebit(entry)) { 430 statistics.addDebit(entry.getTransactionLedgerEntryAmount()); 431 } 432 else if (isCredit(entry)) { 433 statistics.addCredit(entry.getTransactionLedgerEntryAmount()); 434 } 435 else { 436 statistics.addBudget(entry.getTransactionLedgerEntryAmount()); 437 } 438 } 439 440 /** 441 * Sets document with the statistics data 442 * 443 * @param statistics origin entry statistics that are being used to set document 444 * @param document document with statistic information being set 445 */ 446 public static void copyStatisticsToDocument(OriginEntryStatistics statistics, GeneralLedgerCorrectionProcessDocument document) { 447 document.setCorrectionCreditTotalAmount(statistics.getCreditTotalAmount()); 448 document.setCorrectionDebitTotalAmount(statistics.getDebitTotalAmount()); 449 document.setCorrectionBudgetTotalAmount(statistics.getBudgetTotalAmount()); 450 document.setCorrectionRowCount(statistics.getRowCount()); 451 } 452}