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  	public 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 = TKUtils.getDaySpanForCalendarEntry(payCalendarEntry);
78  		for(Interval dayInt : dayIntervals){
79  			List<TimeBlock> dayTimeBlocks = new ArrayList<TimeBlock>();
80  			for(TimeBlock timeBlock : timeBlocks){
81  
82                  // Assumption: Timezones can only be switched at pay period end boundaries.
83                  // If the above assumption becomes false, the logic below will need to
84                  // accommodate virtual chopping of time blocks to have them fit nicely
85                  // in the "days" that are displayed to users.
86  
87  				DateTime beginTime = useUserTimeZone ? timeBlock.getBeginTimeDisplay() : new DateTime(timeBlock.getBeginTimestamp(), TKUtils.getSystemDateTimeZone());
88  				DateTime endTime = useUserTimeZone ? timeBlock.getEndTimeDisplay() :  new DateTime(timeBlock.getEndTimestamp(), TKUtils.getSystemDateTimeZone());
89  				if(dayInt.contains(beginTime)){
90  					if(dayInt.contains(endTime) || endTime.compareTo(dayInt.getEnd()) == 0){
91  						// determine if the time block needs to be pushed forward / backward
92  						if(beginTime.getHourOfDay() < dayInt.getStart().getHourOfDay()) {
93  							timeBlock.setPushBackward(true);
94  						}
95  
96  						dayTimeBlocks.add(timeBlock);
97  					}
98  				}
99  			}
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 }