001    /**
002     * Copyright 2004-2013 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.hr.time.util;
017    
018    import java.sql.Time;
019    import java.util.ArrayList;
020    import java.util.Collections;
021    import java.util.Comparator;
022    import java.util.List;
023    import java.util.ListIterator;
024    
025    import org.apache.commons.collections.CollectionUtils;
026    import org.joda.time.DateTime;
027    import org.joda.time.DateTimeZone;
028    import org.joda.time.Interval;
029    import org.joda.time.LocalDateTime;
030    import org.joda.time.LocalTime;
031    import org.kuali.hr.time.calendar.Calendar;
032    import org.kuali.hr.time.calendar.CalendarEntries;
033    import org.kuali.hr.time.flsa.FlsaDay;
034    import org.kuali.hr.time.flsa.FlsaWeek;
035    import org.kuali.hr.time.service.base.TkServiceLocator;
036    import org.kuali.hr.time.timeblock.TimeBlock;
037    import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
038    
039    public class TkTimeBlockAggregate {
040            public List<List<TimeBlock>> dayTimeBlockList = new ArrayList<List<TimeBlock>>();
041            private CalendarEntries payCalendarEntry;
042            private Calendar payCalendar;
043    
044        /**
045         * Defaults to using SYSTEM time zone.
046         *
047         * @param timeBlocks
048         * @param payCalendarEntry
049         */
050            public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry){
051                    this(timeBlocks, payCalendarEntry, TkServiceLocator.getCalendarService().getCalendar(payCalendarEntry.getHrCalendarId()));
052            }
053    
054        /**
055         * Defaults to using SYSTEM time zone.
056         *
057         * @param timeBlocks
058         * @param payCalendarEntry
059         * @param payCalendar
060         */
061            public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar) {
062            this(timeBlocks, payCalendarEntry, payCalendar, false);
063        }
064    
065        /**
066         * Provides the option to refer to the time zone adjusted time for the current
067         * user.
068         * @param timeBlocks
069         * @param payCalendarEntry
070         * @param payCalendar
071         * @param useUserTimeZone
072         */
073        public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar, boolean useUserTimeZone) {
074                    this.payCalendarEntry = payCalendarEntry;
075                    this.payCalendar = payCalendar;
076    
077                    List<Interval> dayIntervals = TKUtils.getDaySpanForCalendarEntry(payCalendarEntry);
078                    for(Interval dayInt : dayIntervals){
079                            List<TimeBlock> dayTimeBlocks = new ArrayList<TimeBlock>();
080                            for(TimeBlock timeBlock : timeBlocks){
081    
082                    // Assumption: Timezones can only be switched at pay period end boundaries.
083                    // If the above assumption becomes false, the logic below will need to
084                    // accommodate virtual chopping of time blocks to have them fit nicely
085                    // in the "days" that are displayed to users.
086    
087                                    DateTime beginTime = useUserTimeZone ? timeBlock.getBeginTimeDisplay() : new DateTime(timeBlock.getBeginTimestamp(), TKUtils.getSystemDateTimeZone());
088                                    DateTime endTime = useUserTimeZone ? timeBlock.getEndTimeDisplay() :  new DateTime(timeBlock.getEndTimestamp(), TKUtils.getSystemDateTimeZone());
089                                    if(dayInt.contains(beginTime)){
090                                            if(dayInt.contains(endTime) || endTime.compareTo(dayInt.getEnd()) == 0){
091                                                    // determine if the time block needs to be pushed forward / backward
092                                                    if(beginTime.getHourOfDay() < dayInt.getStart().getHourOfDay()) {
093                                                            timeBlock.setPushBackward(true);
094                                                    }
095    
096                                                    dayTimeBlocks.add(timeBlock);
097                                            }
098                                    }
099                            }
100                            dayTimeBlockList.add(dayTimeBlocks);
101                    }
102            }
103    
104        public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar, boolean useUserTimeZone, List<Interval> dayIntervals) {
105            this.payCalendarEntry = payCalendarEntry;
106                    this.payCalendar = payCalendar;
107                    
108                    for(Interval dayInt : dayIntervals){
109                            List<TimeBlock> dayTimeBlocks = new ArrayList<TimeBlock>();
110                            for(TimeBlock timeBlock : timeBlocks){
111    
112                    // Assumption: Timezones can only be switched at pay period end boundaries.
113                    // If the above assumption becomes false, the logic below will need to
114                    // accommodate virtual chopping of time blocks to have them fit nicely
115                    // in the "days" that are displayed to users.
116    
117                                    DateTime beginTime = useUserTimeZone ? timeBlock.getBeginTimeDisplay() : new DateTime(timeBlock.getBeginTimestamp(), TKUtils.getSystemDateTimeZone());
118                                    DateTime endTime = useUserTimeZone ? timeBlock.getEndTimeDisplay() :  new DateTime(timeBlock.getEndTimestamp(), TKUtils.getSystemDateTimeZone());
119                                    if(dayInt.contains(beginTime)){
120                                            if(dayInt.contains(endTime) || endTime.compareTo(dayInt.getEnd()) == 0){
121                                                    // determine if the time block needs to be pushed forward / backward
122                                                    if(beginTime.getHourOfDay() < dayInt.getStart().getHourOfDay()) {
123                                                            timeBlock.setPushBackward(true);
124                                                    }
125    
126                                                    dayTimeBlocks.add(timeBlock);
127                                            }
128                                    }
129                            }
130                            dayTimeBlockList.add(dayTimeBlocks);
131                    }
132                    
133        }
134        
135        
136            public List<TimeBlock> getFlattenedTimeBlockList(){
137                    List<TimeBlock> lstTimeBlocks = new ArrayList<TimeBlock>();
138                    for(List<TimeBlock> timeBlocks : dayTimeBlockList){
139                            lstTimeBlocks.addAll(timeBlocks);
140                    }
141    
142                    Collections.sort(lstTimeBlocks, new Comparator<TimeBlock>() { // Sort the Time Blocks
143                            public int compare(TimeBlock tb1, TimeBlock tb2) {
144                                    if (tb1 != null && tb2 != null)
145                                            return tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
146                                    return 0;
147                            }
148                    });
149    
150                    return lstTimeBlocks;
151            }
152    
153            /**
154             * Provides a way to access all of the time blocks for a given week.
155             *
156             * Outer list is 0 indexed list representing days in a week.
157             * Inner List are all of the time blocks for that day.
158             *
159             * Ex.
160             *
161             * List<List<TimeBlock>> week0 = getWeekTimeBlocks(0);
162             * List<TimeBlock> day0 = week0.get(0);
163             *
164             * @param week
165             * @return
166             */
167            public List<List<TimeBlock>> getWeekTimeBlocks(int week){
168                    int startIndex = week*7;
169                    int endIndex = (week*7)+7;
170                    endIndex = endIndex > dayTimeBlockList.size() ? dayTimeBlockList.size() : endIndex;
171    
172            // Need to sort each day by clock time.
173            List<List<TimeBlock>> wList = dayTimeBlockList.subList(startIndex, endIndex);
174            for (List<TimeBlock> dList : wList) {
175                Collections.sort(dList, new Comparator<TimeBlock>() { // Sort the Time Blocks
176                    public int compare(TimeBlock tb1, TimeBlock tb2) {
177                        if (tb1 != null && tb2 != null)
178                            return tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
179                        return 0;
180                    }
181                });
182            }
183    
184                    return wList;
185            }
186    
187            /**
188             * When consuming these weeks, you must be aware that you could be on a
189             * virtual day, ie noon to noon schedule and have your FLSA time start
190             * before the virtual day start,
191             * but still have a full 7 days for your week.
192         *
193         * @param zone The TimeZone to use when constructing this relative sorting.
194             */
195            public List<FlsaWeek> getFlsaWeeks(DateTimeZone zone){
196                    int flsaDayConstant = payCalendar.getFlsaBeginDayConstant();
197                    Time flsaBeginTime  = payCalendar.getFlsaBeginTime();
198    
199                    // We can use these to build our interval, we have to make sure we
200                    // place them on the proper day when we construct it.
201                    LocalTime flsaBeginLocalTime = LocalTime.fromDateFields(flsaBeginTime);
202    
203                    // Defines both the start date and the start virtual time.
204                    // We will add 1 day to this to move over all days.
205                    //
206                    // FLSA time is set.  This is an FLSA start date.
207            LocalDateTime startLDT = payCalendarEntry.getBeginLocalDateTime();
208    //              DateTime startDate = new DateTime(payCalendarEntry.getBeginPeriodDateTime());
209    //              startDate = startDate.toLocalDate().toDateTime(flsaBeginLocalTime,TKUtils.getSystemDateTimeZone());
210    
211                    List<FlsaWeek> flsaWeeks = new ArrayList<FlsaWeek>();
212                    List<TimeBlock> flatSortedBlockList = getFlattenedTimeBlockList();
213                    FlsaWeek currentWeek = new FlsaWeek(flsaDayConstant, flsaBeginLocalTime, LocalTime.fromDateFields(payCalendarEntry.getBeginPeriodDateTime()));
214                    FlsaDay flsaDay = new FlsaDay(startLDT, flatSortedBlockList, zone);
215                    currentWeek.addFlsaDay(flsaDay);
216                    flsaWeeks.add(currentWeek);
217    
218                    for (int i = 1; i < dayTimeBlockList.size(); i++) {
219                            LocalDateTime currentDate = startLDT.plusDays(i);
220                            flsaDay = new FlsaDay(currentDate, flatSortedBlockList, zone);
221    
222                            if (currentDate.getDayOfWeek() == flsaDayConstant) {
223                                    currentWeek = new FlsaWeek(flsaDayConstant, flsaBeginLocalTime, flsaBeginLocalTime);
224                                    flsaWeeks.add(currentWeek);     
225                            }
226                            
227                            currentWeek.addFlsaDay(flsaDay);
228                    }
229    
230                    return flsaWeeks;
231            }
232            
233            public List<List<FlsaWeek>> getFlsaWeeks(DateTimeZone zone, String principalId) {
234                    List<List<FlsaWeek>> flsaWeeks = new ArrayList<List<FlsaWeek>>();
235                    
236                    List<FlsaWeek> currentWeeks = getFlsaWeeks(zone);
237                    
238                    for (ListIterator<FlsaWeek> weekIterator = currentWeeks.listIterator(); weekIterator.hasNext(); ) {
239                            List<FlsaWeek> flsaWeek = new ArrayList<FlsaWeek>();
240                            
241                            int index = weekIterator.nextIndex();
242                            FlsaWeek currentWeek = weekIterator.next();
243                            
244                            if (index == 0 && !currentWeek.isFirstWeekFull()) {
245                                    CalendarEntries previousCalendarEntry = TkServiceLocator.getCalendarEntriesService().getPreviousCalendarEntriesByCalendarId(payCalendar.getHrCalendarId(), payCalendarEntry);
246                                    if (previousCalendarEntry != null) {
247                                            TimesheetDocumentHeader timesheetDocumentHeader = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, previousCalendarEntry.getBeginPeriodDateTime(), previousCalendarEntry.getEndPeriodDateTime());
248                                            if (timesheetDocumentHeader != null) { 
249                                                    List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(timesheetDocumentHeader.getDocumentId());
250                                                    if (CollectionUtils.isNotEmpty(timeBlocks)) {
251                                                            TkTimeBlockAggregate previousAggregate = new TkTimeBlockAggregate(timeBlocks, previousCalendarEntry, payCalendar, true);
252                                                            List<FlsaWeek> previousWeek = previousAggregate.getFlsaWeeks(zone);
253                                                            if (CollectionUtils.isNotEmpty(previousWeek)) {
254                                                                    flsaWeek.add(previousWeek.get(previousWeek.size() - 1));
255                                                            }
256                                                    }
257                                             }
258                                    }
259                            }
260                            
261                            flsaWeek.add(currentWeek);
262                            
263                            if (index == currentWeeks.size() - 1 && !currentWeek.isLastWeekFull()) {
264                                    CalendarEntries nextCalendarEntry = TkServiceLocator.getCalendarEntriesService().getNextCalendarEntriesByCalendarId(payCalendar.getHrCalendarId(), payCalendarEntry);
265                                    if (nextCalendarEntry != null) {
266                                            TimesheetDocumentHeader timesheetDocumentHeader = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, nextCalendarEntry.getBeginPeriodDateTime(), nextCalendarEntry.getEndPeriodDateTime());
267                                            if (timesheetDocumentHeader != null) { 
268                                                    List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(timesheetDocumentHeader.getDocumentId());
269                                                    if (CollectionUtils.isNotEmpty(timeBlocks)) {
270                                                            TkTimeBlockAggregate nextAggregate = new TkTimeBlockAggregate(timeBlocks, nextCalendarEntry, payCalendar, true);
271                                                            List<FlsaWeek> nextWeek = nextAggregate.getFlsaWeeks(zone);
272                                                            if (CollectionUtils.isNotEmpty(nextWeek)) {
273                                                                    flsaWeek.add(nextWeek.get(0));
274                                                            }
275                                                    }
276                                             }
277                                    }
278                            }
279                            
280                            flsaWeeks.add(flsaWeek);
281                    }
282                    
283                    return flsaWeeks;
284            }
285    
286            /**
287             * @return the total number of weeks that this object represents.
288             */
289            public int numberOfAggregatedWeeks() {
290                    int weeks = 0;
291    
292                    if (this.dayTimeBlockList.size() > 0) {
293                            weeks = this.dayTimeBlockList.size() / 7;
294                            if (this.dayTimeBlockList.size() % 7 > 0)
295                                    weeks++;
296                    }
297    
298                    return weeks;
299            }
300    
301            public List<List<TimeBlock>> getDayTimeBlockList() {
302                    return dayTimeBlockList;
303            }
304    
305            public CalendarEntries getPayCalendarEntry() {
306                    return payCalendarEntry;
307            }
308    
309            public void setPayCalendarEntry(CalendarEntries payCalendarEntry) {
310                    this.payCalendarEntry = payCalendarEntry;
311            }
312    
313            public Calendar getPayCalendar() {
314                    return payCalendar;
315            }
316    
317            public void setPayCalendar(Calendar payCalendar) {
318                    this.payCalendar = payCalendar;
319            }
320    
321    }