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 }