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