001package org.kuali.ole.deliver.util;
002
003import org.apache.commons.collections.CollectionUtils;
004import org.apache.commons.lang3.StringUtils;
005import org.apache.commons.lang3.time.DateUtils;
006import org.joda.time.Interval;
007import org.kuali.ole.OLEConstants;
008import org.kuali.ole.deliver.bo.OleCirculationDesk;
009import org.kuali.ole.deliver.calendar.bo.*;
010import org.kuali.ole.deliver.drools.FixedDateUtil;
011import org.kuali.ole.deliver.service.ParameterValueResolver;
012import org.kuali.rice.krad.service.BusinessObjectService;
013import org.kuali.rice.krad.service.KRADServiceLocator;
014
015import java.sql.Timestamp;
016import java.util.*;
017
018/**
019 * Created by pvsubrah on 10/3/15.
020 */
021public class LoanDateTimeUtil extends ExceptionDateLoanDateTimeUtil {
022    private String policyId;
023    private Map<String, OleCalendar> calendarMap;
024    private OleCalendar activeCalendar;
025    private BusinessObjectService businessObjectService;
026    private Boolean nonWorkingHoursCheck = false;
027
028    public Date calculateDateTimeByPeriod(String loanPeriod, OleCirculationDesk oleCirculationDesk) {
029        Date loanDueDate;
030
031        loanDueDate = getLoanDueDate(loanPeriod);
032
033        if (null != loanDueDate && null != oleCirculationDesk) {
034            OleCalendar activeCalendar = getActiveCalendar(loanDueDate, oleCirculationDesk.getCalendarGroupId());
035            setActiveCalendar(activeCalendar);
036
037            if (null != activeCalendar) {
038                loanDueDate = calculateDueDate(loanDueDate);
039            }
040        }
041
042        return loanDueDate;
043    }
044
045    private Date calculateDueDate(Date loanDueDate) {
046        OleCalendarExceptionPeriod oleCalendarExceptionPeriod = doesDateFallInExceptionPeriod(getActiveCalendar(), loanDueDate);
047
048        if (null == oleCalendarExceptionPeriod) {
049            OleCalendarExceptionDate exceptionDate = isDateAnExceptionDate(getActiveCalendar(), loanDueDate);
050            if (null == exceptionDate) {
051                loanDueDate = handleNonWorkingHoursWorkflow(loanDueDate, getActiveCalendar().getOleCalendarWeekList());
052            } else {
053                if (StringUtils.isEmpty(exceptionDate.getOpenTime()) && StringUtils.isEmpty(exceptionDate.getCloseTime())) {
054                    //Holiday workflow;
055                    Date followingDay = DateUtils.addDays(loanDueDate, 1);
056                    loanDueDate = calculateDueDate(followingDay);
057                } else {
058                    // Partial hours workflow
059                    loanDueDate = handleExceptionDayWithPartialHours(loanDueDate, exceptionDate);
060                }
061            }
062        } else {
063            List<OleCalendarExceptionPeriodWeek> oleCalendarExceptionPeriodWeekList = oleCalendarExceptionPeriod.getOleCalendarExceptionPeriodWeekList();
064            //If the week list is empty i.e its a holiday period;
065            if (CollectionUtils.isEmpty(oleCalendarExceptionPeriodWeekList)) {
066                Timestamp endDate = oleCalendarExceptionPeriod.getEndDate();
067                Date followingDay = DateUtils.addDays(endDate, 1);
068                loanDueDate = calculateDueDate(followingDay);
069            } else {
070                loanDueDate = handleNonWorkingHoursWorkflow(loanDueDate, oleCalendarExceptionPeriodWeekList);
071            }
072        }
073        return loanDueDate;
074    }
075
076    private Date handleExceptionDayWithPartialHours(Date loanDueDate, OleCalendarExceptionDate exceptionDate) {
077        Calendar instance = Calendar.getInstance();
078        instance.setTime(exceptionDate.getDate());
079        exceptionDate.setStartDay(String.valueOf(instance.get(Calendar.DAY_OF_WEEK)-1));
080        exceptionDate.setEndDay(String.valueOf(instance.get(Calendar.DAY_OF_WEEK)-1));
081
082        List oleBaseCalendarWeekList = new ArrayList<>();
083        oleBaseCalendarWeekList.add(exceptionDate);
084        loanDueDate = handleNonWorkingHoursWorkflow(loanDueDate, oleBaseCalendarWeekList);
085
086        return loanDueDate;
087    }
088
089    private Date handleNonWorkingHoursWorkflow(Date loanDueDate, List<? extends OleBaseCalendarWeek> oleBaseCalendarWeekList) {
090        Map<String, Map<String, String>> openAndClosingTimeForTheGivenDayFromWeekList = getOpenAndClosingTimeForTheGivenDayFromWeekList(loanDueDate, oleBaseCalendarWeekList);
091        if (nonWorkingHoursCheck) {
092            Map<String, String> openTime = openAndClosingTimeForTheGivenDayFromWeekList.get("openTime");
093            Calendar calendar = resolveDateTime(openTime, loanDueDate);
094            loanDueDate = calendar.getTime();
095            loanDueDate = handleGracePeriod(loanDueDate);
096        } else {
097            boolean loanDueTimeWithinWorkingHours = isLoanDueTimeWithinWorkingHours(loanDueDate, oleBaseCalendarWeekList);
098
099            if (!loanDueTimeWithinWorkingHours) {
100                if (includeNonWorkingHours()) {
101                    Date followingDay = DateUtils.addDays(loanDueDate, 1);
102                    nonWorkingHoursCheck = true;
103                    loanDueDate = calculateDueDate(followingDay);
104                } else {
105                    Map<String, String> closeTime = openAndClosingTimeForTheGivenDayFromWeekList.get("closeTime");
106                    Calendar calendar = resolveDateTime(closeTime, loanDueDate);
107                    loanDueDate = calendar.getTime();
108                }
109            }
110        }
111        return loanDueDate;
112    }
113
114
115    private Date getLoanDueDate(String loanPeriod) {
116        Date loanDueDate = null;
117
118        if (loanPeriod.equalsIgnoreCase(OLEConstants.FIXED_DUE_DATE)) {
119            loanDueDate = new FixedDateUtil().getFixedDateByPolicyId(getPolicyId());
120        } else {
121            StringTokenizer stringTokenizer = new StringTokenizer(loanPeriod, "-");
122            String amount = stringTokenizer.nextToken();
123            String period = stringTokenizer.nextToken();
124
125            if (period.equalsIgnoreCase("m")) {
126                loanDueDate = DateUtils.addMinutes(new Date(), Integer.parseInt(amount));
127            } else if (period.equalsIgnoreCase("h")) {
128                loanDueDate = DateUtils.addHours(new Date(), Integer.parseInt(amount));
129            } else if (period.equalsIgnoreCase("d")) {
130                loanDueDate = DateUtils.addDays(new Date(), Integer.parseInt(amount));
131            } else if (period.equalsIgnoreCase("w")) {
132                loanDueDate = DateUtils.addWeeks(new Date(), Integer.parseInt(amount));
133            }
134        }
135        return loanDueDate;
136    }
137
138    private Date handleGracePeriod(Date loanDueDate) {
139        Date updatedDate = null;
140        String gracePeriod = getGracePeriodForIncludingNonWorkingHours();
141        if (StringUtils.isNotBlank(gracePeriod)) {
142            StringTokenizer stringTokenizer = new StringTokenizer(gracePeriod, "-");
143            String amount = stringTokenizer.nextToken();
144            String interval = stringTokenizer.nextToken();
145            if (interval.equalsIgnoreCase("m")) {
146                updatedDate = DateUtils.addMinutes(loanDueDate, Integer.valueOf(amount));
147            } else if (interval.equalsIgnoreCase("h")) {
148                updatedDate = DateUtils.addHours(loanDueDate, Integer.valueOf(amount));
149            }
150        } else {
151            updatedDate = loanDueDate;
152        }
153        return updatedDate;
154    }
155
156    public Boolean includeNonWorkingHours() {
157        return ParameterValueResolver.getInstance().getParameterAsBoolean(OLEConstants
158                .APPL_ID, OLEConstants.DLVR_NMSPC, OLEConstants.DLVR_CMPNT, OLEConstants.CALENDER_FLAG);
159    }
160
161
162    private boolean compareTimes(Map<String, String> openTimeForTheGivenDay, Map<String, String> closingTimeForTheGivenDay, Date loanDueDate) {
163        Calendar closeTimeCalendar = resolveDateTime(closingTimeForTheGivenDay, loanDueDate);
164        Calendar openTimeCalendar = resolveDateTime(openTimeForTheGivenDay, loanDueDate);
165        //Compares for the givne day if the loan due time falls within the closing time
166        return (openTimeCalendar.getTime().compareTo(loanDueDate) <= 0 && closeTimeCalendar.getTime().compareTo(loanDueDate) >= 0);
167    }
168
169    private Calendar resolveDateTime(Map<String, String> closingTimeForTheGivenDay, Date loanDueDate) {
170        String time = closingTimeForTheGivenDay.keySet().iterator().next();
171        String timeSession = closingTimeForTheGivenDay.get(time);
172        StringTokenizer timeTokenizer = new StringTokenizer(time, ":");
173        int hour = Integer.parseInt(timeTokenizer.nextToken());
174
175        Calendar instance = Calendar.getInstance();
176        //The date is being set to the loan due date to ensure the comparisons are for the given day.
177        instance.setTime(loanDueDate);
178        //The hour and minutes are for closing times.
179        instance.set(Calendar.HOUR_OF_DAY, hour);
180        instance.set(Calendar.MINUTE, Integer.parseInt(timeTokenizer.nextToken()));
181        return instance;
182    }
183
184
185    public OleCalendar getActiveCalendar(Date date, String groupId) {
186        if (!getCalendarMap().containsKey(groupId)) {
187            List<OleCalendar> oleCalendarList = getOleCalendars(groupId);
188            for (OleCalendar calendar : oleCalendarList) {
189                if (calendarExists(new Timestamp(date.getTime()), calendar.getBeginDate(), calendar.getEndDate())) {
190                    getCalendarMap().put(groupId, calendar);
191                }
192            }
193        }
194        return getCalendarMap().get(groupId);
195    }
196
197    protected List<OleCalendar> getOleCalendars(String groupId) {
198        HashMap criteriaMap = new HashMap();
199        criteriaMap.put(OLEConstants.CALENDER_ID, groupId);
200        return (List<OleCalendar>) getBusinessObjectService().findMatching(OleCalendar.class, criteriaMap);
201    }
202
203    public boolean calendarExists(Timestamp date, Timestamp fromDate, Timestamp toDate) {
204        Interval interval;
205        if (null != fromDate) {
206            if (null != toDate) {
207                interval = new Interval(fromDate.getTime(), toDate.getTime());
208                return interval.contains(date.getTime());
209            } else {
210                return date.compareTo(fromDate) > 0 ? true : false;
211            }
212        }
213
214        return false;
215    }
216
217    public Map<String, OleCalendar> getCalendarMap() {
218        if (null == calendarMap) {
219            calendarMap = new HashMap<>();
220        }
221        return calendarMap;
222    }
223
224    public void setCalendarMap(Map<String, OleCalendar> calendarMap) {
225        this.calendarMap = calendarMap;
226    }
227
228    public BusinessObjectService getBusinessObjectService() {
229        if (null == businessObjectService) {
230            businessObjectService = KRADServiceLocator.getBusinessObjectService();
231        }
232        return businessObjectService;
233    }
234
235    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
236        this.businessObjectService = businessObjectService;
237    }
238
239    public String getGracePeriodForIncludingNonWorkingHours() {
240        return ParameterValueResolver.getInstance().getParameter(OLEConstants
241                .APPL_ID, OLEConstants.DLVR_NMSPC, OLEConstants.DLVR_CMPNT, OLEConstants.GRACE_PERIOD_FOR_NON_WORKING_HOURS);
242    }
243
244
245    public String getPolicyId() {
246        return policyId;
247    }
248
249    public void setPolicyId(String policyId) {
250        this.policyId = policyId;
251    }
252
253    public boolean isLoanDueTimeWithinWorkingHours(Date loanDueDate, List<? extends OleBaseCalendarWeek> oleBaseCalendarWeekList) {
254        Map<String, Map<String, String>> openAndClosingTimeForTheGivenDay = getOpenAndClosingTimeForTheGivenDay(loanDueDate, oleBaseCalendarWeekList);
255        return compareTimes(openAndClosingTimeForTheGivenDay.get("openTime"), openAndClosingTimeForTheGivenDay.get("closeTime"), loanDueDate);
256    }
257
258    private Map<String, Map<String, String>> getOpenAndClosingTimeForTheGivenDay(Date loanDueDate, List<? extends OleBaseCalendarWeek> oleBaseCalendarWeekList) {
259        Map<String, Map<String, String>> openingAndClosingTimeMap;
260
261        openingAndClosingTimeMap = getOpenAndClosingTimeForTheGivenDayFromWeekList(loanDueDate, oleBaseCalendarWeekList);
262
263        return openingAndClosingTimeMap;
264    }
265
266
267    public Map<String, Map<String, String>> getOpenAndClosingTimeForTheGivenDayFromWeekList(Date loanDueDate, List<? extends OleBaseCalendarWeek> oleCalendarWeekList) {
268        int day = loanDueDate.getDay();
269        Map<String, Map<String, String>> openingAndClosingTimeMap = new HashMap<>();
270        Map<String, String> closingTimeMap = new HashMap<>();
271        Map<String, String> openingTimeMap = new HashMap<>();
272
273        for (Iterator<? extends OleBaseCalendarWeek> iterator = oleCalendarWeekList.iterator(); iterator.hasNext(); ) {
274            OleBaseCalendarWeek OleBaseCalendarWeek = iterator.next();
275            String startDay = OleBaseCalendarWeek.getStartDay();
276            if (startDay.equals(String.valueOf(day))) {
277                resolveOpenAndCloseTimesForCalendarWeek(closingTimeMap, openingTimeMap, OleBaseCalendarWeek);
278                break;
279            }
280            String endDay = OleBaseCalendarWeek.getEndDay();
281            //The start day may not always be Sunday (0); Hence the check.
282            if (Integer.valueOf(startDay) < Integer.valueOf(endDay)) {
283                if (day > Integer.valueOf(startDay) && day <= Integer.valueOf(endDay)) {
284                    resolveOpenAndCloseTimesForCalendarWeek(closingTimeMap, openingTimeMap, OleBaseCalendarWeek);
285                    break;
286                }
287            } else {
288                if (day < Integer.valueOf(startDay) && day >= Integer.valueOf(endDay)) {
289                    resolveOpenAndCloseTimesForCalendarWeek(closingTimeMap, openingTimeMap, OleBaseCalendarWeek);
290                    break;
291                }
292            }
293        }
294        openingAndClosingTimeMap.put("openTime", openingTimeMap);
295        openingAndClosingTimeMap.put("closeTime", closingTimeMap);
296        return openingAndClosingTimeMap;
297    }
298
299    private void resolveOpenAndCloseTimesForCalendarWeek(Map<String, String> closingTimeMap, Map<String, String> openingTimeMap, OleBaseCalendarWeek oleBaseCalendarWeek) {
300        String closeTime = oleBaseCalendarWeek.getCloseTime();
301        String closeTimeSession = oleBaseCalendarWeek.getCloseTimeSession();
302        closingTimeMap.put(closeTime, closeTimeSession);
303
304        String openTime = oleBaseCalendarWeek.getOpenTime();
305        String openTimeSession = oleBaseCalendarWeek.getOpenTimeSession();
306        openingTimeMap.put(openTime, openTimeSession);
307    }
308
309    public OleCalendar getActiveCalendar() {
310        return activeCalendar;
311    }
312
313    public void setActiveCalendar(OleCalendar activeCalendar) {
314        this.activeCalendar = activeCalendar;
315    }
316}