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.timeblock.service; 017 018 import java.math.BigDecimal; 019 import java.sql.Date; 020 import java.sql.Timestamp; 021 import java.util.ArrayList; 022 import java.util.List; 023 024 import org.apache.commons.lang.StringUtils; 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.job.Job; 031 import org.kuali.hr.lm.earncodesec.EarnCodeSecurity; 032 import org.kuali.hr.time.assignment.Assignment; 033 import org.kuali.hr.time.earncode.EarnCode; 034 import org.kuali.hr.time.paytype.PayType; 035 import org.kuali.hr.time.service.base.TkServiceLocator; 036 import org.kuali.hr.time.timeblock.TimeBlock; 037 import org.kuali.hr.time.timeblock.TimeBlockHistory; 038 import org.kuali.hr.time.timeblock.TimeHourDetail; 039 import org.kuali.hr.time.timeblock.dao.TimeBlockDao; 040 import org.kuali.hr.time.timesheet.TimesheetDocument; 041 import org.kuali.hr.time.util.TKContext; 042 import org.kuali.hr.time.util.TKUtils; 043 import org.kuali.hr.time.util.TkConstants; 044 import org.kuali.rice.krad.service.KRADServiceLocator; 045 import org.kuali.rice.krad.util.GlobalVariables; 046 047 public class TimeBlockServiceImpl implements TimeBlockService { 048 049 private static final Logger LOG = Logger.getLogger(TimeBlockServiceImpl.class); 050 private TimeBlockDao timeBlockDao; 051 052 public void setTimeBlockDao(TimeBlockDao timeBlockDao) { 053 this.timeBlockDao = timeBlockDao; 054 } 055 056 //This function is used to build timeblocks that span days 057 public List<TimeBlock> buildTimeBlocksSpanDates(Assignment assignment, String earnCode, TimesheetDocument timesheetDocument, 058 Timestamp beginTimestamp, Timestamp endTimestamp, BigDecimal hours, BigDecimal amount, 059 Boolean isClockLogCreated, Boolean isLunchDeleted, String spanningWeeks, String userPrincipalId) { 060 DateTimeZone zone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback(); 061 DateTime beginDt = new DateTime(beginTimestamp.getTime(), zone); 062 DateTime endDt = beginDt.toLocalDate().toDateTime((new DateTime(endTimestamp.getTime(), zone)).toLocalTime(), zone); 063 if (endDt.isBefore(beginDt)) endDt = endDt.plusDays(1); 064 065 List<Interval> dayInt = TKUtils.getDaySpanForCalendarEntry(timesheetDocument.getCalendarEntry()); 066 TimeBlock firstTimeBlock = new TimeBlock(); 067 List<TimeBlock> lstTimeBlocks = new ArrayList<TimeBlock>(); 068 for (Interval dayIn : dayInt) { 069 if (dayIn.contains(beginDt)) { 070 if (dayIn.contains(endDt) || dayIn.getEnd().equals(endDt)) { 071 // KPME-1446 if "Include weekends" check box is checked, don't add Sat and Sun to the timeblock list 072 if (StringUtils.isEmpty(spanningWeeks) && 073 (dayIn.getStart().getDayOfWeek() == DateTimeConstants.SATURDAY ||dayIn.getStart().getDayOfWeek() == DateTimeConstants.SUNDAY)) { 074 // do nothing 075 } else { 076 firstTimeBlock = createTimeBlock(timesheetDocument, beginTimestamp, new Timestamp(endDt.getMillis()), assignment, earnCode, hours, amount, false, isLunchDeleted, userPrincipalId); 077 lstTimeBlocks.add(firstTimeBlock); 078 } 079 } else { 080 //TODO move this to prerule validation 081 //throw validation error if this case met error 082 } 083 } 084 } 085 086 DateTime endTime = new DateTime(endTimestamp.getTime(), zone); 087 DateTime endOfFirstDay = new DateTime(firstTimeBlock.getEndTimestamp(), zone); 088 long diffInMillis = endOfFirstDay.minus(beginDt.getMillis()).getMillis(); 089 DateTime currTime = beginDt.plusDays(1); 090 while (currTime.isBefore(endTime) || currTime.isEqual(endTime)) { 091 // KPME-1446 if "Include weekends" check box is checked, don't add Sat and Sun to the timeblock list 092 if (StringUtils.isEmpty(spanningWeeks) && 093 (currTime.getDayOfWeek() == DateTimeConstants.SATURDAY || currTime.getDayOfWeek() == DateTimeConstants.SUNDAY)) { 094 // do nothing 095 } else { 096 Timestamp begin = new Timestamp(currTime.getMillis()); 097 Timestamp end = new Timestamp((currTime.plus(diffInMillis).getMillis())); 098 TimeBlock tb = createTimeBlock(timesheetDocument, begin, end, assignment, earnCode, hours, amount, false, isLunchDeleted, userPrincipalId); 099 lstTimeBlocks.add(tb); 100 } 101 currTime = currTime.plusDays(1); 102 } 103 return lstTimeBlocks; 104 } 105 106 107 public List<TimeBlock> buildTimeBlocks(Assignment assignment, String earnCode, TimesheetDocument timesheetDocument, 108 Timestamp beginTimestamp, Timestamp endTimestamp, BigDecimal hours, BigDecimal amount, 109 Boolean isClockLogCreated, Boolean isLunchDeleted, String userPrincipalId) { 110 111 //Create 1 or many timeblocks if the span of timeblocks exceed more than one 112 //day that is determined by pay period day(24 hrs + period begin date) 113 Interval firstDay = null; 114 List<Interval> dayIntervals = TKUtils.getDaySpanForCalendarEntry(timesheetDocument.getCalendarEntry()); 115 List<TimeBlock> lstTimeBlocks = new ArrayList<TimeBlock>(); 116 Timestamp beginTemp = beginTimestamp; 117 118 for (Interval dayInt : dayIntervals) { 119 // the time period spans more than one day 120 if (firstDay != null) { 121 if(!dayInt.contains(endTimestamp.getTime())){ 122 beginTemp = new Timestamp(dayInt.getStartMillis()); 123 } else if((dayInt.getStartMillis() - endTimestamp.getTime()) != 0){ 124 TimeBlock tb = createTimeBlock(timesheetDocument, new Timestamp(dayInt.getStartMillis()), endTimestamp, assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted, userPrincipalId); 125 lstTimeBlocks.add(tb); 126 break; 127 } 128 } 129 if (dayInt.contains(beginTemp.getTime())) { 130 firstDay = dayInt; 131 // KPME-361 132 // added a condition to handle the time block which ends at 12a, e.g. a 10p-12a timeblock 133 // this is the same fix as TkTimeBlockAggregate 134 if (dayInt.contains(endTimestamp.getTime()) || (endTimestamp.getTime() == dayInt.getEnd().getMillis())) { 135 //create one timeblock if contained in one day interval 136 TimeBlock tb = createTimeBlock(timesheetDocument, beginTemp, endTimestamp, assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted, userPrincipalId); 137 tb.setBeginTimestamp(beginTemp); 138 tb.setEndTimestamp(endTimestamp); 139 lstTimeBlocks.add(tb); 140 break; 141 } else { 142 // create a timeblock that wraps the 24 hr day 143 TimeBlock tb = createTimeBlock(timesheetDocument, beginTemp, new Timestamp(dayInt.getEndMillis()), assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted, userPrincipalId); 144 tb.setBeginTimestamp(beginTemp); 145 tb.setEndTimestamp(new Timestamp(firstDay.getEndMillis())); 146 lstTimeBlocks.add(tb); 147 } 148 } 149 } 150 return lstTimeBlocks; 151 } 152 153 public void saveTimeBlocks(List<TimeBlock> oldTimeBlocks, List<TimeBlock> newTimeBlocks, String userPrincipalId) { 154 List<TimeBlock> alteredTimeBlocks = new ArrayList<TimeBlock>(); 155 for (TimeBlock tb : newTimeBlocks) { 156 boolean persist = true; 157 for (TimeBlock tbOld : oldTimeBlocks) { 158 if (tb.equals(tbOld)) { 159 persist = false; 160 break; 161 } 162 } 163 if (persist) { 164 alteredTimeBlocks.add(tb); 165 } 166 } 167 168 for (TimeBlock timeBlock : alteredTimeBlocks) { 169 TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(timeBlock.getTkTimeBlockId()); 170 timeBlock.setUserPrincipalId(userPrincipalId); 171 } 172 173 List<TimeBlock> savedTimeBlocks = (List<TimeBlock>) KRADServiceLocator.getBusinessObjectService().save(alteredTimeBlocks); 174 175 for (TimeBlock timeBlock : savedTimeBlocks) { 176 timeBlock.setTimeBlockHistories(createTimeBlockHistories(timeBlock, TkConstants.ACTIONS.ADD_TIME_BLOCK)); 177 KRADServiceLocator.getBusinessObjectService().save(timeBlock.getTimeBlockHistories()); 178 } 179 } 180 181 public void saveTimeBlocks(List<TimeBlock> tbList) { 182 for (TimeBlock tb : tbList) { 183 TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(tb.getTkTimeBlockId()); 184 timeBlockDao.saveOrUpdate(tb); 185 for(TimeBlockHistory tbh : tb.getTimeBlockHistories()){ 186 TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh); 187 } 188 } 189 } 190 191 public void updateTimeBlock(TimeBlock tb) { 192 timeBlockDao.saveOrUpdate(tb); 193 } 194 195 196 public TimeBlock createTimeBlock(TimesheetDocument timesheetDocument, Timestamp beginTime, Timestamp endTime, Assignment assignment, String earnCode, BigDecimal hours, BigDecimal amount, Boolean clockLogCreated, Boolean lunchDeleted, String userPrincipalId) { 197 DateTimeZone timezone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback(); 198 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(earnCode, timesheetDocument.getAsOfDate()); 199 200 TimeBlock tb = new TimeBlock(); 201 tb.setDocumentId(timesheetDocument.getDocumentHeader().getDocumentId()); 202 tb.setPrincipalId(timesheetDocument.getPrincipalId()); 203 tb.setJobNumber(assignment.getJobNumber()); 204 tb.setWorkArea(assignment.getWorkArea()); 205 tb.setTask(assignment.getTask()); 206 tb.setEarnCode(earnCode); 207 tb.setBeginTimestamp(beginTime); 208 tb.setBeginTimestampTimezone(timezone.getID()); 209 tb.setEndTimestamp(endTime); 210 tb.setEndTimestampTimezone(timezone.getID()); 211 tb.setBeginTimeDisplay(new DateTime(tb.getBeginTimestamp(), timezone)); 212 tb.setEndTimeDisplay(new DateTime(tb.getEndTimestamp(), timezone)); 213 // only calculate the hours from the time fields if the passed in hour is zero 214 if(hours == null || hours.compareTo(BigDecimal.ZERO) == 0) { 215 hours = TKUtils.getHoursBetween(beginTime.getTime(), endTime.getTime()); 216 } 217 tb.setAmount(amount); 218 //If earn code has an inflate min hours check if it is greater than zero 219 //and compare if the hours specified is less than min hours awarded for this 220 //earn code 221 if (earnCodeObj.getInflateMinHours() != null) { 222 if ((earnCodeObj.getInflateMinHours().compareTo(BigDecimal.ZERO) != 0) && 223 earnCodeObj.getInflateMinHours().compareTo(hours) > 0) { 224 hours = earnCodeObj.getInflateMinHours(); 225 } 226 } 227 //If earn code has an inflate factor multiple hours specified by the factor 228 if (earnCodeObj.getInflateFactor() != null) { 229 if ((earnCodeObj.getInflateFactor().compareTo(new BigDecimal(1.0)) != 0) 230 && (earnCodeObj.getInflateFactor().compareTo(BigDecimal.ZERO)!= 0) ) { 231 hours = earnCodeObj.getInflateFactor().multiply(hours, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE); 232 } 233 } 234 235 tb.setEarnCodeType(earnCodeObj.getEarnCodeType()); 236 tb.setHours(hours); 237 tb.setClockLogCreated(clockLogCreated); 238 tb.setUserPrincipalId(userPrincipalId); 239 tb.setTimestamp(new Timestamp(System.currentTimeMillis())); 240 tb.setLunchDeleted(lunchDeleted); 241 242 tb.setTimeHourDetails(this.createTimeHourDetails(tb.getEarnCode(), tb.getHours(), tb.getAmount(), tb.getTkTimeBlockId())); 243 244 return tb; 245 } 246 247 public TimeBlock getTimeBlock(String tkTimeBlockId) { 248 return timeBlockDao.getTimeBlock(tkTimeBlockId); 249 } 250 251 @Override 252 public void deleteTimeBlock(TimeBlock timeBlock) { 253 timeBlockDao.deleteTimeBlock(timeBlock); 254 255 } 256 257 public void resetTimeHourDetail(List<TimeBlock> origTimeBlocks) { 258 for (TimeBlock tb : origTimeBlocks) { 259 tb.setTimeHourDetails(createTimeHourDetails(tb.getEarnCode(), tb.getHours(), tb.getAmount(), tb.getTkTimeBlockId())); 260 //reset time block history details 261 for(TimeBlockHistory tbh : tb.getTimeBlockHistories()) { 262 TkServiceLocator.getTimeBlockHistoryService().addTimeBlockHistoryDetails(tbh,tb); 263 } 264 } 265 } 266 267 private List<TimeHourDetail> createTimeHourDetails(String earnCode, BigDecimal hours, BigDecimal amount, String timeBlockId) { 268 List<TimeHourDetail> timeHourDetails = new ArrayList<TimeHourDetail>(); 269 270 TimeHourDetail timeHourDetail = new TimeHourDetail(); 271 timeHourDetail.setEarnCode(earnCode); 272 timeHourDetail.setHours(hours); 273 timeHourDetail.setAmount(amount); 274 timeHourDetail.setTkTimeBlockId(timeBlockId); 275 timeHourDetails.add(timeHourDetail); 276 277 return timeHourDetails; 278 } 279 280 public List<TimeBlockHistory> createTimeBlockHistories(TimeBlock tb, String actionHistory) { 281 List<TimeBlockHistory> tbhs = new ArrayList<TimeBlockHistory>(); 282 283 TimeBlockHistory tbh = new TimeBlockHistory(tb); 284 tbh.setActionHistory(actionHistory); 285 // add time block history details to this time block history 286 TkServiceLocator.getTimeBlockHistoryService().addTimeBlockHistoryDetails(tbh, tb); 287 288 tbhs.add(tbh); 289 290 return tbhs; 291 } 292 293 // This method now translates time based on timezone settings. 294 // 295 public List<TimeBlock> getTimeBlocks(String documentId) { 296 List<TimeBlock> timeBlocks = timeBlockDao.getTimeBlocks(documentId); 297 TkServiceLocator.getTimezoneService().translateForTimezone(timeBlocks); 298 for(TimeBlock tb : timeBlocks) { 299 String earnCodeType = TkServiceLocator.getEarnCodeService().getEarnCodeType(tb.getEarnCode(), new java.sql.Date(tb.getBeginTimestamp().getTime())); 300 tb.setEarnCodeType(earnCodeType); 301 } 302 303 return timeBlocks; 304 } 305 306 public List<TimeBlock> getTimeBlocksForAssignment(Assignment assign) { 307 List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>(); 308 if(assign != null) { 309 timeBlocks = timeBlockDao.getTimeBlocksForAssignment(assign); 310 } 311 TkServiceLocator.getTimezoneService().translateForTimezone(timeBlocks); 312 for(TimeBlock tb : timeBlocks) { 313 String earnCodeType = TkServiceLocator.getEarnCodeService().getEarnCodeType(tb.getEarnCode(), new java.sql.Date(tb.getBeginTimestamp().getTime())); 314 tb.setEarnCodeType(earnCodeType); 315 } 316 return timeBlocks; 317 } 318 319 320 @Override 321 public void deleteTimeBlocksAssociatedWithDocumentId(String documentId) { 322 timeBlockDao.deleteTimeBlocksAssociatedWithDocumentId(documentId); 323 } 324 325 @Override 326 // figure out if the user has permission to edit/delete the time block 327 public Boolean isTimeBlockEditable(TimeBlock tb) { 328 String userId = GlobalVariables.getUserSession().getPrincipalId(); 329 330 if(userId != null) { 331 332 if(TKContext.getUser().isSystemAdmin()) { 333 return true; 334 } 335 336 if(TKContext.getUser().isTimesheetApprover() && TKContext.getUser().getApproverWorkAreas().contains(tb.getWorkArea()) 337 || TKContext.getUser().isTimesheetReviewer() && TKContext.getUser().getReviewerWorkAreas().contains(tb.getWorkArea())) { 338 Job job = TkServiceLocator.getJobService().getJob(TKContext.getTargetPrincipalId(),tb.getJobNumber(), tb.getEndDate()); 339 PayType payType = TkServiceLocator.getPayTypeService().getPayType(job.getHrPayType(), tb.getEndDate()); 340 if(StringUtils.equals(payType.getRegEarnCode(), tb.getEarnCode())){ 341 return true; 342 } 343 344 List<EarnCodeSecurity> deptEarnCodes = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), tb.getEndDate()); 345 for(EarnCodeSecurity dec : deptEarnCodes){ 346 if(dec.isApprover() && StringUtils.equals(dec.getEarnCode(), tb.getEarnCode())){ 347 return true; 348 } 349 } 350 } 351 352 if(userId.equals(TKContext.getTargetPrincipalId())) { 353 Job job = TkServiceLocator.getJobService().getJob(TKContext.getTargetPrincipalId(),tb.getJobNumber(), tb.getEndDate()); 354 PayType payType = TkServiceLocator.getPayTypeService().getPayType(job.getHrPayType(), tb.getEndDate()); 355 if(StringUtils.equals(payType.getRegEarnCode(), tb.getEarnCode())){ 356 return true; 357 } 358 359 List<EarnCodeSecurity> deptEarnCodes = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), tb.getEndDate()); 360 for(EarnCodeSecurity dec : deptEarnCodes){ 361 if(dec.isEmployee() && StringUtils.equals(dec.getEarnCode(), tb.getEarnCode())){ 362 return true; 363 } 364 } 365 // if the user is the creator of this time block 366 } 367 368 369 } 370 return false; 371 } 372 373 @Override 374 public List<TimeBlock> getTimeBlocksForClockLogEndId(String tkClockLogId) { 375 return timeBlockDao.getTimeBlocksForClockLogEndId(tkClockLogId); 376 } 377 @Override 378 public List<TimeBlock> getTimeBlocksForClockLogBeginId(String tkClockLogId) { 379 return timeBlockDao.getTimeBlocksForClockLogBeginId(tkClockLogId); 380 } 381 382 public List<TimeBlock> getTimeBlocks(){ 383 return timeBlockDao.getTimeBlocks(); 384 } 385 386 public List<TimeBlock> getLatestEndTimestamp(){ 387 return timeBlockDao.getLatestEndTimestamp(); 388 } 389 390 @Override 391 public List<TimeBlock> getOvernightTimeBlocks(String clockLogEndId) { 392 return timeBlockDao.getOvernightTimeBlocks(clockLogEndId); 393 } 394 395 @Override 396 public void deleteLunchDeduction(String tkTimeHourDetailId) { 397 TimeHourDetail thd = TkServiceLocator.getTimeHourDetailService().getTimeHourDetail(tkTimeHourDetailId); 398 TimeBlock tb = getTimeBlock(thd.getTkTimeBlockId()); 399 400 // mark the lunch deleted as Y 401 tb.setLunchDeleted(true); 402 // save the change 403 timeBlockDao.saveOrUpdate(tb); 404 // remove the related time hour detail row with the lunch deduction 405 TkServiceLocator.getTimeHourDetailService().removeTimeHourDetail(thd.getTkTimeHourDetailId()); 406 } 407 @Override 408 public List<TimeBlock> getTimeBlocksWithEarnCode(String earnCode, Date effDate) { 409 return timeBlockDao.getTimeBlocksWithEarnCode(earnCode, effDate); 410 } 411 }