001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.endow.document.service.impl;
017    
018    import java.sql.Date;
019    import java.util.Calendar;
020    import java.util.HashMap;
021    import java.util.Map;
022    
023    import org.apache.commons.lang.StringUtils;
024    import org.kuali.kfs.module.endow.EndowConstants;
025    import org.kuali.kfs.module.endow.businessobject.FrequencyCode;
026    import org.kuali.kfs.module.endow.document.service.FrequencyDatesService;
027    import org.kuali.kfs.module.endow.document.service.KEMService;
028    import org.kuali.rice.kns.service.BusinessObjectService;
029    import org.kuali.rice.kns.service.DateTimeService;
030    
031    public class FrequencyDatesServiceImpl implements FrequencyDatesService {
032        
033        protected BusinessObjectService businessObjectService;
034        protected DateTimeService dateTimeService;
035        protected KEMService kemService;
036        
037        /**
038         * @see org.kuali.kfs.module.endow.document.service.FrequencyCodeService#getByPrimaryKey(java.lang.String)
039         */
040        public FrequencyCode getByPrimaryKey(String code) {
041            FrequencyCode frequencyCode = null;
042            if (StringUtils.isNotBlank(code)) {
043                frequencyCode = (FrequencyCode) businessObjectService.findBySinglePrimaryKey(FrequencyCode.class, code);
044            }
045            
046            return frequencyCode;
047        }
048        
049        /**
050         * @see org.kuali.kfs.module.endow.document.service.FrequencyCodeService#calculateNextDueDate(java.lang.String, java.sql.Date)
051         */
052        public Date calculateNextDueDate(String frequencyCode, Date currentDate) {
053    
054            Calendar nextDate = null;
055            
056            if (currentDate != null && frequencyCode != null && !frequencyCode.trim().isEmpty()) {
057            
058                String frequencyType = frequencyCode.substring(0, 1);
059                
060                if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.DAILY)) {
061                    nextDate = calculateNextDate(currentDate);
062                }
063                else if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.WEEKLY)) {
064                    String dayOfWeek =  frequencyCode.substring(1, 4).toUpperCase();
065                    nextDate = calculateNextWeeklyDate(dayOfWeek, currentDate);
066                }
067                else if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.SEMI_MONTHLY)) {
068                    String dayOfSemiMonthly =  frequencyCode.substring(1, 3);
069                    nextDate = calculateNextSemiMonthlyDate(dayOfSemiMonthly, currentDate);
070                }    
071                else if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.MONTHLY)) {
072                    String dayOfMonth =  frequencyCode.substring(1, 3);
073                    nextDate = calculateNextMonthlyDate(dayOfMonth, currentDate);
074                }    
075                else if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.QUARTERLY) || 
076                    frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.SEMI_ANNUALLY) ||
077                    frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.ANNUALLY)) {
078                    String month = frequencyCode.substring(1, 2);
079                    String dayOfMonth =  frequencyCode.substring(2, 4);
080                    nextDate = calculateNextQuarterlyOrSemiAnnuallyOrAnnuallyProcessDate(month, dayOfMonth, frequencyType, currentDate);
081                }
082            }
083            
084            return nextDate == null ? null : new java.sql.Date(nextDate.getTimeInMillis());    
085        }
086    
087        /**
088         * Calculates the next date
089         * @param currentDate
090         * @return
091         */
092        protected Calendar calculateNextDate(Date currentDate) {
093            Calendar calendar = Calendar.getInstance();
094            calendar.setTime(currentDate);
095            calendar.add(Calendar.DATE, 1);
096            return calendar;
097        }
098        
099        /**
100         * Method to calculate the next processing week date based on the frequency type
101         * adds the appropriate number of days to the current date
102         * @param dayOfWeek
103         * @return next processing date
104         */
105        protected Calendar calculateNextWeeklyDate(String dayOfWeekFromFrequencyCode, Date currentDate) {
106            Calendar calendar = Calendar.getInstance();
107            calendar.setTime(currentDate);
108            
109            int daysToAdd = 0;
110            int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);  // today's day of the week
111            int maximumDaysInWeek = calendar.getActualMaximum(Calendar.DAY_OF_WEEK);
112                    
113            if (dayOfWeekFromFrequencyCode.equalsIgnoreCase(EndowConstants.FrequencyWeekDays.MONDAY)) {
114                if (dayOfWeek < Calendar.MONDAY)
115                    daysToAdd = Calendar.MONDAY - dayOfWeek;
116                else
117                    daysToAdd = maximumDaysInWeek - dayOfWeek + Calendar.MONDAY;
118            } else if (dayOfWeekFromFrequencyCode.equalsIgnoreCase(EndowConstants.FrequencyWeekDays.TUESDAY)) {
119                       if (dayOfWeek < Calendar.TUESDAY)
120                           daysToAdd = Calendar.TUESDAY - dayOfWeek;
121                       else
122                           daysToAdd = maximumDaysInWeek - dayOfWeek + Calendar.TUESDAY;
123                  } else if (dayOfWeekFromFrequencyCode.equalsIgnoreCase(EndowConstants.FrequencyWeekDays.WEDNESDAY)) {
124                             if (dayOfWeek < Calendar.WEDNESDAY)
125                                 daysToAdd = Calendar.WEDNESDAY - dayOfWeek;
126                             else
127                                 daysToAdd = maximumDaysInWeek - dayOfWeek + Calendar.WEDNESDAY;
128                    } else if (dayOfWeekFromFrequencyCode.equalsIgnoreCase(EndowConstants.FrequencyWeekDays.THURSDAY)) {
129                               if (dayOfWeek < Calendar.THURSDAY)
130                                   daysToAdd = Calendar.THURSDAY - dayOfWeek;
131                               else   
132                                   daysToAdd = maximumDaysInWeek - dayOfWeek + Calendar.THURSDAY;
133                          } else if (dayOfWeekFromFrequencyCode.equalsIgnoreCase(EndowConstants.FrequencyWeekDays.FRIDAY)) {
134                                     if (dayOfWeek < Calendar.FRIDAY)
135                                         daysToAdd = Calendar.FRIDAY - dayOfWeek;
136                                     else   
137                                         daysToAdd = maximumDaysInWeek - dayOfWeek + Calendar.FRIDAY;
138                            }
139    
140            calendar.add(Calendar.DAY_OF_MONTH, daysToAdd);
141            
142            return calendar;
143        }
144    
145        /**
146         * Method to calculate the next processing semi-monthly date based on the frequency type
147         * Sets the day of the month and then returns the processing date
148         * @param dayOfSemiMonthly
149         * @return next processing date
150         */
151        protected Calendar calculateNextSemiMonthlyDate(String dayOfSemiMonthly, Date currentDate) {
152            Calendar calendar = Calendar.getInstance();
153            calendar.setTime(currentDate);
154            
155            int dayOfMonthToSet = Integer.parseInt(dayOfSemiMonthly);
156            int dayOfMonthNextToSet = 15;
157            
158            // start from the target day
159            calendar.set(Calendar.DAY_OF_MONTH, dayOfMonthToSet);
160            
161            // it must be greater than the current date
162            if (currentDate.compareTo(new java.sql.Date(calendar.getTimeInMillis())) > -1) {
163                calendar.add(Calendar.DAY_OF_MONTH, dayOfMonthNextToSet);
164            }
165            // if it is not still greater, it should be in the next month
166            if (currentDate.compareTo(new java.sql.Date(calendar.getTimeInMillis())) > -1) {
167                calendar.set(Calendar.DAY_OF_MONTH, dayOfMonthToSet);
168                calendar.add(Calendar.MONTH, 1);
169            }
170            
171            return calendar;
172        }
173        
174        /**
175         * Method to calculate the next processing monthly date based on the frequency type
176         * Sets the day in the calendar based on day part of the frequency code.
177         * @param dayOfMonth
178         * @return next processing date
179         */
180        protected Calendar calculateNextMonthlyDate(String dayOfMonth, Date currentDate) {
181            int dayInMonthToSet;
182            
183            Calendar calendar = Calendar.getInstance();
184            calendar.setTime(currentDate);
185            setCalendarWithDays(calendar, dayOfMonth);
186            while (new java.sql.Date(calendar.getTimeInMillis()).before(currentDate)) {
187                calendar.add(Calendar.MONTH, 1);  
188            }
189            
190            return calendar;
191        }
192    
193        /**
194         * Method to calculate the next processing quarterly or semi-annually or annually date based on the frequency type
195         * Sets the day in the calendar based on day part of the frequency code.
196         * @param frequencyType frequency code for quarterly, month, dayOfMonth
197         * @return next processing date
198         */ 
199        protected Calendar calculateNextQuarterlyOrSemiAnnuallyOrAnnuallyProcessDate(String month, String dayOfMonth, String frequencyType, Date currentDate) {
200            Calendar calendar = setCalendarWithMonth(month, currentDate);        
201            setCalendarWithDays(calendar, dayOfMonth);
202            
203            if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.QUARTERLY)) {
204                while (new java.sql.Date(calendar.getTimeInMillis()).compareTo(currentDate) < 1) {
205                    calendar.add(Calendar.MONTH, 3);  
206                    setCalendarWithDays(calendar, dayOfMonth);
207                }
208            } else if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.SEMI_ANNUALLY)) { 
209                while (new java.sql.Date(calendar.getTimeInMillis()).compareTo(currentDate) < 1) {
210                    calendar.add(Calendar.MONTH, 6);  
211                    setCalendarWithDays(calendar, dayOfMonth);
212                }
213            } else if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.ANNUALLY)) { 
214                while (new java.sql.Date(calendar.getTimeInMillis()).compareTo(currentDate) < 1) {
215                    calendar.add(Calendar.MONTH, 12); 
216                    setCalendarWithDays(calendar, dayOfMonth);
217                }
218            }
219            
220            return calendar;
221        }
222        
223        /**
224         * This method will check the current month and set the calendar to that month
225         * @param month month to set the calendar
226         * @return calendar calendar is set to the month selected
227         */
228        protected Calendar setCalendarWithMonth(String month, Date currentDate) {
229            Calendar calendar = Calendar.getInstance();
230            calendar.setTime(currentDate);
231            int calendarMonth = Calendar.JANUARY;
232            
233            if (EndowConstants.FrequencyMonths.JANUARY.equalsIgnoreCase(month)) {
234                calendarMonth = Calendar.JANUARY;
235            } else if (EndowConstants.FrequencyMonths.FEBRUARY.equalsIgnoreCase(month)) {
236                calendarMonth = Calendar.FEBRUARY;
237              } else if (EndowConstants.FrequencyMonths.MARCH.equalsIgnoreCase(month)) {
238                  calendarMonth = Calendar.MARCH;
239                } else if (EndowConstants.FrequencyMonths.APRIL.equalsIgnoreCase(month)) {
240                    calendarMonth = Calendar.APRIL;
241                  } else if (EndowConstants.FrequencyMonths.MAY.equalsIgnoreCase(month)) {
242                      calendarMonth = Calendar.MAY;
243                    } else if (EndowConstants.FrequencyMonths.JUNE.equalsIgnoreCase(month)) {
244                        calendarMonth = Calendar.JUNE;
245                      } else if (EndowConstants.FrequencyMonths.JULY.equalsIgnoreCase(month)) {
246                          calendarMonth = Calendar.JULY;
247                        } else if (EndowConstants.FrequencyMonths.AUGUST.equalsIgnoreCase(month)) {
248                            calendarMonth = Calendar.AUGUST;
249                          } else if (EndowConstants.FrequencyMonths.SEPTEMBER.equalsIgnoreCase(month)) {
250                              calendarMonth = Calendar.SEPTEMBER;
251                            } else if (EndowConstants.FrequencyMonths.OCTOBER.equalsIgnoreCase(month)) {
252                                calendarMonth = Calendar.OCTOBER;
253                              } else if (EndowConstants.FrequencyMonths.NOVEMBER.equalsIgnoreCase(month)) {
254                                  calendarMonth = Calendar.NOVEMBER;
255                                } else if (EndowConstants.FrequencyMonths.DECEMBER.equalsIgnoreCase(month)) {
256                                    calendarMonth = Calendar.DECEMBER;
257                                  }
258            
259            calendar.set(Calendar.MONTH, calendarMonth);
260            
261            return calendar;
262        }
263    
264        /**
265         * This method will check the current month and set the calendar to that month
266         * @param month, dayOfMonth month to set the calendar, dayOfMonth day of the month to set to
267         * @return calendar calendar is set to the month selected
268         */
269        protected void setCalendarWithDays(Calendar calendar, String dayOfMonth) {
270            int dayInMonthToSet;
271            int calendarMonth = calendar.get(Calendar.MONTH);
272            
273            if (StringUtils.equalsIgnoreCase(dayOfMonth, EndowConstants.FrequencyMonthly.MONTH_END)) { // month end for the month so need to get max days...
274                dayInMonthToSet = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
275            } else {
276                dayInMonthToSet = Integer.parseInt(dayOfMonth);
277                if ( dayInMonthToSet > calendar.getActualMaximum(Calendar.DAY_OF_MONTH) ) {
278                    dayInMonthToSet = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
279                }
280            }
281                
282            calendar.set(Calendar.DAY_OF_MONTH, dayInMonthToSet);
283        }
284        
285        
286        /**
287         * This method gets the businessObjectService.
288         * 
289         * @return businessObjectService
290         */
291        protected BusinessObjectService getBusinessObjectService() {
292            return businessObjectService;
293        }
294    
295        /**
296         * This method sets the businessObjectService
297         * 
298         * @param businessObjectService
299         */
300        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
301            this.businessObjectService = businessObjectService;
302        }
303        
304        public void setDateTimeService(DateTimeService dateTimeService) {
305            this.dateTimeService = dateTimeService;
306        }
307    
308        protected DateTimeService getDateTimeService() {
309            return dateTimeService;
310        }
311        
312        protected KEMService getKemService() {
313            return kemService;
314        }
315        
316        public void setKemService(KEMService kemService) {
317            this.kemService = kemService;
318        }
319    
320    }