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