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 }