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}