/* * 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.module.tem.document.validation.impl; import java.math.BigDecimal; import java.util.Collection; import java.util.List; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.module.tem.TemConstants.PerDiemType; import org.kuali.kfs.module.tem.TemKeyConstants; import org.kuali.kfs.module.tem.TemPropertyConstants; import org.kuali.kfs.module.tem.businessobject.ActualExpense; import org.kuali.kfs.module.tem.businessobject.MileageRate; import org.kuali.kfs.module.tem.businessobject.PerDiemExpense; import org.kuali.kfs.module.tem.businessobject.TemExpense; import org.kuali.kfs.module.tem.document.TravelDocument; import org.kuali.kfs.sys.document.validation.GenericValidation; import org.kuali.kfs.sys.util.KfsDateUtils; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.DictionaryValidationService; import org.kuali.rice.krad.util.ErrorMessage; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.MessageMap; import org.kuali.rice.krad.util.ObjectUtils; public abstract class TemDocumentExpenseLineValidation extends GenericValidation { protected boolean warningOnly = true; protected BusinessObjectService businessObjectService; protected DictionaryValidationService dictionaryValidationService; /** * This method validates following rules 1.Validated whether mileage, hosted meal or lodging specified in perdiem section, if * specified alerts the user * * @param actualExpense * @param document * @return boolean */ protected boolean validatePerDiemRules(ActualExpense actualExpense, TravelDocument document) { boolean success = true; PerDiemType perDiem = null; // Check to see if the same expense type is been entered in PerDiem if (actualExpense.isMileage() && isPerDiemMileageEntered(actualExpense.getExpenseDate(), document.getPerDiemExpenses())) { perDiem = PerDiemType.mileage; } else if (actualExpense.isLodging() && isPerDiemLodgingEntered(actualExpense.getExpenseDate(), document.getPerDiemExpenses())) { perDiem = PerDiemType.lodging; } else if (actualExpense.isIncidental() && isPerDiemIncidentalEntered(actualExpense.getExpenseDate(), document.getPerDiemExpenses())) { perDiem = PerDiemType.incidentals; } else if (actualExpense.isBreakfast() && isPerDiemBreakfastEntered(actualExpense.getExpenseDate(), document.getPerDiemExpenses())) { perDiem = PerDiemType.breakfast; } else if (actualExpense.isLunch() && isPerDiemLunchEntered(actualExpense.getExpenseDate(), document.getPerDiemExpenses())) { perDiem = PerDiemType.lunch; } else if (actualExpense.isDinner() && isPerDiemDinnerEntered(actualExpense.getExpenseDate(), document.getPerDiemExpenses())) { perDiem = PerDiemType.dinner; } if (perDiem != null){ success &= addPerDiemError(perDiem, isWarningOnly()); } return success; } /** * Adds a per diem error * * @param perDiem the type of the per diem to error about * @param warningOnly whether error should be a true error or a warning only * @return true if rule suceeded, false otherwise */ protected boolean addPerDiemError(PerDiemType perDiem, boolean warningOnly) { boolean success = true; if (warningOnly) { GlobalVariables.getMessageMap().putWarning(TemPropertyConstants.EXEPENSE_TYPE_OBJECT_CODE_ID, TemKeyConstants.WARNING_DUPLICATE_EXPENSE, perDiem.label); } else { success = false; GlobalVariables.getMessageMap().putError(TemPropertyConstants.EXEPENSE_TYPE_OBJECT_CODE_ID, TemKeyConstants.WARNING_DUPLICATE_EXPENSE, perDiem.label); final String matchingErrorPath = StringUtils.join(GlobalVariables.getMessageMap().getErrorPath(), ".") + "." + TemPropertyConstants.EXEPENSE_TYPE_OBJECT_CODE_ID; GlobalVariables.getMessageMap().removeAllWarningMessagesForProperty(matchingErrorPath); } return success; } /** * PerDiem Mileage entered * * @param perDiemExpenses * @return */ protected boolean isPerDiemMileageEntered(java.sql.Date expenseDate, List<PerDiemExpense> perDiemExpenses) { for (PerDiemExpense perDiemExpenseLine : perDiemExpenses) { if (KfsDateUtils.isSameDay(perDiemExpenseLine.getMileageDate(), expenseDate) && ObjectUtils.isNotNull(perDiemExpenseLine.getMiles()) && perDiemExpenseLine.getMiles() > 0) { return true; } } return false; } /** * PerDiem Meals entered * * @param perDiemExpenses * @return */ protected boolean isPerDiemMealsEntered(java.sql.Date expenseDate, List<PerDiemExpense> perDiemExpenses) { for (PerDiemExpense perDiemExpenseLine : perDiemExpenses) { if (KfsDateUtils.isSameDay(perDiemExpenseLine.getMileageDate(), expenseDate) && ObjectUtils.isNotNull(perDiemExpenseLine.getMealsAndIncidentals()) && (perDiemExpenseLine.getMealsAndIncidentals().isGreaterThan(KualiDecimal.ZERO) || perDiemExpenseLine.getMealsTotal().isGreaterThan(KualiDecimal.ZERO))) { return true; } } return false; } /** * PerDiem Lodging entered * * @param perDiemExpenses * @return */ protected boolean isPerDiemLodgingEntered(java.sql.Date expenseDate, List<PerDiemExpense> perDiemExpenses) { for (PerDiemExpense perDiemExpenseLine : perDiemExpenses) { if (KfsDateUtils.isSameDay(perDiemExpenseLine.getMileageDate(), expenseDate) && ObjectUtils.isNotNull(perDiemExpenseLine.getLodgingTotal()) && perDiemExpenseLine.getLodgingTotal().isGreaterThan(KualiDecimal.ZERO)) { return true; } } return false; } /** * PerDiem Incidental entered * * @param perDiemExpenses * @return */ protected boolean isPerDiemIncidentalEntered(java.sql.Date expenseDate, List<PerDiemExpense> perDiemExpenses) { for (PerDiemExpense perDiemExpenseLine : perDiemExpenses) { if (KfsDateUtils.isSameDay(perDiemExpenseLine.getMileageDate(), expenseDate) && perDiemExpenseLine.getLodgingTotal().isGreaterThan(KualiDecimal.ZERO)) { return true; } } return false; } /** * Loops through given per diem expenses to see if the per diem associated with the given expense date has a breakfast amount added * @param expenseDate the date of the actual expense we're validating * @param perDiemExpenses the per diem expenses associated with the document we're validating * @return true if breakfast is added; false otherwise */ protected boolean isPerDiemBreakfastEntered(java.sql.Date expenseDate, List<PerDiemExpense> perDiemExpenses) { for (PerDiemExpense perDiemExpenseLine : perDiemExpenses) { if (KfsDateUtils.isSameDay(perDiemExpenseLine.getMileageDate(), expenseDate) && perDiemExpenseLine.getBreakfastValue().isGreaterThan(KualiDecimal.ZERO)) { return true; } } return false; } /** * Loops through given per diem expenses to see if the per diem associated with the given expense date has a lunch amount added * @param expenseDate the date of the actual expense we're validating * @param perDiemExpenses the per diem expenses associated with the document we're validating * @return true if lunch is added; false otherwise */ protected boolean isPerDiemLunchEntered(java.sql.Date expenseDate, List<PerDiemExpense> perDiemExpenses) { for (PerDiemExpense perDiemExpenseLine : perDiemExpenses) { if (KfsDateUtils.isSameDay(perDiemExpenseLine.getMileageDate(), expenseDate) && perDiemExpenseLine.getLunchValue().isGreaterThan(KualiDecimal.ZERO)) { return true; } } return false; } /** * Loops through given per diem expenses to see if the per diem associated with the given expense date has a dinner amount added * @param expenseDate the date of the actual expense we're validating * @param perDiemExpenses the per diem expenses associated with the document we're validating * @return true if dinner is added; false otherwise */ protected boolean isPerDiemDinnerEntered(java.sql.Date expenseDate, List<PerDiemExpense> perDiemExpenses) { for (PerDiemExpense perDiemExpenseLine : perDiemExpenses) { if (KfsDateUtils.isSameDay(perDiemExpenseLine.getMileageDate(), expenseDate) && perDiemExpenseLine.getDinnerValue().isGreaterThan(KualiDecimal.ZERO)) { return true; } } return false; } /** * This method validated following rules * * 1.Raises warning if note is not entered * * @param actualExpense * @param document * @return boolean */ protected boolean validateAirfareRules(ActualExpense actualExpense, TravelDocument document) { boolean success = true; MessageMap message = GlobalVariables.getMessageMap(); if (actualExpense.isAirfare() && StringUtils.isEmpty(actualExpense.getDescription())) { boolean justificationAdded = false; for ( TemExpense expenseDetail : actualExpense.getExpenseDetails()) { justificationAdded = StringUtils.isEmpty(expenseDetail.getDescription()) ? false : true; } if (!message.containsMessageKey(TemPropertyConstants.TEM_ACTUAL_EXPENSE_NOTCE) && !justificationAdded){ if (isWarningOnly()) { message.putWarning(TemPropertyConstants.TEM_ACTUAL_EXPENSE_NOTCE, TemKeyConstants.WARNING_NOTES_JUSTIFICATION); } else { removeWarningsForProperty(TemPropertyConstants.TEM_ACTUAL_EXPENSE_NOTCE); message.putError(TemPropertyConstants.TEM_ACTUAL_EXPENSE_NOTCE, TemKeyConstants.WARNING_NOTES_JUSTIFICATION); success = false; } } } return success; } /** * Removes any warnings associated with the given property name * @param propertyName the property name to remove warning messages for */ protected void removeWarningsForProperty(String propertyName) { final String fullPropertyName = StringUtils.join(GlobalVariables.getMessageMap().getErrorPath(), ".")+"."+propertyName; List<ErrorMessage> warningMessages = GlobalVariables.getMessageMap().getWarningMessagesForProperty(fullPropertyName); if (warningMessages != null && !warningMessages.isEmpty()) { GlobalVariables.getMessageMap().removeAllWarningMessagesForProperty(fullPropertyName); } } /** * This method validates following rules * * 1.If the Approval Required flag = "Y" for the rental car type, the document routes to the Special Request approver routing. * Display a warning, "Enter justification in the Notes field". (This is under Rental Car Specific Rules) * * No warning if there is already an error * * @param expenseDetail * @param document * @return */ public boolean validateRentalCarRules(ActualExpense expense, TravelDocument document) { boolean success = true; MessageMap message = GlobalVariables.getMessageMap(); // Check to see care rental needs special request approval if (ObjectUtils.isNotNull(expense.getExpenseTypeObjectCode()) && expense.getExpenseTypeObjectCode().isSpecialRequestRequired()) { if (StringUtils.isBlank(expense.getDescription())) { boolean justificationAdded = false; for ( TemExpense expenseDetail : expense.getExpenseDetails()) { justificationAdded = StringUtils.isEmpty(expenseDetail.getDescription()) ? false : true; } if (!message.containsMessageKey(TemPropertyConstants.TEM_ACTUAL_EXPENSE_NOTCE) && !justificationAdded){ if (isWarningOnly()) { message.putWarning(TemPropertyConstants.TEM_ACTUAL_EXPENSE_NOTCE, TemKeyConstants.WARNING_NOTES_JUSTIFICATION); } else { removeWarningsForProperty(TemPropertyConstants.TEM_ACTUAL_EXPENSE_NOTCE); message.putError(TemPropertyConstants.TEM_ACTUAL_EXPENSE_NOTCE, TemKeyConstants.WARNING_NOTES_JUSTIFICATION); success = false; } } } } return success; } /** * Get the maximum Mileage rate * * @return */ public BigDecimal getMaxMileageRate() { BigDecimal maxMileage = BigDecimal.ZERO; Collection<MileageRate> mileageRates = getBusinessObjectService().findAll(MileageRate.class); for (MileageRate mileageRate : mileageRates) { if (mileageRate.getRate().compareTo(maxMileage) > 0) { maxMileage = mileageRate.getRate(); } } return maxMileage; } public boolean isWarningOnly() { return warningOnly; } public void setWarningOnly(boolean warningOnly) { this.warningOnly = warningOnly; } /** * * @return */ public final DictionaryValidationService getDictionaryValidationService() { return dictionaryValidationService; } public void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) { this.dictionaryValidationService = dictionaryValidationService; } public BusinessObjectService getBusinessObjectService() { return businessObjectService; } public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } }