/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.gl.document; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; import java.util.List; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.gl.businessobject.CorrectionChange; import org.kuali.kfs.gl.businessobject.CorrectionChangeGroup; import org.kuali.kfs.gl.businessobject.CorrectionCriteria; import org.kuali.kfs.gl.businessobject.OriginEntryFull; import org.kuali.kfs.gl.businessobject.OriginEntryStatistics; import org.kuali.kfs.gl.businessobject.options.OriginEntryFieldFinder; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; /** * This class provides utility methods for the correction document */ public class CorrectionDocumentUtils { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CorrectionDocumentUtils.class); public static final int DEFAULT_RECORD_COUNT_FUNCTIONALITY_LIMIT = 1000; /** * The GLCP document will always be on restricted functionality mode, regardless of input group size */ public static final int RECORD_COUNT_FUNCTIONALITY_LIMIT_IS_NONE = 0; /** * The GLCP document will never be on restricted functionality mode, regardless of input group size */ public static final int RECORD_COUNT_FUNCTIONALITY_LIMIT_IS_UNLIMITED = -1; public static final int DEFAULT_RECORDS_PER_PAGE = 10; /** * This method returns the limit for record count functionality * * @return limit for record count functionality */ public static int getRecordCountFunctionalityLimit() { String limitString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(GeneralLedgerCorrectionProcessDocument.class, KFSConstants.GeneralLedgerCorrectionProcessApplicationParameterKeys.RECORD_COUNT_FUNCTIONALITY_LIMIT); if (limitString != null) { return Integer.valueOf(limitString); } return DEFAULT_RECORD_COUNT_FUNCTIONALITY_LIMIT; } /** * This method returns the number of records per page * * @return number of records per page */ public static int getRecordsPerPage() { String limitString = SpringContext.getBean(ParameterService.class).getParameterValueAsString(GeneralLedgerCorrectionProcessDocument.class, KFSConstants.GeneralLedgerCorrectionProcessApplicationParameterKeys.RECORDS_PER_PAGE); if (limitString != null) { return Integer.valueOf(limitString); } return DEFAULT_RECORDS_PER_PAGE; } /** * This method returns true if input group size is greater than or equal to record count functionality limit * * @param inputGroupSize size of input groups * @param recordCountFunctionalityLimit limit for record count functionality * @return true if input group size is greater than or equal to record count functionality limit */ public static boolean isRestrictedFunctionalityMode(int inputGroupSize, int recordCountFunctionalityLimit) { return (recordCountFunctionalityLimit != CorrectionDocumentUtils.RECORD_COUNT_FUNCTIONALITY_LIMIT_IS_UNLIMITED && inputGroupSize >= recordCountFunctionalityLimit) || recordCountFunctionalityLimit == CorrectionDocumentUtils.RECORD_COUNT_FUNCTIONALITY_LIMIT_IS_NONE; } /** * 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 * not blank * * @param correctionCriteria validated correction criteria * @return true if correction criteria is valid for adding */ public static boolean validCorrectionCriteriaForAdding(CorrectionCriteria correctionCriteria) { String fieldName = correctionCriteria.getCorrectionFieldName(); if (StringUtils.isBlank(fieldName)) { return false; } return true; } /** * 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 * * @param correctionCriteria validated correction criteria * @return true if correction criteria is valid for saving */ public static boolean validCorrectionCriteriaForSaving(CorrectionCriteria correctionCriteria) { return correctionCriteria == null || (StringUtils.isBlank(correctionCriteria.getCorrectionFieldName()) && StringUtils.isBlank(correctionCriteria.getCorrectionFieldValue())); } /** * 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 * blank * * @param correctionChange validated correction change * @return true is correction change is valid for adding */ public static boolean validCorrectionChangeForAdding(CorrectionChange correctionChange) { String fieldName = correctionChange.getCorrectionFieldName(); if (StringUtils.isBlank(fieldName)) { return false; } return true; } /** * 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 * * @param correctionCriteria validated correction criteria * @return true if correction change is valid for saving (i.e. correction change is null or correction field name and field * value are blank) */ public static boolean validCorrectionChangeForSaving(CorrectionChange correctionChange) { return correctionChange == null || (StringUtils.isBlank(correctionChange.getCorrectionFieldName()) && StringUtils.isBlank(correctionChange.getCorrectionFieldValue())); } /** * Sets all origin entries' entry IDs to null within the collection. * * @param originEntries collection of origin entries */ public static void setAllEntryIdsToNull(Collection<OriginEntryFull> originEntries) { for (OriginEntryFull entry : originEntries) { entry.setEntryId(null); } } /** * Sets all origin entries' entry IDs to be sequential starting from 0 in the collection * * @param originEntries collection of origin entries */ public static void setSequentialEntryIds(Collection<OriginEntryFull> originEntries) { int index = 0; for (OriginEntryFull entry : originEntries) { entry.setEntryId(new Integer(index)); index++; } } /** * Returns whether an origin entry matches the passed in criteria. If both the criteria and actual value are both String types * and are empty, null, or whitespace only, then they will match. * * @param cc correction criteria to test against origin entry * @param oe origin entry to test * @return true if origin entry matches the passed in criteria */ public static boolean entryMatchesCriteria(CorrectionCriteria cc, OriginEntryFull oe) { OriginEntryFieldFinder oeff = new OriginEntryFieldFinder(); Object fieldActualValue = oe.getFieldValue(cc.getCorrectionFieldName()); String fieldTestValue = StringUtils.isBlank(cc.getCorrectionFieldValue()) ? "" : cc.getCorrectionFieldValue(); String fieldType = oeff.getFieldType(cc.getCorrectionFieldName()); String fieldActualValueString = convertToString(fieldActualValue, fieldType); if ("String".equals(fieldType) || "sw".equals(cc.getCorrectionOperatorCode()) || "ew".equals(cc.getCorrectionOperatorCode()) || "ct".equals(cc.getCorrectionOperatorCode())) { return compareStringData(cc, fieldTestValue, fieldActualValueString); } int compareTo = 0; try { if (fieldActualValue == null) { return false; } if ("Integer".equals(fieldType)) { compareTo = ((Integer) fieldActualValue).compareTo(Integer.parseInt(fieldTestValue)); } if ("KualiDecimal".equals(fieldType)) { compareTo = ((KualiDecimal) fieldActualValue).compareTo(new KualiDecimal(Double.parseDouble(fieldTestValue))); } if ("BigDecimal".equals(fieldType)) { compareTo = ((BigDecimal) fieldActualValue).compareTo(new BigDecimal(Double.parseDouble(fieldTestValue))); } if ("Date".equals(fieldType)) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); compareTo = ((Date) fieldActualValue).compareTo(df.parse(fieldTestValue)); } } catch (Exception e) { // any exception while parsing data return false return false; } return compareTo(compareTo, cc.getCorrectionOperatorCode()); } /** * Compares string data * * @param cc criteria * @param fieldTestValue test value * @param fieldActualValueString actual value * @return flag true if matches with criteria */ public static boolean compareStringData(CorrectionCriteria cc, String fieldTestValue, String fieldActualValueString) { if ("eq".equals(cc.getCorrectionOperatorCode())) { return fieldActualValueString.equals(fieldTestValue); } else if ("ne".equals(cc.getCorrectionOperatorCode())) { return (!fieldActualValueString.equals(fieldTestValue)); } else if ("sw".equals(cc.getCorrectionOperatorCode())) { return fieldActualValueString.startsWith(fieldTestValue); } else if ("ew".equals(cc.getCorrectionOperatorCode())) { return fieldActualValueString.endsWith(fieldTestValue); } else if ("ct".equals(cc.getCorrectionOperatorCode())) { return (fieldActualValueString.indexOf(fieldTestValue) > -1); } else if ("lt".equals(cc.getCorrectionOperatorCode())) { return (fieldActualValueString.compareTo(fieldTestValue) < 0); } else if ("le".equals(cc.getCorrectionOperatorCode())) { return (fieldActualValueString.compareTo(fieldTestValue) <= 0); } else if ("gt".equals(cc.getCorrectionOperatorCode())) { return (fieldActualValueString.compareTo(fieldTestValue) > 0); } else if ("ge".equals(cc.getCorrectionOperatorCode())) { return (fieldActualValueString.compareTo(fieldTestValue) >= 0); } throw new IllegalArgumentException("Unknown operator: " + cc.getCorrectionOperatorCode()); } /** * Returns true is compared indicator matches * * @param compareTo * @param operatorCode * @return */ public static boolean compareTo(int compareTo, String operatorCode) { if ("eq".equals(operatorCode)) { return (compareTo == 0); } else if ("ne".equals(operatorCode)) { return (compareTo != 0); } else if ("lt".equals(operatorCode)) { return (compareTo < 0); } else if ("le".equals(operatorCode)) { return (compareTo <= 0); } else if ("gt".equals(operatorCode)) { return (compareTo > 0); } else if ("ge".equals(operatorCode)) { return (compareTo >= 0); } throw new IllegalArgumentException("Unknown operator: " + operatorCode); } /** * Converts the value into a string, with the appropriate formatting * * @param fieldActualValue actual field value * @param fieldType field type (i.e. "String", "Integer", "Date") * @return String object value as a string */ public static String convertToString(Object fieldActualValue, String fieldType) { if (fieldActualValue == null) { return ""; } if ("String".equals(fieldType)) { return (String) fieldActualValue; } else if ("Integer".equals(fieldType)) { Integer i = (Integer) fieldActualValue; return i.toString(); } else if ("KualiDecimal".equals(fieldType)) { KualiDecimal kd = (KualiDecimal) fieldActualValue; return kd.toString(); } else if ("BigDecimal".equals(fieldType)) { BigDecimal bd = (BigDecimal) fieldActualValue; return bd.toString(); } else if ("Date".equals(fieldType)) { Date d = (Date) fieldActualValue; SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); return df.format(d); } return ""; } /** * Applies a list of change criteria groups to an origin entry. Note that the returned value, if not null, is a reference to the * same instance as the origin entry passed in (i.e. intentional side effect) * * @param entry origin entry * @param matchCriteriaOnly if true and no criteria match, then this method will return null * @param changeCriteriaGroups list of change criteria groups to apply * @return the passed in entry instance, or null (see above) */ public static OriginEntryFull applyCriteriaToEntry(OriginEntryFull entry, boolean matchCriteriaOnly, List<CorrectionChangeGroup> changeCriteriaGroups) { if (matchCriteriaOnly && !doesEntryMatchAnyCriteriaGroups(entry, changeCriteriaGroups)) { return null; } for (CorrectionChangeGroup ccg : changeCriteriaGroups) { int matches = 0; for (CorrectionCriteria cc : ccg.getCorrectionCriteria()) { if (entryMatchesCriteria(cc, entry)) { matches++; } } // If they all match, change it if (matches == ccg.getCorrectionCriteria().size()) { for (CorrectionChange change : ccg.getCorrectionChange()) { // Change the row entry.setFieldValue(change.getCorrectionFieldName(), change.getCorrectionFieldValue()); } } } return entry; } /** * Returns whether the entry matches any of the criteria groups * * @param entry origin entry * @param groups collection of correction change group * @return true if origin entry matches any of the criteria groups */ public static boolean doesEntryMatchAnyCriteriaGroups(OriginEntryFull entry, Collection<CorrectionChangeGroup> groups) { boolean anyGroupMatch = false; for (CorrectionChangeGroup ccg : groups) { int matches = 0; for (CorrectionCriteria cc : ccg.getCorrectionCriteria()) { if (CorrectionDocumentUtils.entryMatchesCriteria(cc, entry)) { matches++; } } // If they all match, change it if (matches == ccg.getCorrectionCriteria().size()) { anyGroupMatch = true; break; } } return anyGroupMatch; } /** * Computes the statistics (credit amount, debit amount, row count) of a collection of origin entries. * * @param entries list of orgin entry entries * @return {@link OriginEntryStatistics} statistics (credit amount, debit amount, row count) of a collection of origin entries. */ public static OriginEntryStatistics getStatistics(Collection<OriginEntryFull> entries) { OriginEntryStatistics oes = new OriginEntryStatistics(); for (OriginEntryFull oe : entries) { updateStatisticsWithEntry(oe, oes); } return oes; } /** * Returns whether the origin entry represents a debit * * @param oe origin entry * @return true if origin entry represents a debit */ public static boolean isDebit(OriginEntryFull oe) { return (KFSConstants.GL_DEBIT_CODE.equals(oe.getTransactionDebitCreditCode())); } /** * Returns whether the origin entry represents a budget * * @param oe origin entry * @return true if origin entry represents a budget */ public static boolean isBudget(OriginEntryFull oe) { return KFSConstants.GL_BUDGET_CODE.equals(oe.getTransactionDebitCreditCode()); } /** * Returns whether the origin entry represents a credit * * @param oe origin entry * @return true if origin entry represents a credit */ public static boolean isCredit(OriginEntryFull oe) { return KFSConstants.GL_CREDIT_CODE.equals(oe.getTransactionDebitCreditCode()); } /** * Given an instance of statistics, it adds information from the passed in entry to the statistics * * @param entry origin entry * @param statistics adds statistics from the passed in origin entry to the passed in statistics */ public static void updateStatisticsWithEntry(OriginEntryFull entry, OriginEntryStatistics statistics) { statistics.incrementCount(); if (isDebit(entry)) { statistics.addDebit(entry.getTransactionLedgerEntryAmount()); } else if (isCredit(entry)) { statistics.addCredit(entry.getTransactionLedgerEntryAmount()); } else { statistics.addBudget(entry.getTransactionLedgerEntryAmount()); } } /** * Sets document with the statistics data * * @param statistics origin entry statistics that are being used to set document * @param document document with statistic information being set */ public static void copyStatisticsToDocument(OriginEntryStatistics statistics, GeneralLedgerCorrectionProcessDocument document) { document.setCorrectionCreditTotalAmount(statistics.getCreditTotalAmount()); document.setCorrectionDebitTotalAmount(statistics.getDebitTotalAmount()); document.setCorrectionBudgetTotalAmount(statistics.getBudgetTotalAmount()); document.setCorrectionRowCount(statistics.getRowCount()); } }