/*
* 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());
}
}