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