View Javadoc

1   /**
2    * Copyright 2004-2013 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.hr.time.util;
17  
18  import java.sql.Time;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.Comparator;
22  import java.util.List;
23  import java.util.ListIterator;
24  
25  import org.apache.commons.collections.CollectionUtils;
26  import org.joda.time.DateTime;
27  import org.joda.time.DateTimeZone;
28  import org.joda.time.Interval;
29  import org.joda.time.LocalDateTime;
30  import org.joda.time.LocalTime;
31  import org.kuali.hr.time.calendar.Calendar;
32  import org.kuali.hr.time.calendar.CalendarEntries;
33  import org.kuali.hr.time.flsa.FlsaDay;
34  import org.kuali.hr.time.flsa.FlsaWeek;
35  import org.kuali.hr.time.service.base.TkServiceLocator;
36  import org.kuali.hr.time.timeblock.TimeBlock;
37  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
38  
39  public class TkTimeBlockAggregate {
40  	private List<List<TimeBlock>> dayTimeBlockList = new ArrayList<List<TimeBlock>>();
41  	private CalendarEntries payCalendarEntry;
42  	private Calendar payCalendar;
43  
44      /**
45       * Defaults to using SYSTEM time zone.
46       *
47       * @param timeBlocks
48       * @param payCalendarEntry
49       */
50  	public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry){
51  		this(timeBlocks, payCalendarEntry, TkServiceLocator.getCalendarService().getCalendar(payCalendarEntry.getHrCalendarId()));
52  	}
53  
54      /**
55       * Defaults to using SYSTEM time zone.
56       *
57       * @param timeBlocks
58       * @param payCalendarEntry
59       * @param payCalendar
60       */
61  	public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar) {
62          this(timeBlocks, payCalendarEntry, payCalendar, false);
63      }
64  
65      /**
66       * Provides the option to refer to the time zone adjusted time for the current
67       * user.
68       * @param timeBlocks
69       * @param payCalendarEntry
70       * @param payCalendar
71       * @param useUserTimeZone
72       */
73      public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar, boolean useUserTimeZone) {
74  		this.payCalendarEntry = payCalendarEntry;
75  		this.payCalendar = payCalendar;
76  
77          List<Interval> dayIntervals = null;
78          if (useUserTimeZone) {
79              dayIntervals = TKUtils.getDaySpanForCalendarEntry(payCalendarEntry);
80          } else {
81              dayIntervals = TKUtils.getDaySpanForCalendarEntry(payCalendarEntry, TKUtils.getSystemDateTimeZone());
82          }
83  		for(Interval dayInt : dayIntervals){
84  			List<TimeBlock> dayTimeBlocks = new ArrayList<TimeBlock>();
85  			for(TimeBlock timeBlock : timeBlocks){
86  
87                  // Assumption: Timezones can only be switched at pay period end boundaries.
88                  // If the above assumption becomes false, the logic below will need to
89                  // accommodate virtual chopping of time blocks to have them fit nicely
90                  // in the "days" that are displayed to users.
91  
92  				DateTime beginTime = useUserTimeZone ? timeBlock.getBeginTimeDisplay() : new DateTime(timeBlock.getBeginTimestamp(), TKUtils.getSystemDateTimeZone());
93  				DateTime endTime = useUserTimeZone ? timeBlock.getEndTimeDisplay() :  new DateTime(timeBlock.getEndTimestamp(), TKUtils.getSystemDateTimeZone());
94  				if(dayInt.contains(beginTime)){
95  					if(dayInt.contains(endTime) || endTime.compareTo(dayInt.getEnd()) == 0){
96  						// determine if the time block needs to be pushed forward / backward
97  						if(beginTime.getHourOfDay() < dayInt.getStart().getHourOfDay()) {
98  							timeBlock.setPushBackward(true);
99  						}
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 }