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