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.leavecalendar.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.leavecalendar.LeaveCalendarDocument; 026 import org.kuali.hr.lm.leavecalendar.dao.LeaveCalendarDao; 027 import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader; 028 import org.kuali.hr.lm.workflow.LeaveRequestDocument; 029 import org.kuali.hr.time.assignment.Assignment; 030 import org.kuali.hr.time.calendar.CalendarEntries; 031 import org.kuali.hr.time.service.base.TkServiceLocator; 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.rice.core.api.config.property.ConfigContext; 037 import org.kuali.rice.kew.api.KewApiServiceLocator; 038 import org.kuali.rice.kew.api.WorkflowDocument; 039 import org.kuali.rice.kew.api.WorkflowDocumentFactory; 040 import org.kuali.rice.kew.api.exception.WorkflowException; 041 import org.kuali.rice.kew.api.note.Note; 042 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName; 043 import org.kuali.rice.kim.api.identity.principal.Principal; 044 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 045 import org.kuali.rice.krad.service.KRADServiceLocator; 046 047 import java.math.BigDecimal; 048 import java.util.Date; 049 import java.util.List; 050 051 public class LeaveCalendarServiceImpl implements LeaveCalendarService { 052 053 private static final Logger LOG = Logger.getLogger(LeaveCalendarServiceImpl.class); 054 055 private LeaveCalendarDao leaveCalendarDao; 056 057 @Override 058 public LeaveCalendarDocument getLeaveCalendarDocument(String documentId) { 059 LeaveCalendarDocument lcd = null; 060 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId); 061 062 if (lcdh != null) { 063 lcd = new LeaveCalendarDocument(lcdh); 064 CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(lcdh.getPrincipalId(), lcdh.getEndDate(), LMConstants.LEAVE_CALENDAR_TYPE); 065 lcd.setCalendarEntry(pce); 066 } else { 067 throw new RuntimeException("Could not find LeaveCalendarDocumentHeader for DocumentID: " + documentId); 068 } 069 070 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForDocumentId(documentId); 071 lcd.setLeaveBlocks(leaveBlocks); 072 073 // Fetching assignments 074 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(lcdh.getPrincipalId(), lcd.getCalendarEntry()); 075 lcd.setAssignments(assignments); 076 077 return lcd; 078 } 079 080 @Override 081 public LeaveCalendarDocument openLeaveCalendarDocument(String principalId, CalendarEntries calEntry) throws WorkflowException { 082 LeaveCalendarDocument doc; 083 084 Date begin = calEntry.getBeginPeriodDateTime(); 085 Date end = calEntry.getEndPeriodDateTime(); 086 087 LeaveCalendarDocumentHeader header = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, begin, end); 088 if (header == null) { 089 EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId); 090 String principalName = person != null && person.getDefaultName() != null ? person.getDefaultName().getCompositeName() : StringUtils.EMPTY; 091 String beginDateString = TKUtils.formatDate(new java.sql.Date(begin.getTime())); 092 String endDateString = TKUtils.formatDate(new java.sql.Date(end.getTime())); 093 String leaveCalendarDocumentTitle = LeaveCalendarDocument.LEAVE_CALENDAR_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + beginDateString + "-" + endDateString; 094 095 doc = initiateWorkflowDocument(principalId, begin, end, calEntry, LeaveCalendarDocument.LEAVE_CALENDAR_DOCUMENT_TYPE, leaveCalendarDocumentTitle); 096 } else { 097 doc = getLeaveCalendarDocument(header.getDocumentId()); 098 } 099 doc.setCalendarEntry(calEntry); 100 // TODO: need to set the summary 101 return doc; 102 } 103 104 //Should only create leave calendar document if active jobs were found with flsa elig = no and ben elg = yes 105 public boolean shouldCreateLeaveDocument(String principalId, CalendarEntries calEntry){ 106 if (StringUtils.isEmpty(principalId) || calEntry == null) { 107 return false; 108 } 109 110 boolean isPlanningCalendar = TkServiceLocator.getLeaveCalendarService().isLeavePlanningCalendar(principalId, calEntry.getBeginPeriodDateTime(), calEntry.getEndPeriodDateTime()); 111 if (isPlanningCalendar) { 112 return false; 113 } 114 115 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByPayEntry(principalId, calEntry); 116 List<Assignment> results = TkServiceLocator.getAssignmentService().filterAssignments(assignments, TkConstants.FLSA_STATUS_EXEMPT, true); 117 return CollectionUtils.isNotEmpty(results); 118 } 119 120 protected LeaveCalendarDocument initiateWorkflowDocument(String principalId, Date payBeginDate, Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException { 121 LeaveCalendarDocument leaveCalendarDocument = null; 122 WorkflowDocument workflowDocument = null; 123 124 workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title); 125 126 String status = workflowDocument.getStatus().getCode(); 127 LeaveCalendarDocumentHeader documentHeader = new LeaveCalendarDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status); 128 129 documentHeader.setDocumentId(workflowDocument.getDocumentId()); 130 documentHeader.setDocumentStatus(TkConstants.ROUTE_STATUS.INITIATED); 131 132 KRADServiceLocator.getBusinessObjectService().save(documentHeader); 133 134 leaveCalendarDocument = new LeaveCalendarDocument(documentHeader); 135 leaveCalendarDocument.setCalendarEntry(calendarEntries); 136 loadLeaveCalendarDocumentData(leaveCalendarDocument, principalId, calendarEntries); 137 TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(leaveCalendarDocument, payEndDate); 138 139 updateLeaveBlockDocumentIds(principalId, payBeginDate, payEndDate, workflowDocument.getDocumentId()); 140 141 updatePlannedLeaveBlocks(principalId, payBeginDate, payEndDate); 142 143 return leaveCalendarDocument; 144 } 145 146 private void updateLeaveBlockDocumentIds(String principalId, Date beginDate, Date endDate, String documentId) { 147 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate); 148 149 for (LeaveBlock leaveBlock : leaveBlocks) { 150 leaveBlock.setDocumentId(documentId); 151 } 152 153 TkServiceLocator.getLeaveBlockService().saveLeaveBlocks(leaveBlocks); 154 } 155 156 private void updatePlannedLeaveBlocks(String principalId, Date beginDate, Date endDate) { 157 String batchUserPrincipalId = getBatchUserPrincipalId(); 158 159 if (batchUserPrincipalId != null) { 160 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate); 161 162 for (LeaveBlock leaveBlock : leaveBlocks) { 163 if (StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.PLANNED) 164 || StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.DEFERRED)) { 165 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId); 166 } else if (StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.REQUESTED)) { 167 if (StringUtils.equals(getInitiateLeaveRequestAction(), LMConstants.INITIATE_LEAVE_REQUEST_ACTION_OPTIONS.DELETE)) { 168 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId); 169 } else if (StringUtils.equals(getInitiateLeaveRequestAction(), LMConstants.INITIATE_LEAVE_REQUEST_ACTION_OPTIONS.APPROVE)) { 170 List<LeaveRequestDocument> leaveRequestDocuments = TkServiceLocator.getLeaveRequestDocumentService().getLeaveRequestDocumentsByLeaveBlockId(leaveBlock.getLmLeaveBlockId()); 171 for (LeaveRequestDocument leaveRequestDocument : leaveRequestDocuments) { 172 TkServiceLocator.getLeaveRequestDocumentService().suBlanketApproveLeave(leaveRequestDocument.getDocumentNumber(), batchUserPrincipalId); 173 } 174 } 175 } 176 } 177 } else { 178 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME); 179 LOG.error("Could not update leave request blocks due to missing batch user " + principalName); 180 } 181 } 182 183 private String getBatchUserPrincipalId() { 184 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME); 185 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName); 186 return principal == null ? null : principal.getPrincipalId(); 187 } 188 189 private String getInitiateLeaveRequestAction() { 190 return ConfigContext.getCurrentContextConfig().getProperty(LMConstants.INITIATE_LEAVE_REQUEST_ACTION); 191 } 192 193 /** 194 * Preload the document data. It preloads: 195 * - LeaveBlocks on the document. 196 * @param ldoc 197 * @param principalId 198 * @param calEntry 199 */ 200 protected void loadLeaveCalendarDocumentData(LeaveCalendarDocument ldoc, String principalId, CalendarEntries calEntry) { 201 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForDocumentId(ldoc.getDocumentId()); 202 ldoc.setLeaveBlocks(leaveBlocks); 203 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(principalId, calEntry); 204 ldoc.setAssignments(assignments); 205 } 206 207 public LeaveCalendarDao getLeaveCalendarDao() { 208 return leaveCalendarDao; 209 } 210 211 public void setLeaveCalendarDao(LeaveCalendarDao leaveCalendarDao) { 212 this.leaveCalendarDao = leaveCalendarDao; 213 } 214 215 @Override 216 public LeaveCalendarDocument getLeaveCalendarDocument( 217 String principalId, CalendarEntries calendarEntry) { 218 LeaveCalendarDocument leaveCalendarDocument = new LeaveCalendarDocument(calendarEntry); 219 LeaveCalendarDocumentHeader lcdh = new LeaveCalendarDocumentHeader(); 220 lcdh.setBeginDate(calendarEntry.getBeginPeriodDateTime()); 221 lcdh.setEndDate(calendarEntry.getEndPeriodDateTime()); 222 leaveCalendarDocument.setDocumentHeader(lcdh); 223 // Fetching assignments 224 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(principalId, calendarEntry); 225 leaveCalendarDocument.setAssignments(assignments); 226 return leaveCalendarDocument; 227 } 228 229 @Override 230 public void routeLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) { 231 leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.ROUTE, principalId, leaveCalendarDocument); 232 } 233 234 @Override 235 public void routeLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument, String action) { 236 leaveCalendarDocumentAction(action, principalId, leaveCalendarDocument); 237 } 238 239 @Override 240 public void approveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) { 241 leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, leaveCalendarDocument); 242 } 243 244 @Override 245 public void approveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument, String action) { 246 leaveCalendarDocumentAction(action, principalId, leaveCalendarDocument); 247 } 248 249 @Override 250 public void disapproveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) { 251 leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, leaveCalendarDocument); 252 } 253 254 public boolean isReadyToApprove(LeaveCalendarDocument document) { 255 if (document == null) { 256 return false; 257 } 258 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(), 259 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER); 260 leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(), 261 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT)); 262 for(LeaveBlock lb : leaveBlocks) { 263 if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) && 264 !StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED)) 265 return false; 266 } 267 // check if there are any pending calendars are there 268 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getMinBeginDatePendingLeaveCalendar(document.getPrincipalId()); 269 if (lcdh != null){ //if there were any pending document 270 //check to see if it's before the current document. if it is, then this document is not approvable. 271 if (TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(document.getDocumentId()).getBeginDate().compareTo(lcdh.getEndDate()) >= 0){ 272 return false; 273 } 274 } 275 276 return true; 277 /* List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(), 278 document.getCalendarEntry().getBeginPeriodDate(), 279 document.getCalendarEntry().getEndPeriodDate()); 280 if (!CollectionUtils.isEmpty(balanceTransfers)) { 281 for(BalanceTransfer balanceTransfer : balanceTransfers) { 282 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE)) 283 return false; 284 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus()) 285 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) { 286 return false; 287 } 288 } 289 } 290 List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(), 291 document.getCalendarEntry().getBeginPeriodDate(), 292 document.getCalendarEntry().getEndPeriodDate()); 293 if (!CollectionUtils.isEmpty(leavePayouts)) { 294 for(LeavePayout payout : leavePayouts) { 295 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE)) 296 return false; 297 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus()) 298 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) { 299 return false; 300 } 301 } 302 } 303 return true;*/ 304 } 305 306 protected void leaveCalendarDocumentAction(String action, String principalId, LeaveCalendarDocument leaveCalendarDocument) { 307 WorkflowDocument wd = null; 308 if (leaveCalendarDocument != null) { 309 String rhid = leaveCalendarDocument.getDocumentId(); 310 wd = WorkflowDocumentFactory.loadDocument(principalId, rhid); 311 312 if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) { 313 wd.route("Routing for Approval"); 314 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) { 315 Note.Builder builder = Note.Builder.create(rhid, principalId); 316 builder.setCreateDate(new DateTime()); 317 builder.setText("Routed via Employee Approval batch job"); 318 KewApiServiceLocator.getNoteService().createNote(builder.build()); 319 320 wd.route("Batch job routing leave calendar"); 321 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.APPROVE)) { 322 if (TKUser.getCurrentTargetRoles().isSystemAdmin() && 323 !TKUser.getCurrentTargetRoles().isApproverForTimesheet(leaveCalendarDocument)) { 324 wd.superUserBlanketApprove("Superuser approving timesheet."); 325 } else { 326 wd.approve("Approving timesheet."); 327 } 328 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) { 329 Note.Builder builder = Note.Builder.create(rhid, principalId); 330 builder.setCreateDate(new DateTime()); 331 builder.setText("Approved via Supervisor Approval batch job"); 332 KewApiServiceLocator.getNoteService().createNote(builder.build()); 333 334 wd.superUserBlanketApprove("Batch job approving leave calendar"); 335 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.DISAPPROVE)) { 336 if (TKUser.getCurrentTargetRoles().isSystemAdmin() 337 && !TKUser.getCurrentTargetRoles().isApproverForTimesheet(leaveCalendarDocument)) { 338 wd.superUserDisapprove("Superuser disapproving timesheet."); 339 } else { 340 wd.disapprove("Disapproving timesheet."); 341 } 342 } 343 } 344 } 345 346 public boolean isLeavePlanningCalendar(String principalId, Date beginDate, Date endDate) { 347 Date today = new Date(); 348 349 List<Job> jobs = TkServiceLocator.getJobService().getJobs(principalId, endDate); 350 for (Job job : jobs) { 351 // Check for Leave eligibility. 352 if (job.isEligibleForLeave()) { 353 // Check for Time (FLSA nonexempt) jobs. If one exists, then the Leave Calendar is always a Leave Planning Calendar 354 if (job.getFlsaStatus().equalsIgnoreCase(TkConstants.FLSA_STATUS_NON_EXEMPT)) { 355 return true; 356 } else { 357 // If leave eligible and FLSA exempt, then report leave in the Leave Calendar. Use the date to determine Planning vs Recording Calendars. 358 if ( beginDate.after(today) ) { 359 // future period, this is a Planning Calendar. 360 return true; 361 } else { 362 // not a future period, this is a Reporting Calendar. 363 return false; 364 } 365 } 366 } else { 367 // not leave eligible 368 return false; 369 } 370 } 371 return false; 372 } 373 374 } 375