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.timesheet.service; 017 018 import java.math.BigDecimal; 019 import java.sql.Timestamp; 020 import java.util.ArrayList; 021 import java.util.Date; 022 import java.util.LinkedList; 023 import java.util.List; 024 025 import org.apache.commons.collections.CollectionUtils; 026 import org.apache.commons.lang.StringUtils; 027 import org.apache.log4j.Logger; 028 import org.joda.time.DateTime; 029 import org.kuali.hr.job.Job; 030 import org.kuali.hr.lm.LMConstants; 031 import org.kuali.hr.lm.balancetransfer.BalanceTransfer; 032 import org.kuali.hr.lm.leaveblock.LeaveBlock; 033 import org.kuali.hr.lm.leavepayout.LeavePayout; 034 import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff; 035 import org.kuali.hr.time.assignment.Assignment; 036 import org.kuali.hr.time.calendar.CalendarEntries; 037 import org.kuali.hr.time.principal.PrincipalHRAttributes; 038 import org.kuali.hr.time.service.base.TkServiceLocator; 039 import org.kuali.hr.time.timeblock.TimeBlock; 040 import org.kuali.hr.time.timesheet.TimesheetDocument; 041 import org.kuali.hr.time.util.TKContext; 042 import org.kuali.hr.time.util.TKUser; 043 import org.kuali.hr.time.util.TKUtils; 044 import org.kuali.hr.time.util.TkConstants; 045 import org.kuali.hr.time.workflow.TimesheetDocumentHeader; 046 import org.kuali.rice.core.api.config.property.ConfigContext; 047 import org.kuali.rice.kew.api.KewApiServiceLocator; 048 import org.kuali.rice.kew.api.WorkflowDocument; 049 import org.kuali.rice.kew.api.WorkflowDocumentFactory; 050 import org.kuali.rice.kew.api.exception.WorkflowException; 051 import org.kuali.rice.kew.api.note.Note; 052 import org.kuali.rice.kim.api.identity.Person; 053 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 054 055 public class TimesheetServiceImpl implements TimesheetService { 056 057 @SuppressWarnings("unused") 058 private static final Logger LOG = Logger.getLogger(TimesheetServiceImpl.class); 059 060 @Override 061 public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument) { 062 routeTimesheet(principalId, timesheetDocument, TkConstants.DOCUMENT_ACTIONS.ROUTE); 063 } 064 065 @Override 066 public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) { 067 timesheetAction(action, principalId, timesheetDocument); 068 } 069 070 @Override 071 public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument) { 072 timesheetAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, timesheetDocument); 073 } 074 075 @Override 076 public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) { 077 timesheetAction(action, principalId, timesheetDocument); 078 } 079 080 @Override 081 public void disapproveTimesheet(String principalId, TimesheetDocument timesheetDocument) { 082 timesheetAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, timesheetDocument); 083 } 084 085 protected void timesheetAction(String action, String principalId, TimesheetDocument timesheetDocument) { 086 WorkflowDocument wd = null; 087 if (timesheetDocument != null) { 088 String rhid = timesheetDocument.getDocumentId(); 089 wd = WorkflowDocumentFactory.loadDocument(principalId, rhid); 090 091 if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) { 092 wd.route("Routing for Approval"); 093 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) { 094 Note.Builder builder = Note.Builder.create(rhid, principalId); 095 builder.setCreateDate(new DateTime()); 096 builder.setText("Routed via Employee Approval batch job"); 097 KewApiServiceLocator.getNoteService().createNote(builder.build()); 098 099 wd.route("Batch job routing timesheet"); 100 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.APPROVE)) { 101 if (TKContext.getUser().getCurrentTargetRoles().isSystemAdmin() && 102 !TKContext.getUser().getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) { 103 wd.superUserBlanketApprove("Superuser approving timesheet."); 104 } else { 105 wd.approve("Approving timesheet."); 106 } 107 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) { 108 Note.Builder builder = Note.Builder.create(rhid, principalId); 109 builder.setCreateDate(new DateTime()); 110 builder.setText("Approved via Supervisor Approval batch job"); 111 KewApiServiceLocator.getNoteService().createNote(builder.build()); 112 113 wd.superUserBlanketApprove("Batch job approving timesheet."); 114 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.DISAPPROVE)) { 115 if (TKContext.getUser().getCurrentTargetRoles().isSystemAdmin() 116 && !TKContext.getUser().getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) { 117 wd.superUserDisapprove("Superuser disapproving timesheet."); 118 } else { 119 wd.disapprove("Disapproving timesheet."); 120 } 121 } 122 } 123 } 124 125 @Override 126 public TimesheetDocument openTimesheetDocument(String principalId, CalendarEntries calendarDates) throws WorkflowException { 127 TimesheetDocument timesheetDocument = null; 128 129 Date begin = calendarDates.getBeginPeriodDateTime(); 130 Date end = calendarDates.getEndPeriodDateTime(); 131 132 TimesheetDocumentHeader header = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, begin, end); 133 134 if (header == null) { 135 List<Assignment> activeAssignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, calendarDates); 136 //TkServiceLocator.getAssignmentService().getAssignments(principalId, TKUtils.getTimelessDate(payCalendarDates.getEndPeriodDate())); 137 if (activeAssignments.size() == 0) { 138 LOG.warn("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate()); 139 return null; 140 //throw new RuntimeException("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate()); 141 } 142 143 Person person = KimApiServiceLocator.getPersonService().getPerson(principalId); 144 String principalName = person != null ? person.getName() : StringUtils.EMPTY; 145 String endDateString = TKUtils.formatDate(new java.sql.Date(end.getTime())); 146 String timesheetDocumentTitle = TimesheetDocument.TIMESHEET_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + endDateString; 147 148 timesheetDocument = this.initiateWorkflowDocument(principalId, begin, end, calendarDates, TimesheetDocument.TIMESHEET_DOCUMENT_TYPE, timesheetDocumentTitle); 149 //timesheetDocument.setPayCalendarEntry(calendarDates); 150 //this.loadTimesheetDocumentData(timesheetDocument, principalId, calendarDates); 151 //TODO switch this to scheduled time offs 152 //this.loadHolidaysOnTimesheet(timesheetDocument, principalId, begin, end); 153 } else { 154 timesheetDocument = this.getTimesheetDocument(header.getDocumentId()); 155 timesheetDocument.setCalendarEntry(calendarDates); 156 } 157 158 timesheetDocument.setTimeSummary(TkServiceLocator.getTimeSummaryService().getTimeSummary(timesheetDocument)); 159 return timesheetDocument; 160 } 161 162 public void loadHolidaysOnTimesheet(TimesheetDocument timesheetDocument, String principalId, Date beginDate, Date endDate) { 163 PrincipalHRAttributes principalCalendar = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, new java.sql.Date(beginDate.getTime())); 164 if (principalCalendar != null && StringUtils.isNotEmpty(principalCalendar.getLeavePlan())) { 165 List<SystemScheduledTimeOff> sstoList = TkServiceLocator.getSysSchTimeOffService() 166 .getSystemScheduledTimeOffForPayPeriod(principalCalendar.getLeavePlan(), beginDate, endDate); 167 Assignment sstoAssign = TkServiceLocator.getAssignmentService().getAssignmentToApplyScheduledTimeOff(timesheetDocument, TKUtils.getTimelessDate(endDate)); 168 if (sstoAssign != null) { 169 for(SystemScheduledTimeOff ssto : sstoList) { 170 BigDecimal sstoCalcHours = TkServiceLocator.getSysSchTimeOffService().calculateSysSchTimeOffHours(sstoAssign.getJob(), ssto.getAmountofTime()); 171 TimeBlock timeBlock = TkServiceLocator.getTimeBlockService().createTimeBlock(timesheetDocument, new Timestamp(ssto.getScheduledTimeOffDate().getTime()), 172 new Timestamp(ssto.getScheduledTimeOffDate().getTime()), sstoAssign, TkConstants.HOLIDAY_EARN_CODE, sstoCalcHours, BigDecimal.ZERO, false, false, TKContext.getPrincipalId()); 173 timesheetDocument.getTimeBlocks().add(timeBlock); 174 } 175 //If system scheduled time off are loaded will need to save them to the database 176 if (CollectionUtils.isNotEmpty(sstoList)) { 177 TkServiceLocator.getTimeBlockService().saveTimeBlocks(new LinkedList<TimeBlock>(), timesheetDocument.getTimeBlocks(), TKContext.getPrincipalId()); 178 } 179 } 180 } 181 } 182 183 protected TimesheetDocument initiateWorkflowDocument(String principalId, Date payBeginDate, Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException { 184 TimesheetDocument timesheetDocument = null; 185 WorkflowDocument workflowDocument = null; 186 187 workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title); 188 189 String status = workflowDocument.getStatus().getCode(); 190 TimesheetDocumentHeader documentHeader = new TimesheetDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status); 191 192 documentHeader.setDocumentId(workflowDocument.getDocumentId().toString()); 193 documentHeader.setDocumentStatus("I"); 194 195 TkServiceLocator.getTimesheetDocumentHeaderService().saveOrUpdate(documentHeader); 196 timesheetDocument = new TimesheetDocument(documentHeader); 197 timesheetDocument.setCalendarEntry(calendarEntries); 198 loadTimesheetDocumentData(timesheetDocument, principalId, calendarEntries); 199 TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(timesheetDocument, payEndDate); 200 201 if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) { 202 deleteNonApprovedLeaveBlocks(principalId, calendarEntries.getBeginPeriodDate(), calendarEntries.getEndPeriodDate()); 203 } 204 205 return timesheetDocument; 206 } 207 208 private void deleteNonApprovedLeaveBlocks(String principalId, Date beginDate, Date endDate) { 209 String batchUserPrincipalId = getBatchUserPrincipalId(); 210 211 if (batchUserPrincipalId != null) { 212 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate); 213 214 for (LeaveBlock leaveBlock : leaveBlocks) { 215 if (!StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.APPROVED)) { 216 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId); 217 } 218 } 219 } else { 220 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME); 221 LOG.error("Could not delete leave request blocks due to missing batch user " + principalName); 222 } 223 } 224 225 private String getBatchUserPrincipalId() { 226 String principalId = null; 227 228 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME); 229 Person person = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName); 230 if (person != null) { 231 principalId = person.getPrincipalId(); 232 } 233 234 return principalId; 235 } 236 237 public List<TimeBlock> getPrevDocumentTimeBlocks(String principalId, Date payBeginDate) { 238 TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, payBeginDate); 239 if (prevTdh == null) { 240 return new ArrayList<TimeBlock>(); 241 } 242 return TkServiceLocator.getTimeBlockService().getTimeBlocks(prevTdh.getDocumentId()); 243 } 244 245 @Override 246 public TimesheetDocument getTimesheetDocument(String documentId) { 247 TimesheetDocument timesheetDocument = null; 248 TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId); 249 250 if (tdh != null) { 251 timesheetDocument = new TimesheetDocument(tdh); 252 CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE); 253 loadTimesheetDocumentData(timesheetDocument, tdh.getPrincipalId(), pce); 254 255 timesheetDocument.setCalendarEntry(pce); 256 } else { 257 throw new RuntimeException("Could not find TimesheetDocumentHeader for DocumentID: " + documentId); 258 } 259 return timesheetDocument; 260 } 261 262 protected void loadTimesheetDocumentData(TimesheetDocument tdoc, String principalId, CalendarEntries payCalEntry) { 263 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, payCalEntry); 264 List<Job> jobs = TkServiceLocator.getJobService().getJobs(principalId, TKUtils.getTimelessDate(payCalEntry.getEndPeriodDate())); 265 List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(tdoc.getDocumentHeader().getDocumentId()); 266 267 tdoc.setAssignments(assignments); 268 tdoc.setJobs(jobs); 269 tdoc.setTimeBlocks(timeBlocks); 270 } 271 272 public boolean isSynchronousUser() { 273 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(TKUser.getCurrentTargetPerson().getPrincipalId(), TKUtils.getCurrentDate()); 274 boolean isSynchronousUser = true; 275 for (Assignment assignment : assignments) { 276 isSynchronousUser &= assignment.isSynchronous(); 277 } 278 return isSynchronousUser; 279 } 280 281 //this is an admin function used for testing 282 public void deleteTimesheet(String documentId) { 283 TkServiceLocator.getTimeBlockService().deleteTimeBlocksAssociatedWithDocumentId(documentId); 284 TkServiceLocator.getTimesheetDocumentHeaderService().deleteTimesheetHeader(documentId); 285 } 286 287 public TimeBlock resetWorkedHours(TimeBlock timeBlock) { 288 if (timeBlock.getBeginTime() != null && timeBlock.getEndTime() != null && StringUtils.equals(timeBlock.getEarnCodeType(), TkConstants.EARN_CODE_TIME)) { 289 BigDecimal hours = TKUtils.getHoursBetween(timeBlock.getBeginTime().getTime(), timeBlock.getEndTime().getTime()); 290 timeBlock.setHours(hours); 291 } 292 return timeBlock; 293 } 294 295 @Override 296 public void resetTimeBlock(List<TimeBlock> timeBlocks) { 297 for (TimeBlock tb : timeBlocks) { 298 resetWorkedHours(tb); 299 } 300 TkServiceLocator.getTimeBlockService().resetTimeHourDetail(timeBlocks); 301 } 302 303 @Override 304 public boolean isReadyToApprove(TimesheetDocument document) { 305 if (document == null) { 306 return false; 307 } 308 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(), 309 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER); 310 leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(), 311 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT)); 312 for(LeaveBlock lb : leaveBlocks) { 313 if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) && 314 !StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED)) 315 return false; 316 } 317 return true; 318 /* List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(), 319 document.getCalendarEntry().getBeginPeriodDate(), 320 document.getCalendarEntry().getEndPeriodDate()); 321 if (!CollectionUtils.isEmpty(balanceTransfers)) { 322 for(BalanceTransfer balanceTransfer : balanceTransfers) { 323 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE)) 324 return false; 325 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus()) 326 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) { 327 return false; 328 } 329 } 330 } 331 List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(), 332 document.getCalendarEntry().getBeginPeriodDate(), 333 document.getCalendarEntry().getEndPeriodDate()); 334 if (!CollectionUtils.isEmpty(leavePayouts)) { 335 for(LeavePayout payout : leavePayouts) { 336 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE)) 337 return false; 338 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus()) 339 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) { 340 return false; 341 } 342 } 343 } 344 return true;*/ 345 } 346 347 }