/* * 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.service.impl; import static org.kuali.kfs.module.tem.TemConstants.TravelReimbursementParameters.LODGING_OBJECT_CODE; import static org.kuali.kfs.module.tem.TemConstants.TravelReimbursementParameters.PER_DIEM_OBJECT_CODE; import java.sql.Date; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.kuali.kfs.coa.businessobject.ObjectCode; import org.kuali.kfs.module.tem.TemConstants; import org.kuali.kfs.module.tem.TemConstants.PerDiemParameter; import org.kuali.kfs.module.tem.TemConstants.TravelAuthorizationParameters; import org.kuali.kfs.module.tem.TemConstants.TravelDocTypes; import org.kuali.kfs.module.tem.TemConstants.TravelParameters; import org.kuali.kfs.module.tem.TemConstants.TravelReimbursementParameters; import org.kuali.kfs.module.tem.TemParameterConstants; import org.kuali.kfs.module.tem.batch.PerDiemLoadStep; import org.kuali.kfs.module.tem.batch.businessobject.MealBreakDownStrategy; import org.kuali.kfs.module.tem.businessobject.AccountingDistribution; import org.kuali.kfs.module.tem.businessobject.ExpenseTypeObjectCode; import org.kuali.kfs.module.tem.businessobject.MileageRate; import org.kuali.kfs.module.tem.businessobject.PerDiem; import org.kuali.kfs.module.tem.businessobject.PerDiemExpense; import org.kuali.kfs.module.tem.businessobject.TemExpense; import org.kuali.kfs.module.tem.businessobject.TravelerDetail; import org.kuali.kfs.module.tem.businessobject.TripType; import org.kuali.kfs.module.tem.dataaccess.PerDiemDao; import org.kuali.kfs.module.tem.dataaccess.TravelDocumentDao; import org.kuali.kfs.module.tem.document.TravelAuthorizationDocument; import org.kuali.kfs.module.tem.document.TravelDocument; import org.kuali.kfs.module.tem.document.TravelReimbursementDocument; import org.kuali.kfs.module.tem.document.web.struts.TravelFormBase; import org.kuali.kfs.module.tem.service.PerDiemService; import org.kuali.kfs.module.tem.service.TemExpenseService; import org.kuali.kfs.module.tem.service.TravelExpenseService; import org.kuali.kfs.module.tem.util.ExpenseUtils; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper; import org.kuali.kfs.sys.util.KfsDateUtils; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.util.ObjectUtils; import org.kuali.rice.location.api.state.State; import org.kuali.rice.location.api.state.StateService; import org.springframework.transaction.annotation.Transactional; /** * implement the service method calls defined in PerDiemService */ @Transactional public class PerDiemServiceImpl extends ExpenseServiceBase implements PerDiemService, TemExpenseService { private static Logger LOG = Logger.getLogger(PerDiemServiceImpl.class); protected DateTimeService dateTimeService; protected ParameterService parameterService; protected BusinessObjectService businessObjectService; protected StateService stateService; protected PerDiemDao perDiemDao; protected Map<String, MealBreakDownStrategy> mealBreakDownStrategies; protected String allStateCodes; protected TravelDocumentDao travelDocumentDao; protected TravelExpenseService travelExpenseService; List<PerDiem> persistedPerDiems; /** * @see org.kuali.kfs.module.tem.service.PerDiemService#breakDownMealsIncidental(java.util.List) */ @Override public <T extends PerDiem> void breakDownMealsIncidental(List<T> perDiemList) { for (T perDiem : perDiemList) { this.breakDownMealsIncidental(perDiem); } } /** * @see org.kuali.kfs.module.tem.service.PerDiemService#breakDownMealsIncidental(org.kuali.kfs.module.tem.businessobject.PerDiem) */ @Override public <T extends PerDiem> void breakDownMealsIncidental(T perDiem) { String conusIndicator = perDiem.getConusIndicator(); if (this.getMealBreakDownStrategies().containsKey(conusIndicator)) { MealBreakDownStrategy mealBreakDownStrategy = this.getMealBreakDownStrategies().get(conusIndicator); mealBreakDownStrategy.breakDown(perDiem); } else { throw new RuntimeException("Fail to meal break down strategy for the CONUS indicator: " + conusIndicator); } } /** * @see org.kuali.kfs.module.tem.service.PerDiemService#retrieveActivePerDiem() */ public <T extends PerDiem> List<T> retrieveInactivePerDiem() { Map<String, Object> fieldValues = new HashMap<String, Object>(); fieldValues.put(KFSPropertyConstants.ACTIVE, Boolean.FALSE); return (List<T>) this.getBusinessObjectService().findMatching(PerDiem.class, fieldValues); } /** * @see org.kuali.kfs.module.tem.service.PerDiemService#updateTripType(java.util.List) */ @Override public <T extends PerDiem> void updateTripType(List<T> perDiemList) { for (T perDiem : perDiemList) { this.updateTripType(perDiem); } } /** * @see org.kuali.kfs.module.tem.service.PerDiemService#updateTripType(org.kuali.kfs.module.tem.businessobject.PerDiem) */ @Override public <T extends PerDiem> void updateTripType(T perDiem) { String region = perDiem.getPrimaryDestination().getRegion().getRegionCode(); String institutionState = this.getInstitutionState(); String tripTypeCode = this.getInternationalTripTypeCode(); if(StringUtils.equals(region, institutionState)) { tripTypeCode = this.getInStateTripTypeCode(); } else if(getAllStateCodes().contains( ";" + region.toUpperCase() + ";")) { tripTypeCode = this.getOutStateTripTypeCode(); } perDiem.getPrimaryDestination().getRegion().setTripTypeCode(tripTypeCode); } /** * get institution state code defined as an application parameter * * @return institution state code */ protected String getInstitutionState() { String institutionState = this.getParameterService().getParameterValueAsString(PerDiemLoadStep.class, PerDiemParameter.INSTITUTION_STATE_PARAM_NAME); return institutionState; } /** * @see org.kuali.kfs.module.tem.service.PerDiemService#retrievePreviousPerDiem(org.kuali.kfs.module.tem.businessobject.PerDiem) */ @Override public <T extends PerDiem> List<PerDiem> retrievePreviousPerDiem(T perDiem) { return (List<PerDiem>) perDiemDao.findSimilarPerDiems(perDiem); } /** * check whether the given per diem exists in the database * * @param perDiem the given per diem * @return true if the given per diem exists in the database */ @Override public <T extends PerDiem> boolean hasExistingPerDiem(T perDiem) { if (ObjectUtils.isNull(persistedPerDiems)) { persistedPerDiems = (List<PerDiem>)businessObjectService.findAll(PerDiem.class); } boolean retval = persistedPerDiems.contains(perDiem); return retval; } /** * get out state trip type code defined as an application parameter * * @return out state trip type code */ protected String getOutStateTripTypeCode() { String outStateTripTypeCode = this.getParameterService().getParameterValueAsString(PerDiemLoadStep.class, PerDiemParameter.OUT_STATE_TRIP_TYPE_CODE_PARAM_NAME); return outStateTripTypeCode; } /** * get in state trip type code defined as an application parameter * * @return in state trip type code */ protected String getInStateTripTypeCode() { String inStateTripTypeCode = this.getParameterService().getParameterValueAsString(PerDiemLoadStep.class, PerDiemParameter.IN_STATE_TRIP_TYPE_CODE_PARAM_NAME); return inStateTripTypeCode; } /** * get international trip type code defined as an application parameter * * @return international trip type code */ protected String getInternationalTripTypeCode() { String internationalTripTypeCode = this.getParameterService().getParameterValueAsString(PerDiemLoadStep.class, PerDiemParameter.INTERNATIONAL_TRIP_TYPE_CODE_PARAM_NAME); return internationalTripTypeCode; } /** * Gets the parameterService attribute. * * @return Returns the parameterService. */ @Override public ParameterService getParameterService() { return parameterService; } /** * Sets the parameterService attribute value. * * @param parameterService The parameterService to set. */ public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } /** * Gets the businessObjectService attribute. * * @return Returns the businessObjectService. */ @Override public BusinessObjectService getBusinessObjectService() { return businessObjectService; } /** * Sets the businessObjectService attribute value. * * @param businessObjectService The businessObjectService to set. */ public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } /** * Gets the dateTimeService attribute. * * @return Returns the dateTimeService. */ public DateTimeService getDateTimeService() { return dateTimeService; } /** * Sets the dateTimeService attribute value. * * @param dateTimeService The dateTimeService to set. */ public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } /** * Gets the perDiemDao attribute. * * @return Returns the perDiemDao */ public PerDiemDao getPerDiemDao() { return perDiemDao; } /** * Sets the perDiemDao attribute. * * @param perDiemDao The perDiemDao to set. */ public void setPerDiemDao(PerDiemDao perDiemDao) { this.perDiemDao = perDiemDao; } /** * Gets the mealBreakDownStrategies attribute. * * @return Returns the mealBreakDownStrategies. */ public Map<String, MealBreakDownStrategy> getMealBreakDownStrategies() { return mealBreakDownStrategies; } /** * Sets the mealBreakDownStrategies attribute value. * * @param mealBreakDownStrategies The mealBreakDownStrategies to set. */ public void setMealBreakDownStrategies(Map<String, MealBreakDownStrategy> mealBreakDownStrategies) { this.mealBreakDownStrategies = mealBreakDownStrategies; } /** * Gets the stateService attribute. * @return Returns the stateService. */ public StateService getStateService() { return stateService; } /** * Sets the stateService attribute value. * @param stateService The stateService to set. */ public void setStateService(StateService stateService) { this.stateService = stateService; } /** * Sets the allStateCodes attribute value. * @param allStateCodes The allStateCodes to set. */ public void setAllStateCodes(String allStateCodes) { this.allStateCodes = allStateCodes; } /** * Gets the allStateCodes attribute. * @return Returns the allStateCodes. */ public String getAllStateCodes() { if(StringUtils.isEmpty(allStateCodes)) { final List<State> codes = getStateService().findAllStatesInCountry(KFSConstants.COUNTRY_CODE_UNITED_STATES); final StringBuffer sb = new StringBuffer(); sb.append(";").append(KFSConstants.COUNTRY_CODE_UNITED_STATES.toUpperCase()).append(";"); for (final State state : codes) { if(state.isActive()) { sb.append(state.getCode().toUpperCase()).append( ";"); } } allStateCodes = sb.toString(); } return allStateCodes; } public String getObjectCodeFrom(final TravelDocument travelDocument, String paramName) { if (travelDocument instanceof TravelReimbursementDocument){ final String parameterValue = getParameterService().getParameterValueAsString(TravelReimbursementDocument.class, paramName); String paramSearchStr = ""; TravelerDetail traveler = travelDocument.getTraveler(); if(traveler != null){ paramSearchStr += traveler.getTravelerTypeCode() + "="; } TripType tripType = travelDocument.getTripType(); if(tripType != null){ paramSearchStr += tripType.getCode() + "="; } final int searchIdx = parameterValue.indexOf(paramSearchStr); if (searchIdx == -1) { return null; } final int endIdx = parameterValue.indexOf(";", searchIdx); if (endIdx == -1) { return parameterValue.substring(searchIdx + paramSearchStr.length()); } return parameterValue.substring(searchIdx + paramSearchStr.length(), endIdx); } else{ if (travelDocument.getTripType() != null){ return travelDocument.getTripType().getEncumbranceObjCode(); } return null; } } @Override public Map<String, AccountingDistribution> getAccountingDistribution(TravelDocument document) { Map<String, AccountingDistribution> distributionMap = new HashMap<String, AccountingDistribution>(); if (document.getPerDiemExpenses() != null && document.getPerDiemExpenses().size() > 0){ String defaultChartCode = ExpenseUtils.getDefaultChartCode(document); String perDiemCode = getObjectCodeFrom(document, PER_DIEM_OBJECT_CODE); if (document instanceof TravelAuthorizationDocument){ if (document.getTripType() != null){ perDiemCode = document.getTripType().getEncumbranceObjCode(); } else{ perDiemCode = null; } } LOG.debug("Looking up Object Code for chart = "+ defaultChartCode+ " perDiemObjectCode = "+ perDiemCode); final ObjectCode perDiemObjCode = getObjectCodeService().getByPrimaryIdForCurrentYear(defaultChartCode, perDiemCode); LOG.debug("Got per diem object code "+ perDiemObjCode); // Per Diem AccountingDistribution accountingDistribution = new AccountingDistribution(); if (perDiemObjCode != null) { String key = perDiemObjCode.getCode() + "-" + document.getDefaultCardTypeCode(); for(PerDiemExpense expense : document.getPerDiemExpenses()){ if (!expense.getPersonal()){ if (!distributionMap.containsKey(key)){ accountingDistribution.setCardType(document.getDefaultCardTypeCode()); accountingDistribution.setObjectCode(perDiemObjCode.getCode()); accountingDistribution.setObjectCodeName(perDiemObjCode.getName()); distributionMap.put(key, accountingDistribution); } distributionMap.get(key).setSubTotal(distributionMap.get(key).getSubTotal().add(expense.getMealsAndIncidentals())); distributionMap.get(key).setRemainingAmount(distributionMap.get(key).getRemainingAmount().add(expense.getMealsAndIncidentals())); LOG.debug("Set perdiem distribution subtotal to "+ accountingDistribution.getSubTotal()); } } if (document.getPerDiemAdjustment() != null && document.getPerDiemAdjustment().isPositive()){ distributionMap.get(key).setSubTotal(distributionMap.get(key).getSubTotal().subtract(document.getPerDiemAdjustment())); distributionMap.get(key).setRemainingAmount(distributionMap.get(key).getRemainingAmount().subtract(document.getPerDiemAdjustment())); } distributeLodging(distributionMap, document); distributeMileage(distributionMap, document); } else { LOG.error("PerDiemObjCode is null!"); } } return distributionMap; } protected void distributeLodging(Map<String, AccountingDistribution> distributionMap, final TravelDocument document) { String defaultChartCode = ExpenseUtils.getDefaultChartCode(document); String lodgingCode = getObjectCodeFrom(document, LODGING_OBJECT_CODE); if (document instanceof TravelAuthorizationDocument){ if (document.getTripType() != null){ lodgingCode = document.getTripType().getEncumbranceObjCode(); } else{ lodgingCode = null; } } LOG.debug("Looking up Object Code for chart = "+ defaultChartCode+ " lodgingCode = "+ lodgingCode); final ObjectCode lodgingObjCode = getObjectCodeService().getByPrimaryIdForCurrentYear(defaultChartCode, lodgingCode); LOG.debug("Got lodging object code "+ lodgingObjCode); AccountingDistribution accountingDistribution = new AccountingDistribution(); String key = lodgingObjCode.getCode() + "-" + document.getDefaultCardTypeCode(); if (document.getPerDiemExpenses() != null) { for(PerDiemExpense expense : document.getPerDiemExpenses()){ if (!distributionMap.containsKey(key)){ accountingDistribution.setCardType(document.getDefaultCardTypeCode()); accountingDistribution.setObjectCode(lodgingObjCode.getCode()); accountingDistribution.setObjectCodeName(lodgingObjCode.getName()); distributionMap.put(key, accountingDistribution); } distributionMap.get(key).setSubTotal(distributionMap.get(key).getSubTotal().add(expense.getLodgingTotal())); distributionMap.get(key).setRemainingAmount(distributionMap.get(key).getRemainingAmount().add(expense.getLodgingTotal())); } } } protected void distributeMileage(Map<String, AccountingDistribution> distributionMap, TravelDocument document) { String defaultChartCode = ExpenseUtils.getDefaultChartCode(document); AccountingDistribution accountingDistribution = new AccountingDistribution(); if (document.getPerDiemExpenses() != null) { for(PerDiemExpense expense : document.getPerDiemExpenses()){ if (!StringUtils.isBlank(expense.getMileageRateExpenseTypeCode())) { String mileageCode = null; if (document instanceof TravelAuthorizationDocument && document.getTripType() != null){ mileageCode = document.getTripType().getEncumbranceObjCode(); } else{ final String travelerTypeCode = (ObjectUtils.isNull(document.getTraveler())) ? null : document.getTraveler().getTravelerTypeCode(); final ExpenseTypeObjectCode expenseTypeObjectCode = getTravelExpenseService().getExpenseType(expense.getMileageRateExpenseTypeCode(), document.getDocumentTypeName(), document.getTripTypeCode(), travelerTypeCode); if (expenseTypeObjectCode != null) { mileageCode = expenseTypeObjectCode.getFinancialObjectCode(); } } LOG.debug("Looking up Object Code for chart = "+ defaultChartCode+ " mileageCode = "+ mileageCode); final ObjectCode mileageObjCode = getObjectCodeService().getByPrimaryIdForCurrentYear(defaultChartCode, mileageCode); LOG.debug("Got mileage object code "+ mileageObjCode); String key = mileageObjCode.getCode() + "-" + document.getDefaultCardTypeCode(); if (!distributionMap.containsKey(key)){ accountingDistribution.setCardType(document.getDefaultCardTypeCode()); accountingDistribution.setObjectCode(mileageObjCode.getCode()); accountingDistribution.setObjectCodeName(mileageObjCode.getName()); distributionMap.put(key, accountingDistribution); } distributionMap.get(key).setSubTotal(distributionMap.get(key).getSubTotal().add(expense.getMileageTotal())); distributionMap.get(key).setRemainingAmount(distributionMap.get(key).getRemainingAmount().add(expense.getMileageTotal())); } } } } /** * @see org.kuali.kfs.module.tem.service.impl.ExpenseServiceBase#getAllExpenseTotal(org.kuali.kfs.module.tem.document.TravelDocument, boolean) */ @Override public KualiDecimal getAllExpenseTotal(TravelDocument document, boolean includeNonReimbursable) { KualiDecimal total = KualiDecimal.ZERO; for (PerDiemExpense expense : document.getPerDiemExpenses()){ if ((expense.getPersonal().booleanValue() && includeNonReimbursable) || !expense.getPersonal().booleanValue()){ total = total.add(expense.getDailyTotalForDocument(document)); } } return total; } /** * @see org.kuali.kfs.module.tem.service.impl.ExpenseServiceBase#getNonReimbursableExpenseTotal(org.kuali.kfs.module.tem.document.TravelDocument) */ @Override public KualiDecimal getNonReimbursableExpenseTotal(TravelDocument document) { // This is because for per diem, when the personal checkbox is checked the amount is already excluded from the total. return KualiDecimal.ZERO; } @Override public KualiDecimal getMealsAndIncidentalsGrandTotal(TravelDocument travelDocument) { KualiDecimal mealsAndIncidentalsTotal = KualiDecimal.ZERO; for (PerDiemExpense expense : travelDocument.getPerDiemExpenses()) { mealsAndIncidentalsTotal = mealsAndIncidentalsTotal.add(expense.getMealsAndIncidentals()); } return mealsAndIncidentalsTotal; } @Override public KualiDecimal getLodgingGrandTotal(TravelDocument travelDocument) { KualiDecimal lodgingTotal = KualiDecimal.ZERO; for (PerDiemExpense perDiemExpense : travelDocument.getPerDiemExpenses()) { if (!perDiemExpense.getPersonal()) { lodgingTotal = lodgingTotal.add(perDiemExpense.getLodgingTotal()); } } return lodgingTotal; } @Override public KualiDecimal getMileageTotalGrandTotal(TravelDocument travelDocument) { KualiDecimal mileageTotal = KualiDecimal.ZERO; for (PerDiemExpense perDiemExpense : travelDocument.getPerDiemExpenses()) { mileageTotal = mileageTotal.add(perDiemExpense.getMileageTotal()); } return mileageTotal; } @Override public KualiDecimal getDailyTotalGrandTotal(TravelDocument travelDocument) { KualiDecimal dailyTotal = KualiDecimal.ZERO; for (PerDiemExpense perDiemExpense : travelDocument.getPerDiemExpenses()) { dailyTotal = dailyTotal.add(perDiemExpense.getDailyTotal()); } return dailyTotal; } @Override public Integer getMilesGrandTotal(TravelDocument travelDocument) { Integer milesTotal = 0; for (PerDiemExpense perDiemExpense : travelDocument.getPerDiemExpenses()) { milesTotal = milesTotal + perDiemExpense.getMiles(); } return milesTotal; } /** * Determines if per diem is handling lodging based on KFS-TEM / Document / PER_DIEM_CATEGORIES * @see org.kuali.kfs.module.tem.service.PerDiemService#isPerDiemHandlingLodging() */ @Override public boolean isPerDiemHandlingLodging() { final Collection<String> perDiemCategoryValues = getParameterService().getParameterValuesAsString(TemParameterConstants.TEM_DOCUMENT.class, TemConstants.TravelParameters.PER_DIEM_CATEGORIES); for (String perDiemCategoryValue : perDiemCategoryValues) { final String[] keyValueSplit = perDiemCategoryValue.split("="); if (keyValueSplit.length == 2) { if (TemConstants.PerDiemType.lodging.name().equalsIgnoreCase(keyValueSplit[0])) { return KFSConstants.ParameterValues.YES.equalsIgnoreCase(keyValueSplit[1]); } } } return false; } /** * Uses travelDocumentDao to look up per diem records (TravelDocumentDao handles the the date wierdnesses of per diem date) * @see org.kuali.kfs.module.tem.service.PerDiemService#getPerDiem(int, java.sql.Date, java.sql.Date) */ @Override public PerDiem getPerDiem(int primaryDestinationId, java.sql.Timestamp perDiemDate, java.sql.Date effectiveDate) { final List<PerDiem> possiblePerDiems = getTravelDocumentDao().findEffectivePerDiems(primaryDestinationId, effectiveDate); Date date = KfsDateUtils.clearTimeFields(new Date(perDiemDate.getTime())); if (possiblePerDiems.isEmpty()) { return null; } if (possiblePerDiems.size() == 1) { return possiblePerDiems.get(0); } Collections.sort(possiblePerDiems, new PerDiemComparator()); PerDiem foundPerDiem = null; for (PerDiem perDiem : possiblePerDiems) { if (isOnOrAfterSeasonBegin(perDiem.getSeasonBeginMonthAndDay(), perDiemDate)) { foundPerDiem = perDiem; } } if (foundPerDiem == null) { // no found per diem, so let's take the *last* one of the list (because years are circular and so the last of the list represents the beginning of the year; see KFSTP-926 for a discussion of this) foundPerDiem = possiblePerDiems.get(possiblePerDiems.size() - 1); } return foundPerDiem; } /** * Comparator to help us sort per diem records by season begin month/day */ protected class PerDiemComparator implements Comparator<PerDiem> { /** * next compare method I write will use patty and selma, I promise * Sorts the season begin month/days such that earlier dates are chosen before later dates * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ @Override public int compare(PerDiem viola, PerDiem sebastian) { if (StringUtils.isBlank(viola.getSeasonBeginMonthAndDay())) { if (StringUtils.isBlank(sebastian.getSeasonBeginMonthAndDay())) { return 0; } return 1; // sebastian has a value - choose sebastian } if (StringUtils.isBlank(sebastian.getSeasonBeginMonthAndDay())) { return -1; // viola has a value but not sebastian - choose viola } final String[] violaSeasonBegin = viola.getSeasonBeginMonthAndDay().split("/"); final String[] sebastianSeasonBegin = sebastian.getSeasonBeginMonthAndDay().split("/"); final int violaBeginMonth = Integer.parseInt(violaSeasonBegin[0]); final int sebastianBeginMonth = Integer.parseInt(sebastianSeasonBegin[0]); if (violaBeginMonth != sebastianBeginMonth) { return violaBeginMonth - sebastianBeginMonth; } final int violaBeginDay = Integer.parseInt(violaSeasonBegin[1]); final int sebastianBeginDay = Integer.parseInt(sebastianSeasonBegin[1]); if (violaBeginDay != sebastianBeginDay) { return violaBeginDay - sebastianBeginDay; } return 0; } } /** * Determines if the given date happens on or after the given season begin month/day for the year of the given date * @param seasonBegin the season begin month/day to check * @param d the date to check if on or after season begin * @return true if the given date is on or after the season begin date, false otherwise */ protected boolean isOnOrAfterSeasonBegin(String seasonBegin, java.sql.Timestamp d) { if (StringUtils.isBlank(seasonBegin)) { return true; // no season begin/end? Well...then we're after that, I should think } Calendar dCal = Calendar.getInstance(); dCal.setTime(d); final int year = dCal.get(Calendar.YEAR); Calendar seasonBeginCal = getSeasonBeginMonthDayCalendar(seasonBegin, year); if (KfsDateUtils.isSameDay(dCal, seasonBeginCal)) { // let's see if they're on the same day, regardless of time return true; } if (dCal.after(seasonBeginCal)) { // now that we know they're not on the same day, time isn't such a big deal return true; } return false; } /** * Given a season begin month/day and a year, returns a Calendar representing the date * @param seasonBegin the season begin month/day * @param year the year to set for the calendar * @return the Calendar from the given date information */ protected Calendar getSeasonBeginMonthDayCalendar(String seasonBegin, int year) { final String[] seasonBeginMonthDay = seasonBegin.split("/"); Calendar seasonBeginCal = Calendar.getInstance(); seasonBeginCal.set(Calendar.MONTH, Integer.parseInt(seasonBeginMonthDay[0]) - 1); seasonBeginCal.set(Calendar.DATE, Integer.parseInt(seasonBeginMonthDay[1])); seasonBeginCal.set(Calendar.YEAR, year); return seasonBeginCal; } @Override public void processExpense(TravelDocument travelDocument, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) { //do nothing } @Override public void updateExpense(TravelDocument travelDocument) { //do nothing } @Override public void calculateDistributionTotals(TravelDocument document, Map<String, AccountingDistribution> distributionMap, List<? extends TemExpense> expenses) { //not used for PerDiem } @Override public List<? extends TemExpense> getExpenseDetails(TravelDocument document) { //not used for PerDiem return null; } /** * @see org.kuali.kfs.module.tem.service.PerDiemService#setPerDiemCategories(org.kuali.kfs.module.tem.document.web.struts.TravelFormBase) */ @Override public void setPerDiemCategoriesAndBreakdown(TravelFormBase form) { Collection<String> perDiemCats = getParameterService().getParameterValuesAsString(TemParameterConstants.TEM_DOCUMENT.class, TravelParameters.PER_DIEM_CATEGORIES); form.parsePerDiemCategories(perDiemCats); //default to TA Boolean showPerDiemBreakdown = parameterService.getParameterValueAsBoolean(TravelAuthorizationDocument.class, TravelAuthorizationParameters.PER_DIEM_AMOUNT_EDITABLE_IND); if (TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT.equals(form.getDocTypeName())){ showPerDiemBreakdown = parameterService.getParameterValueAsBoolean(TravelReimbursementDocument.class, TravelReimbursementParameters.PER_DIEM_AMOUNT_EDITABLE_IND); } form.setShowPerDiemBreakdown(showPerDiemBreakdown); } /** * Determines if: * <ul> * <li>A current mileage rate for the KFS-TEM / Document / PER_DIEM_MILEAGE_RATE_EXPENSE_TYPE_CODE is available; if it is not, then per diem cannot be created * </ul> * @param form the form with the document on it, which may help in making such a decision */ @Override public boolean isMileageRateAvailableForAllPerDiem(TravelDocument doc) { final String defaultPerDiemMileageRate = getDefaultPerDiemMileageRateExpenseType(); if (StringUtils.isBlank(defaultPerDiemMileageRate)) { return false; } if (StringUtils.isBlank(doc.getTripTypeCode()) || doc.getTripBegin() == null || doc.getTripEnd() == null) { return true; // we can't create per diem when trip begin or end are blank anyhow - but we shouldn't get the error } // now we need to loop through each day from begin date to end date to see if there is a validate mileage rate record for it Calendar currDay = Calendar.getInstance(); currDay.setTime(KfsDateUtils.clearTimeFields(doc.getTripBegin())); Calendar lastDay = Calendar.getInstance(); lastDay.setTime(KfsDateUtils.clearTimeFields(doc.getTripEnd())); while (currDay.before(lastDay) || currDay.equals(lastDay)) { java.sql.Date effectiveDay = doc.getEffectiveDateForPerDiem(new java.sql.Timestamp(currDay.getTimeInMillis())); final MileageRate currDayMileageRate = getMileageRateService().findMileageRateByExpenseTypeCodeAndDate(defaultPerDiemMileageRate, effectiveDay); if (currDayMileageRate == null) { return false; } currDay.add(Calendar.DATE, 1); } // we're good return true; } /** * Does the parameter look-up so that validations don't have to * @see org.kuali.kfs.module.tem.service.PerDiemService#getDefaultPerDiemMileageRateExpenseType() */ @Override public String getDefaultPerDiemMileageRateExpenseType() { final String defaultPerDiemMileageRate = getParameterService().getParameterValueAsString(TemParameterConstants.TEM_DOCUMENT.class, TemConstants.TravelParameters.PER_DIEM_MILEAGE_RATE_EXPENSE_TYPE_CODE, KFSConstants.EMPTY_STRING); return defaultPerDiemMileageRate; } public TravelDocumentDao getTravelDocumentDao() { return travelDocumentDao; } public void setTravelDocumentDao(TravelDocumentDao travelDocumentDao) { this.travelDocumentDao = travelDocumentDao; } public TravelExpenseService getTravelExpenseService() { return travelExpenseService; } public void setTravelExpenseService(TravelExpenseService travelExpenseService) { this.travelExpenseService = travelExpenseService; } }