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.lm.leaveblock.service; 017 018 import java.math.BigDecimal; 019 import java.sql.Timestamp; 020 import java.util.*; 021 022 import org.apache.commons.collections.CollectionUtils; 023 import org.apache.commons.lang.StringUtils; 024 import org.apache.commons.lang.time.DateUtils; 025 import org.apache.log4j.Logger; 026 import org.joda.time.DateTime; 027 import org.joda.time.DateTimeConstants; 028 import org.joda.time.DateTimeZone; 029 import org.joda.time.Interval; 030 import org.kuali.hr.lm.LMConstants; 031 import org.kuali.hr.lm.leaveblock.LeaveBlock; 032 import org.kuali.hr.lm.leaveblock.LeaveBlockHistory; 033 import org.kuali.hr.lm.leaveblock.dao.LeaveBlockDao; 034 import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader; 035 import org.kuali.hr.time.assignment.Assignment; 036 import org.kuali.hr.time.calendar.CalendarEntries; 037 import org.kuali.hr.time.earncode.EarnCode; 038 import org.kuali.hr.time.service.base.TkServiceLocator; 039 import org.kuali.hr.time.util.TKContext; 040 import org.kuali.hr.time.util.TKUtils; 041 import org.kuali.hr.time.util.TkConstants; 042 import org.kuali.hr.time.workflow.TimesheetDocumentHeader; 043 import org.kuali.rice.krad.service.KRADServiceLocator; 044 045 public class LeaveBlockServiceImpl implements LeaveBlockService { 046 047 private static final Logger LOG = Logger.getLogger(LeaveBlockServiceImpl.class); 048 049 private LeaveBlockDao leaveBlockDao; 050 051 @Override 052 public LeaveBlock getLeaveBlock(String leaveBlockId) { 053 return leaveBlockDao.getLeaveBlock(leaveBlockId); 054 } 055 056 public LeaveBlockDao getLeaveBlockDao() { 057 return leaveBlockDao; 058 } 059 060 public void setLeaveBlockDao(LeaveBlockDao leaveBlockDao) { 061 this.leaveBlockDao = leaveBlockDao; 062 } 063 064 public List<LeaveBlock> getLeaveBlocksForDocumentId(String documentId) { 065 return leaveBlockDao.getLeaveBlocksForDocumentId(documentId); 066 } 067 068 069 @Override 070 public List<LeaveBlock> getLeaveBlocks(String principalId, Date beginDate, 071 Date endDate) { 072 return leaveBlockDao.getLeaveBlocks(principalId, beginDate, endDate); 073 } 074 075 @Override 076 public List<LeaveBlock> getLeaveBlocksWithAccrualCategory(String principalId, Date beginDate, 077 Date endDate, String accrualCategory) { 078 return leaveBlockDao.getLeaveBlocksWithAccrualCategory(principalId, beginDate, endDate, accrualCategory); 079 } 080 081 @Override 082 public List<LeaveBlock> getLeaveBlocksWithType(String principalId, Date beginDate, 083 Date endDate, String leaveBlockType) { 084 return leaveBlockDao.getLeaveBlocksWithType(principalId, beginDate, endDate, leaveBlockType); 085 } 086 087 @Override 088 public List<LeaveBlock> getLeaveBlocksSinceCarryOver(String principalId, Map<String, LeaveBlock> carryOver, DateTime endDate, boolean includeAllAccrualCategories) { 089 return leaveBlockDao.getLeaveBlocksSinceCarryOver(principalId, carryOver, endDate, includeAllAccrualCategories); 090 } 091 092 @Override 093 public Map<String, LeaveBlock> getLastCarryOverBlocks(String principalId, Date asOfDate) { 094 if (StringUtils.isEmpty(principalId)) { 095 return Collections.emptyMap(); 096 } 097 return leaveBlockDao.getLastCarryOverBlocks(principalId, LMConstants.LEAVE_BLOCK_TYPE.CARRY_OVER, asOfDate); 098 } 099 100 @Override 101 public void saveLeaveBlocks(List<LeaveBlock> leaveBlocks) { 102 KRADServiceLocator.getBusinessObjectService().save(leaveBlocks); 103 104 List<LeaveBlockHistory> leaveBlockHistories = new ArrayList<LeaveBlockHistory>(); 105 for (LeaveBlock leaveBlock : leaveBlocks) { 106 LeaveBlockHistory lbh = new LeaveBlockHistory(leaveBlock); 107 lbh.setAction(LMConstants.ACTION.ADD); 108 leaveBlockHistories.add(lbh); 109 } 110 111 KRADServiceLocator.getBusinessObjectService().save(leaveBlockHistories); 112 } 113 114 @Override 115 public void deleteLeaveBlock(String leaveBlockId, String principalId) { 116 LeaveBlock leaveBlock = getLeaveBlock(leaveBlockId); 117 118 // leaveBlock.setPrincipalIdModified(TKContext.getTargetPrincipalId()); 119 // leaveBlock.setTimestamp(TKUtils.getCurrentTimestamp()); 120 121 // Make entry into LeaveBlockHistory table 122 LeaveBlockHistory leaveBlockHistory = new LeaveBlockHistory(leaveBlock); 123 leaveBlockHistory.setPrincipalIdDeleted(principalId); 124 leaveBlockHistory.setTimestampDeleted(new Timestamp(System.currentTimeMillis())); 125 leaveBlockHistory.setAction(LMConstants.ACTION.DELETE); 126 127 // deleting leaveblock 128 KRADServiceLocator.getBusinessObjectService().delete(leaveBlock); 129 130 // creating history 131 KRADServiceLocator.getBusinessObjectService().save(leaveBlockHistory); 132 133 134 } 135 136 @Override 137 public void saveLeaveBlock(LeaveBlock leaveBlock, String principalId) { 138 139 // first delete and create new entry in the database 140 KRADServiceLocator.getBusinessObjectService().delete(leaveBlock); 141 142 // create new 143 leaveBlock.setLmLeaveBlockId(null); 144 leaveBlock.setTimestamp(new Timestamp(System.currentTimeMillis())); 145 leaveBlock.setPrincipalIdModified(principalId); 146 KRADServiceLocator.getBusinessObjectService().save(leaveBlock); 147 148 // save history 149 LeaveBlockHistory lbh = new LeaveBlockHistory(leaveBlock); 150 lbh.setAction(LMConstants.ACTION.MODIFIED); 151 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh); 152 153 } 154 155 @Override 156 public void addLeaveBlocks(DateTime beginDate, DateTime endDate, CalendarEntries ce, String selectedEarnCode, 157 BigDecimal hours, String description, Assignment selectedAssignment, String spanningWeeks, String leaveBlockType, String principalId) { 158 159 DateTimeZone timezone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback(); 160 DateTime calBeginDateTime = beginDate; 161 DateTime calEndDateTime = endDate; 162 163 if(ce != null) { 164 calBeginDateTime = ce.getBeginLocalDateTime().toDateTime(); 165 calEndDateTime = ce.getEndLocalDateTime().toDateTime(); 166 } else { 167 throw new RuntimeException("Calendar Entry parameter is null."); 168 } 169 170 Interval calendarInterval = new Interval(calBeginDateTime, calEndDateTime); 171 172 // To create the correct interval by the given begin and end dates, 173 // we need to plus one day on the end date to include that date 174 175 Calendar startCal = Calendar.getInstance(); 176 startCal.setTimeInMillis(beginDate.getMillis()); 177 startCal.set(Calendar.HOUR_OF_DAY, 0); 178 startCal.set(Calendar.MINUTE, 0); 179 startCal.set(Calendar.SECOND, 0); 180 startCal.set(Calendar.MILLISECOND, 0); 181 182 183 Calendar endCal = Calendar.getInstance(); 184 endCal.setTimeInMillis(endDate.getMillis()); 185 endCal.add(Calendar.DATE, 1); 186 endCal.set(Calendar.HOUR_OF_DAY, 0); 187 endCal.set(Calendar.MINUTE, 0); 188 endCal.set(Calendar.SECOND, 0); 189 endCal.set(Calendar.MILLISECOND, 0); 190 191 List<Interval> leaveBlockIntervals = TKUtils.createDaySpan(new DateTime(startCal.getTimeInMillis()), new DateTime(endCal.getTimeInMillis()), TKUtils.getSystemDateTimeZone()); 192 // List<Interval> leaveBlockIntervals = TKUtils.createDaySpan(beginDate.toDateMidnight().toDateTime(), endDate.plusDays(1).toDateMidnight().toDateTime(), TKUtils.getSystemDateTimeZone()); 193 194 // need to use beginDate and endDate of the calendar to find all leaveBlocks since LeaveCalendarDocument Id is not always available 195 List<LeaveBlock> currentLeaveBlocks = getLeaveBlocks(principalId, calBeginDateTime.toDate(), calEndDateTime.toDate()); 196 197 // use the current calendar's begin and end date to figure out if this pay period has a leaveDocument 198 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService() 199 .getDocumentHeader(principalId, ce.getBeginLocalDateTime().toDateTime().toDate(), ce.getEndLocalDateTime().toDateTime().toDate()); 200 String docId = lcdh == null ? null : lcdh.getDocumentId(); 201 202 // TODO: need to integrate with the scheduled timeoff. 203 Timestamp beginTemp = new Timestamp(beginDate.getMillis()); 204 Timestamp endTimestamp = new Timestamp(endDate.getMillis()); 205 Interval firstDay = null; 206 for (Interval leaveBlockInt : leaveBlockIntervals) { 207 if (calendarInterval.contains(leaveBlockInt)) { 208 // KPME-1446 if "Include weekends" check box is checked, don't add Sat and Sun to the leaveblock list 209 if (StringUtils.isEmpty(spanningWeeks) && 210 (leaveBlockInt.getStart().getDayOfWeek() == DateTimeConstants.SATURDAY ||leaveBlockInt.getStart().getDayOfWeek() == DateTimeConstants.SUNDAY)) { 211 212 // do nothing 213 } else { 214 215 // Currently, we store the accrual category value in the leave code table, but store accrual category id in the leaveBlock. 216 // That's why there is a two step server call to get the id. This might be changed in the future. 217 218 java.sql.Date sqlDate = new java.sql.Date(ce.getEndLocalDateTime().toDateTime().toDate().getTime()); 219 220 CalendarEntries calendarEntry = TkServiceLocator.getCalendarEntriesService().getCurrentCalendarEntriesByCalendarId(ce.getHrCalendarId(), TKUtils.getCurrentDate()); 221 Date leaveBlockDate = new DateTime(leaveBlockInt.getStartMillis()).toDate(); 222 223 String requestStatus = LMConstants.REQUEST_STATUS.USAGE; 224 if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) { 225 TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeaderForDate(principalId, leaveBlockDate); 226 if (tdh != null) { 227 if (DateUtils.isSameDay(leaveBlockDate, tdh.getEndDate()) || leaveBlockDate.after(tdh.getEndDate())) { 228 requestStatus = LMConstants.REQUEST_STATUS.PLANNED; 229 } 230 } else { 231 requestStatus = LMConstants.REQUEST_STATUS.PLANNED; 232 } 233 } else { 234 if (DateUtils.isSameDay(leaveBlockDate, calendarEntry.getEndPeriodDateTime()) || leaveBlockDate.after(calendarEntry.getEndPeriodDateTime())) { 235 requestStatus = LMConstants.REQUEST_STATUS.PLANNED; 236 } 237 } 238 239 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, sqlDate); 240 241 if(earnCodeObj != null && earnCodeObj.getRecordMethod().equals(LMConstants.RECORD_METHOD.TIME)) { 242 if (firstDay != null) { 243 if(!leaveBlockInt.contains(endTimestamp.getTime())){ 244 beginTemp = new Timestamp(leaveBlockInt.getStartMillis()); 245 } else if((leaveBlockInt.getStartMillis() - endTimestamp.getTime()) != 0){ 246 247 hours = TKUtils.getHoursBetween(leaveBlockInt.getStartMillis(), endTimestamp.getTime()); 248 hours = negateHoursIfNecessary(leaveBlockType, hours); 249 250 LeaveBlock leaveBlock = buildLeaveBlock(new DateTime(leaveBlockInt.getStartMillis()), docId, principalId, selectedEarnCode, hours, description, earnCodeObj.getAccrualCategory(), selectedAssignment, requestStatus, leaveBlockType, new Timestamp(leaveBlockInt.getStartMillis()), endTimestamp); 251 252 if (!currentLeaveBlocks.contains(leaveBlock)) { 253 currentLeaveBlocks.add(leaveBlock); 254 } 255 break; 256 } 257 } 258 if (leaveBlockInt.contains(beginTemp.getTime())) { 259 260 firstDay = leaveBlockInt; 261 262 if (leaveBlockInt.contains(endTimestamp.getTime()) || (endTimestamp.getTime() == leaveBlockInt.getEnd().getMillis())) { 263 264 hours = TKUtils.getHoursBetween(beginTemp.getTime(), endTimestamp.getTime()); 265 hours = negateHoursIfNecessary(leaveBlockType, hours); 266 267 LeaveBlock leaveBlock = buildLeaveBlock(new DateTime(leaveBlockInt.getStartMillis()), docId, principalId, selectedEarnCode, hours, description, earnCodeObj.getAccrualCategory(), selectedAssignment, requestStatus, leaveBlockType, beginTemp, endTimestamp); 268 269 if (!currentLeaveBlocks.contains(leaveBlock)) { 270 currentLeaveBlocks.add(leaveBlock); 271 } 272 273 break; 274 275 } else { 276 // create a leave block that wraps the 24 hr day 277 hours = TKUtils.getHoursBetween(beginTemp.getTime(), firstDay.getEndMillis()); 278 hours = negateHoursIfNecessary(leaveBlockType, hours); 279 280 LeaveBlock leaveBlock = buildLeaveBlock(new DateTime(leaveBlockInt.getStartMillis()), docId, principalId, selectedEarnCode, hours, description, earnCodeObj.getAccrualCategory(), selectedAssignment, requestStatus, leaveBlockType, beginTemp, new Timestamp(firstDay.getEndMillis())); 281 282 if (!currentLeaveBlocks.contains(leaveBlock)) { 283 currentLeaveBlocks.add(leaveBlock); 284 } 285 286 } 287 } 288 } else { 289 hours = negateHoursIfNecessary(leaveBlockType, hours); 290 LeaveBlock leaveBlock = buildLeaveBlock(new DateTime(leaveBlockInt.getStartMillis()), docId, principalId, selectedEarnCode, hours, description, earnCodeObj.getAccrualCategory(), 291 selectedAssignment, requestStatus, leaveBlockType, null, null); 292 if (!currentLeaveBlocks.contains(leaveBlock)) { 293 currentLeaveBlocks.add(leaveBlock); 294 } 295 } 296 } 297 } 298 } 299 saveLeaveBlocks(currentLeaveBlocks); 300 } 301 302 private BigDecimal negateHoursIfNecessary(String leaveBlockType, BigDecimal hours) { 303 if ((leaveBlockType.equals(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_CALENDAR) 304 || leaveBlockType.equals((LMConstants.LEAVE_BLOCK_TYPE.TIME_CALENDAR))) 305 && BigDecimal.ZERO.compareTo(hours) < 0) { 306 hours = hours.negate(); 307 } 308 return hours; 309 } 310 311 312 public LeaveBlock buildLeaveBlock(DateTime leaveDate, String docId, String principalId, String selectedEarnCode, 313 BigDecimal hours, String description, String accrualCategory, Assignment selectedAssignment, String requestStatus, String leaveBlockType, Timestamp beginTimeStamp, Timestamp endTimestamp) { 314 315 LeaveBlock leaveBlock = new LeaveBlock.Builder(leaveDate, docId, principalId, selectedEarnCode, hours) 316 .description(description) 317 .principalIdModified(principalId) 318 .timestamp(TKUtils.getCurrentTimestamp()) 319 .scheduleTimeOffId("0") 320 .accrualCategory(accrualCategory) 321 .workArea(selectedAssignment.getWorkArea()) 322 .jobNumber(selectedAssignment.getJobNumber()) 323 .task(selectedAssignment.getTask()) 324 .requestStatus(requestStatus) 325 .leaveBlockType(leaveBlockType) 326 .build(); 327 328 leaveBlock.setBeginTimestamp(beginTimeStamp); 329 leaveBlock.setEndTimestamp(endTimestamp); 330 331 return leaveBlock; 332 } 333 // KPME-1447 334 @Override 335 public void updateLeaveBlock(LeaveBlock leaveBlock, String principalId) { 336 //verify that if leave block is usage, leave amount is negative 337 if ((LMConstants.LEAVE_BLOCK_TYPE.LEAVE_CALENDAR.equals(leaveBlock.getLeaveBlockType()) 338 || LMConstants.LEAVE_BLOCK_TYPE.LEAVE_CALENDAR.equals(leaveBlock.getLeaveBlockType())) 339 && BigDecimal.ZERO.compareTo(leaveBlock.getLeaveAmount()) < 0) { 340 leaveBlock.setLeaveAmount(leaveBlock.getLeaveAmount().negate()); 341 } 342 343 // Make entry into LeaveBlockHistory table 344 LeaveBlockHistory leaveBlockHistory = new LeaveBlockHistory(leaveBlock); 345 leaveBlockHistory.setPrincipalIdDeleted(principalId); 346 leaveBlockHistory.setTimestampDeleted(new Timestamp(System.currentTimeMillis())); 347 leaveBlockHistory.setAction(LMConstants.ACTION.MODIFIED); 348 349 KRADServiceLocator.getBusinessObjectService().save(leaveBlock); 350 351 // creating history 352 KRADServiceLocator.getBusinessObjectService().save(leaveBlockHistory); 353 } 354 355 public static List<Interval> createDaySpan(DateTime beginDateTime, DateTime endDateTime, DateTimeZone zone) { 356 beginDateTime = beginDateTime.toDateTime(zone); 357 endDateTime = endDateTime.toDateTime(zone); 358 List<Interval> dayIntervals = new ArrayList<Interval>(); 359 360 DateTime currDateTime = beginDateTime; 361 while (currDateTime.isBefore(endDateTime)) { 362 DateTime prevDateTime = currDateTime; 363 currDateTime = currDateTime.plusDays(1); 364 Interval daySpan = new Interval(prevDateTime, currDateTime); 365 dayIntervals.add(daySpan); 366 } 367 368 return dayIntervals; 369 } 370 371 @Override 372 public List<LeaveBlock> getLeaveBlocks(String principalId, String leaveBlockType, String requestStatus, Date currentDate) { 373 return leaveBlockDao.getLeaveBlocks(principalId, leaveBlockType, requestStatus, currentDate); 374 } 375 376 @Override 377 public List<LeaveBlock> getLeaveBlocks(String principalId, String leaveBlockType, String requestStatus, Date beginDate, Date endDate) { 378 return leaveBlockDao.getLeaveBlocks(principalId, leaveBlockType, requestStatus, beginDate, endDate); 379 } 380 381 @Override 382 public List<LeaveBlock> getLeaveBlocksForDate(String principalId, Date leaveDate) { 383 return leaveBlockDao.getLeaveBlocksForDate(principalId, leaveDate); 384 } 385 386 @Override 387 public List<LeaveBlock> getNotAccrualGeneratedLeaveBlocksForDate(String principalId, Date leaveDate) { 388 return leaveBlockDao.getNotAccrualGeneratedLeaveBlocksForDate(principalId, leaveDate); 389 } 390 391 public List<LeaveBlock> getLeaveBlocksForTimeCalendar(String principalId, Date beginDate, Date endDate, List<String> assignmentKeys) { 392 List<LeaveBlock> col = leaveBlockDao.getCalendarLeaveBlocks(principalId, beginDate, endDate); 393 List<LeaveBlock> leaveBlocks = filterLeaveBlocksForTimeCalendar(col, assignmentKeys); 394 return leaveBlocks; 395 } 396 397 public List<LeaveBlock> getLeaveBlocksForLeaveCalendar(String principalId, Date beginDate, Date endDate, List<String> assignmentKeys) { 398 List<LeaveBlock> col = leaveBlockDao.getLeaveBlocks(principalId, beginDate, endDate); 399 List<LeaveBlock> leaveBlocks = filterLeaveBlocksForLeaveCalendar(col, assignmentKeys); 400 return leaveBlocks; 401 } 402 403 public List<LeaveBlock> filterLeaveBlocksForTimeCalendar(List<LeaveBlock> lbs, List<String> assignmentKeys) { 404 if(CollectionUtils.isEmpty(assignmentKeys)) { 405 return lbs; 406 } 407 List<LeaveBlock> results = new ArrayList<LeaveBlock> (); 408 for(LeaveBlock lb : lbs) { 409 if(lb != null) { 410 if (StringUtils.equals(lb.getLeaveBlockType(), LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE) 411 && StringUtils.isNotEmpty(lb.getScheduleTimeOffId()) 412 && lb.getLeaveAmount().compareTo(BigDecimal.ZERO) < 0) { 413 // display usage leave blocks generated by system scheduled time off 414 results.add(lb); 415 } else if(StringUtils.isNotEmpty(lb.getAssignmentKey()) && assignmentKeys.contains(lb.getAssignmentKey())) { 416 if (StringUtils.equals(lb.getLeaveBlockType(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_CALENDAR)) { 417 // only add approved leave blocks that are created from leave calendar 418 if (StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.APPROVED)) { 419 results.add(lb); 420 } 421 } else if(StringUtils.equals(lb.getLeaveBlockType(), LMConstants.LEAVE_BLOCK_TYPE.TIME_CALENDAR)) { 422 results.add(lb); 423 } 424 } 425 } 426 } 427 return results; 428 } 429 430 public List<LeaveBlock> filterLeaveBlocksForLeaveCalendar(List<LeaveBlock> lbs, List<String> assignmentKeys) { 431 if(CollectionUtils.isEmpty(assignmentKeys)) { 432 return lbs; 433 } 434 List<LeaveBlock> leaveBlocks = new ArrayList<LeaveBlock>(); 435 for(LeaveBlock lb : lbs) { 436 if(lb != null) { 437 if(lb.getLeaveBlockType().equals(LMConstants.LEAVE_BLOCK_TYPE.TIME_CALENDAR)) { 438 if(StringUtils.isNotEmpty(lb.getAssignmentKey()) && assignmentKeys.contains(lb.getAssignmentKey())) { 439 leaveBlocks.add(lb); 440 } 441 } else { 442 leaveBlocks.add(lb); 443 } 444 } 445 446 447 } 448 return leaveBlocks; 449 } 450 451 @Override 452 public void deleteLeaveBlocksForDocumentId(String documentId){ 453 leaveBlockDao.deleteLeaveBlocksForDocumentId(documentId); 454 } 455 456 457 @Override 458 public List<LeaveBlock> getAccrualGeneratedLeaveBlocks(String principalId, Date beginDate, Date endDate) { 459 return leaveBlockDao.getAccrualGeneratedLeaveBlocks(principalId, beginDate, endDate); 460 } 461 462 @Override 463 public List<LeaveBlock> getSSTOLeaveBlocks(String principalId, String sstoId, Date accruledDate) { 464 return leaveBlockDao.getSSTOLeaveBlocks(principalId, sstoId, accruledDate); 465 } 466 467 @Override 468 public List<LeaveBlock> getABELeaveBlocksSinceTime(String principalId, Timestamp lastRanTime) { 469 return leaveBlockDao.getABELeaveBlocksSinceTime(principalId, lastRanTime); 470 } 471 }