View Javadoc
1   /*
2    * Copyright 2006 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.fp.document.service.impl;
17  
18  import java.math.BigDecimal;
19  import java.sql.Date;
20  import java.sql.Timestamp;
21  import java.text.ParseException;
22  import java.util.Calendar;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  import org.kuali.ole.fp.businessobject.TravelMileageRate;
27  import org.kuali.ole.fp.document.dataaccess.TravelMileageRateDao;
28  import org.kuali.ole.fp.document.service.DisbursementVoucherTravelService;
29  import org.kuali.ole.sys.service.NonTransactional;
30  import org.kuali.ole.sys.util.KfsDateUtils;
31  import org.kuali.rice.core.api.datetime.DateTimeService;
32  import org.kuali.rice.core.api.util.type.KualiDecimal;
33  
34  /**
35   * This is the default implementation of the DisbursementVoucherTravelService interface.
36   * Performs calculations of travel per diem and mileage amounts.
37   */
38  
39  @NonTransactional
40  public class DisbursementVoucherTravelServiceImpl implements DisbursementVoucherTravelService {
41      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DisbursementVoucherTravelServiceImpl.class);
42  
43      private TravelMileageRateDao travelMileageRateDao;
44      private DateTimeService dateTimeService;
45  
46      /**
47       * This method calculates the per diem amount for a given period of time at the rate provided.  The per diem amount is 
48       * calculated as described below.
49       * 
50       * For same day trips: 
51       * - Per diem is equal to 1/2 of the per diem rate provided if the difference in time between the start and end time is 
52       * greater than 12 hours.  An additional 1/4 of a day is added back to the amount if the trip lasted past 7:00pm.  
53       * - If the same day trip is less than 12 hours, the per diem amount will be zero.
54       * 
55       * For multiple day trips:
56       * - Per diem amount is equal to the full rate times the number of full days of travel.  A full day is equal to any day
57       * during the trip that is not the first day or last day of the trip.
58       * - For the first day of the trip, 
59       *   if the travel starts before noon, you receive a full day per diem, 
60       *   if the travel starts between noon and 5:59pm, you get a half day per diem,
61       *   if the travel starts after 6:00pm, you only receive a quarter day per diem
62       * - For the last day of the trip, 
63       *   if the travel ends before 6:00am, you only receive a quarter day per diem,
64       *   if the travel ends between 6:00am and noon, you receive a half day per diem,
65       *   if the travel ends after noon, you receive a full day per diem
66       *   
67       * @param stateDateTime The starting date and time of the period the per diem amount is calculated for.
68       * @param endDateTime The ending date and time of the period the per diema mount is calculated for.
69       * @param rate The per diem rate used to calculate the per diem amount.
70       * @return The per diem amount for the period specified, at the rate given.
71       * 
72       * @see org.kuali.ole.fp.document.service.DisbursementVoucherTravelService#calculatePerDiemAmount(org.kuali.ole.fp.businessobject.DisbursementVoucherNonEmployeeTravel)
73       */
74      public KualiDecimal calculatePerDiemAmount(Timestamp startDateTime, Timestamp endDateTime, KualiDecimal rate) {
75          KualiDecimal perDiemAmount = KualiDecimal.ZERO;
76          KualiDecimal perDiemRate = new KualiDecimal(rate.doubleValue());
77  
78          // make sure we have the fields needed
79          if (perDiemAmount == null || startDateTime == null || endDateTime == null) {
80              LOG.error("Per diem amount, Start date/time, and End date/time must all be given.");
81              throw new RuntimeException("Per diem amount, Start date/time, and End date/time must all be given.");
82          }
83  
84          // check end time is after start time
85          if (endDateTime.compareTo(startDateTime) <= 0) {
86              LOG.error("End date/time must be after start date/time.");
87              throw new RuntimeException("End date/time must be after start date/time.");
88          }
89  
90          Calendar startCalendar = Calendar.getInstance();
91          startCalendar.setTime(startDateTime);
92  
93          Calendar endCalendar = Calendar.getInstance();
94          endCalendar.setTime(endDateTime);
95  
96          double diffDays = KfsDateUtils.getDifferenceInDays(startDateTime, endDateTime);
97          double diffHours = KfsDateUtils.getDifferenceInHours(startDateTime, endDateTime);
98  
99          // same day travel
100         if (diffDays == 0) {
101             // no per diem for only 12 hours or less
102             if (diffHours > 12) {
103                 // half day of per diem
104                 perDiemAmount = perDiemRate.divide(new KualiDecimal(2));
105 
106                 // add in another 1/4 of a day if end time past 7:00
107                 if (timeInPerDiemPeriod(endCalendar, 19, 0, 23, 59)) {
108                     perDiemAmount = perDiemAmount.add(perDiemRate.divide(new KualiDecimal(4)));
109                 }
110             }
111         }
112 
113         // multiple days of travel
114         else {
115             // must at least have 7 1/2 hours to get any per diem
116             if (diffHours >= 7.5) {
117                 // per diem for whole days
118                 perDiemAmount = perDiemRate.multiply(new KualiDecimal(diffDays - 1));
119 
120                 // per diem for first day
121                 if (timeInPerDiemPeriod(startCalendar, 0, 0, 11, 59)) { // Midnight to noon
122                     perDiemAmount = perDiemAmount.add(perDiemRate);
123                 }
124                 else if (timeInPerDiemPeriod(startCalendar, 12, 0, 17, 59)) { // Noon to 5:59pm
125                     perDiemAmount = perDiemAmount.add(perDiemRate.divide(new KualiDecimal(2)));
126                 }
127                 else if (timeInPerDiemPeriod(startCalendar, 18, 0, 23, 59)) { // 6:00pm to Midnight
128                     perDiemAmount = perDiemAmount.add(perDiemRate.divide(new KualiDecimal(4)));
129                 }
130 
131                 // per diem for end day
132                 if (timeInPerDiemPeriod(endCalendar, 0, 1, 6, 0)) { // Midnight to 6:00am
133                     perDiemAmount = perDiemAmount.add(perDiemRate.divide(new KualiDecimal(4)));
134                 }
135                 else if (timeInPerDiemPeriod(endCalendar, 6, 1, 12, 0)) { // 6:00am to noon
136                     perDiemAmount = perDiemAmount.add(perDiemRate.divide(new KualiDecimal(2)));
137                 }
138                 else if (timeInPerDiemPeriod(endCalendar, 12, 01, 23, 59)) { // Noon to midnight
139                     perDiemAmount = perDiemAmount.add(perDiemRate);
140                 }
141             }
142         }
143 
144         return perDiemAmount;
145     }
146 
147     /**
148      * Checks whether the date is in a per diem period given by the start hour and end hour and minutes.
149      * 
150      * @param cal The date being checked to see if it occurred within the defined travel per diem period.
151      * @param periodStartHour The starting hour of the per diem period.
152      * @param periodStartMinute The starting minute of the per diem period.
153      * @param periodEndHour The ending hour of the per diem period.
154      * @param periodEndMinute The ending minute of the per diem period.
155      * @return True if the date passed in occurred within the period defined by the given parameters, false otherwise.
156      */
157     protected boolean timeInPerDiemPeriod(Calendar cal, int periodStartHour, int periodStartMinute, int periodEndHour, int periodEndMinute) {
158         int hour = cal.get(Calendar.HOUR_OF_DAY);
159         int minute = cal.get(Calendar.MINUTE);
160 
161         return (((hour > periodStartHour) || (hour == periodStartHour && minute >= periodStartMinute)) && ((hour < periodEndHour) || (hour == periodEndHour && minute <= periodEndMinute)));
162     }
163 
164     /**
165      * This method calculates the mileage amount based on the total mileage traveled and the using the reimbursement rate
166      * applicable to when the trip started.
167      * 
168      * For this method, a collection of mileage rates is retrieved, where each mileage rate is defined by a mileage limit.  
169      * This collection is iterated over to determine which mileage rate will be used for calculating the total mileage 
170      * amount due.
171      * 
172      * @param totalMileage The total mileage traveled that will be reimbursed for.
173      * @param travelStartDate The start date of the travel, which will be used to retrieve the mileage reimbursement rate.
174      * @return The total reimbursement due to the traveler for the mileage traveled.
175      * 
176      * @see org.kuali.ole.fp.document.service.DisbursementVoucherTravelService#calculateMileageAmount(org.kuali.ole.fp.businessobject.DisbursementVoucherNonEmployeeTravel)
177      */
178     public KualiDecimal calculateMileageAmount(Integer totalMileage, Timestamp travelStartDate) {
179         KualiDecimal mileageAmount = KualiDecimal.ZERO;
180 
181         if (totalMileage == null || travelStartDate == null) {
182             LOG.error("Total Mileage and Travel Start Date must be given.");
183             throw new RuntimeException("Total Mileage and Travel Start Date must be given.");
184         }
185 
186         // convert timestamp to sql date
187         Date effectiveDate = null;
188         try {
189             effectiveDate = dateTimeService.convertToSqlDate(travelStartDate);
190         }
191         catch (ParseException e) {
192             LOG.error("Unable to parse travel start date into sql date " + e.getMessage());
193             throw new RuntimeException("Unable to parse travel start date into sql date " + e.getMessage());
194         }
195 
196         // retrieve mileage rates
197         List mileageRates = (List) travelMileageRateDao.retrieveMostEffectiveMileageRates(effectiveDate);
198 
199         if (mileageRates == null || mileageRates.isEmpty()) {
200             LOG.error("Unable to retreive mileage rates.");
201             throw new RuntimeException("Unable to retreive mileage rates.");
202         }
203 
204         int mileage = totalMileage.intValue();
205         int mileageRemaining = mileage;
206 
207         /**
208          * Iterate over mileage rates sorted in descending order by the mileage limit amount. For all miles over the mileage limit
209          * amount, the rate times those number of miles over is added to the mileage amount.
210          */
211         for (Iterator iter = mileageRates.iterator(); iter.hasNext();) {
212             TravelMileageRate rate = (TravelMileageRate) iter.next();
213             int mileageLimitAmount = rate.getMileageLimitAmount().intValue();
214             if (mileageRemaining > mileageLimitAmount) {
215                 BigDecimal numMiles = new BigDecimal(mileageRemaining - mileageLimitAmount);
216                 BigDecimal rateForMiles = numMiles.multiply(rate.getMileageRate()).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR);
217                 mileageAmount = mileageAmount.add(new KualiDecimal(rateForMiles));
218                 mileageRemaining = mileageLimitAmount;
219             }
220 
221         }
222 
223         return mileageAmount;
224     }
225 
226     /**
227      * Gets the travelMileageRateDao attribute.
228      * @return Returns the travelMileageRateDao.
229      */
230     public TravelMileageRateDao getTravelMileageRateDao() {
231         return travelMileageRateDao;
232     }
233 
234     /**
235      * Sets the travelMileageRateDao attribute.
236      * @param travelMileageRateDao The travelMileageRateDao to set.
237      */
238     public void setTravelMileageRateDao(TravelMileageRateDao travelMileageRateDao) {
239         this.travelMileageRateDao = travelMileageRateDao;
240     }
241 
242     /**
243      * Gets the dateTimeService attribute.
244      * @return Returns the dateTimeService.
245      */
246     public DateTimeService getDateTimeService() {
247         return dateTimeService;
248     }
249 
250     /**
251      * Sets the dateTimeService attribute.
252      * @param dateTimeService The dateTimeService to set.
253      */
254     public void setDateTimeService(DateTimeService dateTimeService) {
255         this.dateTimeService = dateTimeService;
256     }
257 }