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 }