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.gl.document;
017
018 import java.math.BigDecimal;
019 import java.text.SimpleDateFormat;
020 import java.util.Collection;
021 import java.util.Date;
022 import java.util.List;
023
024 import org.apache.commons.lang.StringUtils;
025 import org.kuali.kfs.gl.businessobject.CorrectionChange;
026 import org.kuali.kfs.gl.businessobject.CorrectionChangeGroup;
027 import org.kuali.kfs.gl.businessobject.CorrectionCriteria;
028 import org.kuali.kfs.gl.businessobject.OriginEntryFull;
029 import org.kuali.kfs.gl.businessobject.OriginEntryStatistics;
030 import org.kuali.kfs.gl.businessobject.options.OriginEntryFieldFinder;
031 import org.kuali.kfs.sys.KFSConstants;
032 import org.kuali.kfs.sys.context.SpringContext;
033 import org.kuali.rice.kns.service.ParameterService;
034 import org.kuali.rice.kns.util.KualiDecimal;
035
036 /**
037 * This class provides utility methods for the correction document
038 */
039 public 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).getParameterValue(GeneralLedgerCorrectionProcessDocument.class, KFSConstants.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).getParameterValue(GeneralLedgerCorrectionProcessDocument.class, KFSConstants.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 (KFSConstants.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 KFSConstants.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 KFSConstants.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 }