View Javadoc
1   /**
2    * Copyright 2004-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.kpme.tklm.leave.calendar.service;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.joda.time.DateTime;
22  import org.joda.time.LocalDate;
23  import org.kuali.kpme.core.assignment.Assignment;
24  import org.kuali.kpme.core.batch.BatchJobUtil;
25  import org.kuali.kpme.core.calendar.entry.CalendarEntry;
26  import org.kuali.kpme.core.document.calendar.CalendarDocument;
27  import org.kuali.kpme.core.job.Job;
28  import org.kuali.kpme.core.role.KPMERole;
29  import org.kuali.kpme.core.service.HrServiceLocator;
30  import org.kuali.kpme.core.util.HrConstants;
31  import org.kuali.kpme.core.util.TKUtils;
32  import org.kuali.kpme.tklm.common.LMConstants;
33  import org.kuali.kpme.tklm.leave.block.LeaveBlock;
34  import org.kuali.kpme.tklm.leave.calendar.LeaveCalendarDocument;
35  import org.kuali.kpme.tklm.leave.calendar.dao.LeaveCalendarDao;
36  import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
37  import org.kuali.kpme.tklm.leave.workflow.LeaveCalendarDocumentHeader;
38  import org.kuali.kpme.tklm.leave.workflow.LeaveRequestDocument;
39  import org.kuali.kpme.tklm.time.service.TkServiceLocator;
40  import org.kuali.rice.core.api.config.property.ConfigContext;
41  import org.kuali.rice.kew.api.KewApiServiceLocator;
42  import org.kuali.rice.kew.api.WorkflowDocument;
43  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
44  import org.kuali.rice.kew.api.action.ActionRequest;
45  import org.kuali.rice.kew.api.exception.WorkflowException;
46  import org.kuali.rice.kew.api.note.Note;
47  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
48  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
49  import org.kuali.rice.krad.service.KRADServiceLocator;
50  import org.kuali.rice.krad.util.GlobalVariables;
51  
52  import java.util.List;
53  
54  public class LeaveCalendarServiceImpl implements LeaveCalendarService {
55  	
56  	private static final Logger LOG = Logger.getLogger(LeaveCalendarServiceImpl.class);
57  
58      private LeaveCalendarDao leaveCalendarDao;
59  
60      @Override
61      public LeaveCalendarDocument getLeaveCalendarDocument(String documentId) {
62          LeaveCalendarDocument lcd = null;
63          LeaveCalendarDocumentHeader lcdh = LmServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
64  
65          if (lcdh != null) {
66              lcd = new LeaveCalendarDocument(lcdh);
67              CalendarEntry pce = HrServiceLocator.getCalendarEntryService().getCalendarDatesByPayEndDate(lcdh.getPrincipalId(), lcdh.getEndDateTime(), HrConstants.LEAVE_CALENDAR_TYPE);
68              lcd.setCalendarEntry(pce);
69          } else {
70          	LOG.error("Could not find LeaveCalendarDocumentHeader for DocumentID: " + documentId);
71   	 	 	return null;
72          }
73  
74          List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForDocumentId(documentId);
75          lcd.setLeaveBlocks(leaveBlocks);
76  
77          // Fetching assignments
78          List<Assignment> assignments = HrServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(lcdh.getPrincipalId(), lcd.getCalendarEntry());
79          lcd.setAssignments(assignments);
80          
81          return lcd;
82      }
83  
84      @Override
85      public LeaveCalendarDocument openLeaveCalendarDocument(String principalId, CalendarEntry calEntry) throws WorkflowException {
86          LeaveCalendarDocument doc;
87  
88          DateTime begin = calEntry.getBeginPeriodFullDateTime();
89          DateTime end = calEntry.getEndPeriodFullDateTime();
90  
91          LeaveCalendarDocumentHeader header = LmServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, begin, end);
92          if (header == null) {
93              EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
94              String principalName = person != null && person.getDefaultName() != null ? person.getDefaultName().getCompositeName() : StringUtils.EMPTY;
95              String beginDateString = TKUtils.formatDate(begin.toLocalDate());
96              String endDateString = TKUtils.formatDate(end.toLocalDate());
97              String leaveCalendarDocumentTitle = LeaveCalendarDocument.LEAVE_CALENDAR_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + beginDateString + "-" + endDateString;
98              
99              doc = initiateWorkflowDocument(principalId, begin, end, calEntry, LeaveCalendarDocument.LEAVE_CALENDAR_DOCUMENT_TYPE, leaveCalendarDocumentTitle);
100         } else {
101             doc = getLeaveCalendarDocument(header.getDocumentId());
102         }
103         if (doc != null) {
104         	doc.setCalendarEntry(calEntry);
105         }
106         // TODO: need to set the summary
107         return doc;
108     }
109     
110     //Should only create leave calendar document if active jobs were found with flsa elig = no and ben elg = yes
111     public boolean shouldCreateLeaveDocument(String principalId, CalendarEntry calEntry){
112         if (StringUtils.isEmpty(principalId) || calEntry == null) {
113             return false;
114         }
115         
116         boolean isPlanningCalendar = LmServiceLocator.getLeaveCalendarService().isLeavePlanningCalendar(principalId, calEntry.getBeginPeriodFullDateTime().toLocalDate(), calEntry.getEndPeriodFullDateTime().toLocalDate());
117     	if (isPlanningCalendar) {
118     		return false;
119     	}
120         
121         List<Assignment> assignments = HrServiceLocator.getAssignmentService().getAssignmentsByPayEntry(principalId, calEntry);
122     	List<Assignment> results = HrServiceLocator.getAssignmentService().filterAssignments(assignments, HrConstants.FLSA_STATUS_EXEMPT, true);
123     	return CollectionUtils.isNotEmpty(results);
124     }
125     
126     protected LeaveCalendarDocument initiateWorkflowDocument(String principalId, DateTime payBeginDate, DateTime payEndDate, CalendarEntry calendarEntry, String documentType, String title) throws WorkflowException {
127         LeaveCalendarDocument leaveCalendarDocument = null;
128         WorkflowDocument workflowDocument = null;
129 
130         workflowDocument =  WorkflowDocumentFactory.createDocument(principalId, documentType, title);
131 
132         String status = workflowDocument.getStatus().getCode();
133         LeaveCalendarDocumentHeader documentHeader = new LeaveCalendarDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate.toDate(), payEndDate.toDate(), status);
134 
135         documentHeader.setDocumentId(workflowDocument.getDocumentId());
136         documentHeader.setDocumentStatus(HrConstants.ROUTE_STATUS.INITIATED);
137 
138         KRADServiceLocator.getBusinessObjectService().save(documentHeader);
139         
140         leaveCalendarDocument = new LeaveCalendarDocument(documentHeader);
141         leaveCalendarDocument.setCalendarEntry(calendarEntry);
142         loadLeaveCalendarDocumentData(leaveCalendarDocument, principalId, calendarEntry);
143         TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(leaveCalendarDocument, payEndDate.toLocalDate());
144         
145         updateLeaveBlockDocumentIds(principalId, payBeginDate.toLocalDate(), payEndDate.toLocalDate(), workflowDocument.getDocumentId());
146         
147         updatePlannedLeaveBlocks(principalId, payBeginDate.toLocalDate(), payEndDate.toLocalDate());
148 
149         return leaveCalendarDocument;
150     }
151     
152     private void updateLeaveBlockDocumentIds(String principalId, LocalDate beginDate, LocalDate endDate, String documentId) {
153         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
154         
155         for (LeaveBlock leaveBlock : leaveBlocks) {
156         	leaveBlock.setDocumentId(documentId);
157         }
158         
159         LmServiceLocator.getLeaveBlockService().saveLeaveBlocks(leaveBlocks);
160     }
161     
162     private void updatePlannedLeaveBlocks(String principalId, LocalDate beginDate, LocalDate endDate) {
163         String batchUserPrincipalId = BatchJobUtil.getBatchUserPrincipalId();
164         
165         if (batchUserPrincipalId != null) {
166 	    	List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
167 	
168 	    	for (LeaveBlock leaveBlock : leaveBlocks) {
169 	    		if (StringUtils.equals(leaveBlock.getRequestStatus(), HrConstants.REQUEST_STATUS.PLANNED) 
170 	    				|| StringUtils.equals(leaveBlock.getRequestStatus(), HrConstants.REQUEST_STATUS.DEFERRED)) {
171 	    			LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
172 	    		} else if (StringUtils.equals(leaveBlock.getRequestStatus(), HrConstants.REQUEST_STATUS.REQUESTED)) {
173 	    	        if (StringUtils.equals(getInitiateLeaveRequestAction(), LMConstants.INITIATE_LEAVE_REQUEST_ACTION_OPTIONS.DELETE)) {
174                         LmServiceLocator.getLeaveRequestDocumentService().suCancelLeave(
175                                 leaveBlock.getLeaveRequestDocumentId(), batchUserPrincipalId);
176 	    	        	LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
177 	    	        } else if (StringUtils.equals(getInitiateLeaveRequestAction(), LMConstants.INITIATE_LEAVE_REQUEST_ACTION_OPTIONS.APPROVE)) {
178 	    	        	List<LeaveRequestDocument> leaveRequestDocuments = LmServiceLocator.getLeaveRequestDocumentService().getLeaveRequestDocumentsByLeaveBlockId(leaveBlock.getLmLeaveBlockId());
179 	    	        	for (LeaveRequestDocument leaveRequestDocument : leaveRequestDocuments) {
180 	    	        		LmServiceLocator.getLeaveRequestDocumentService().suBlanketApproveLeave(leaveRequestDocument.getDocumentNumber(), batchUserPrincipalId);
181 	    	        	}
182 	    	        }
183 	    		}
184 	    	}
185         } else {
186         	String principalName = BatchJobUtil.getBatchUserPrincipalName();
187         	LOG.error("Could not update leave request blocks due to missing batch user " + principalName);
188         }
189     }
190     
191     private String getInitiateLeaveRequestAction() {
192     	return ConfigContext.getCurrentContextConfig().getProperty(LMConstants.INITIATE_LEAVE_REQUEST_ACTION);
193     }
194 
195     /**
196      * Preload the document data. It preloads:
197      * - LeaveBlocks on the document.
198      * @param ldoc
199      * @param principalId
200      * @param calEntry
201      */
202     protected void loadLeaveCalendarDocumentData(LeaveCalendarDocument ldoc, String principalId, CalendarEntry calEntry) {
203         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForDocumentId(ldoc.getDocumentId());
204         ldoc.setLeaveBlocks(leaveBlocks);
205         List<Assignment> assignments = HrServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(principalId, calEntry);
206         ldoc.setAssignments(assignments);
207     }
208 
209     public LeaveCalendarDao getLeaveCalendarDao() {
210         return leaveCalendarDao;
211     }
212 
213     public void setLeaveCalendarDao(LeaveCalendarDao leaveCalendarDao) {
214         this.leaveCalendarDao = leaveCalendarDao;
215     }
216 
217 	@Override
218 	public LeaveCalendarDocument getLeaveCalendarDocument(
219 			String principalId, CalendarEntry calendarEntry) {
220 		LeaveCalendarDocument leaveCalendarDocument = new LeaveCalendarDocument(calendarEntry);
221 		LeaveCalendarDocumentHeader lcdh = new LeaveCalendarDocumentHeader();
222 		lcdh.setBeginDate(calendarEntry.getBeginPeriodDateTime());
223 		lcdh.setEndDate(calendarEntry.getEndPeriodDateTime());
224 		leaveCalendarDocument.setDocumentHeader(lcdh);
225 		// Fetching assignments
226         List<Assignment> assignments = HrServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(principalId, calendarEntry);
227         leaveCalendarDocument.setAssignments(assignments);
228 		return leaveCalendarDocument;
229 	}
230 
231     @Override
232     public void routeLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
233         leaveCalendarDocumentAction(HrConstants.DOCUMENT_ACTIONS.ROUTE, principalId, leaveCalendarDocument);
234     }
235     
236     @Override
237     public void routeLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument, String action) {
238         leaveCalendarDocumentAction(action, principalId, leaveCalendarDocument);
239     }
240 
241     @Override
242     public void approveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
243         leaveCalendarDocumentAction(HrConstants.DOCUMENT_ACTIONS.APPROVE, principalId, leaveCalendarDocument);
244     }
245     
246     @Override
247     public void approveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument, String action) {
248         leaveCalendarDocumentAction(action, principalId, leaveCalendarDocument);
249     }
250 
251     @Override
252     public void disapproveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
253         leaveCalendarDocumentAction(HrConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, leaveCalendarDocument);
254     }
255 
256     public boolean isReadyToApprove(CalendarDocument document) {
257         if (document == null) {
258             return false;
259         }
260         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
261         		document.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate(), document.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
262         leaveBlocks.addAll(LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
263         		document.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate(), document.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
264         for(LeaveBlock lb : leaveBlocks) {
265         	if(!StringUtils.equals(lb.getRequestStatus(),HrConstants.REQUEST_STATUS.APPROVED) &&
266         			!StringUtils.equals(lb.getRequestStatus(), HrConstants.REQUEST_STATUS.DISAPPROVED))
267         		return false;
268         }
269         // check if there are any pending calendars are there
270         LeaveCalendarDocumentHeader lcdh = LmServiceLocator.getLeaveCalendarDocumentHeaderService().getMinBeginDatePendingLeaveCalendar(document.getPrincipalId());
271         if (lcdh != null){             //if there were any pending document
272             //check to see if it's before the current document. if it is, then this document is not approvable.
273             if (LmServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(document.getDocumentId()).getBeginDate().compareTo(lcdh.getEndDate()) >= 0){
274                 return false;
275             }
276         }
277 
278         return true;
279     }
280 
281     protected void leaveCalendarDocumentAction(String action, String principalId, LeaveCalendarDocument leaveCalendarDocument) {
282         WorkflowDocument wd = null;
283         if (leaveCalendarDocument != null) {
284             String rhid = leaveCalendarDocument.getDocumentId();
285             wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
286             List<ActionRequest> actionRequests = KewApiServiceLocator.getWorkflowDocumentService().getPendingActionRequests(rhid);
287             
288             if (StringUtils.equals(action, HrConstants.DOCUMENT_ACTIONS.ROUTE)) {
289                 wd.route("Routing for Approval");
290             } else if (StringUtils.equals(action, HrConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
291                 Note.Builder builder = Note.Builder.create(rhid, principalId);
292                 builder.setCreateDate(new DateTime());
293                 builder.setText("Routed via Employee Approval batch job");
294             	KewApiServiceLocator.getNoteService().createNote(builder.build());
295             	
296             	wd.route("Batch job routing leave calendar");
297             } else if (StringUtils.equals(action, HrConstants.DOCUMENT_ACTIONS.APPROVE)) {
298                 if (HrServiceLocator.getHRPermissionService().canSuperUserAdministerCalendarDocument(GlobalVariables.getUserSession().getPrincipalId(), leaveCalendarDocument) 
299                 		&& !HrServiceLocator.getHRPermissionService().canApproveCalendarDocument(GlobalVariables.getUserSession().getPrincipalId(), leaveCalendarDocument)) {
300                     wd.superUserBlanketApprove("Superuser approving timesheet.");
301                 } else {
302                     wd.approve("Approving timesheet.");
303                 }
304             } else if (StringUtils.equals(action, HrConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) {
305             	 boolean approverFlag = false;
306                  for (ActionRequest ar : actionRequests) {
307                  	if(StringUtils.equals(ar.getQualifiedRoleNameLabel(), KPMERole.APPROVER.getRoleName())) {
308                  		approverFlag = true;
309                  		break;
310                  	}
311                  }
312                  // if there's still action requested to be taken by approver, then approve the document with this SupervisorApproval batch job
313                  // otherwise, don't take any actions
314                  if(approverFlag) {
315  	            	// supervisor approval job should take approve action but not finalize the document if there's payroll processor set up for this doc
316                 	Note.Builder builder = Note.Builder.create(rhid, principalId);
317                 	builder.setCreateDate(new DateTime());
318                 	builder.setText("Approved via Supervisor Approval batch job");
319                 	KewApiServiceLocator.getNoteService().createNote(builder.build());
320  	           	 	wd.approve("Supervisor Batch job approving leave calendar on behalf of approvers.");
321                  }
322             } else if (StringUtils.equals(action, HrConstants.BATCH_JOB_ACTIONS.PAYROLL_JOB_APPROVE)) {
323             	boolean payrollProcessorFlag = false;
324                 for (ActionRequest ar : actionRequests) {
325                 	if(StringUtils.equals(ar.getQualifiedRoleNameLabel(), KPMERole.PAYROLL_PROCESSOR.getRoleName())) {
326                 		payrollProcessorFlag = true;
327                  		break;
328                  	}
329                  }
330                  if(payrollProcessorFlag) {
331 	            	Note.Builder builder = Note.Builder.create(rhid, principalId);
332 	           	 	builder.setCreateDate(new DateTime());
333 	           	 	builder.setText("Approved via Payroll Processor Approval batch job");
334 	           	 	KewApiServiceLocator.getNoteService().createNote(builder.build());
335 	            	wd.approve("Payroll Processor Batch job approving leave calendar on behalf of approvers.");
336                  }
337             } else if (StringUtils.equals(action, HrConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
338                 if (HrServiceLocator.getHRPermissionService().canSuperUserAdministerCalendarDocument(GlobalVariables.getUserSession().getPrincipalId(), leaveCalendarDocument) 
339                 		&& !HrServiceLocator.getHRPermissionService().canApproveCalendarDocument(GlobalVariables.getUserSession().getPrincipalId(), leaveCalendarDocument)) {
340                     wd.superUserDisapprove("Superuser disapproving leave calendar.");
341                 } else {
342                     wd.disapprove("Disapproving timesheet.");
343                 }
344             }
345         }
346     }
347 
348     public boolean isLeavePlanningCalendar(String principalId, LocalDate beginDate, LocalDate endDate) {
349         LocalDate today = LocalDate.now();
350 
351         List<Job> jobs = HrServiceLocator.getJobService().getJobs(principalId, endDate);
352         for (Job job : jobs) {
353             //  Check for Leave eligibility.
354             if (job.isEligibleForLeave()) {
355                 //  Check for Time (FLSA nonexempt) jobs. If one exists, then the Leave Calendar is always a Leave Planning Calendar
356                 if (job.getFlsaStatus().equalsIgnoreCase(HrConstants.FLSA_STATUS_NON_EXEMPT)) {
357                     return true;
358                 } else {
359                     //  If leave eligible and FLSA exempt, then report leave in the Leave Calendar. Use the date to determine Planning vs Recording Calendars.
360                     if ( beginDate.isAfter(today) ) {
361                         //  future period, this is a Planning Calendar.
362                         return true;
363                     } else {
364                         //  not a future period, this is a Reporting Calendar.
365                         return false;
366                     }
367                 }
368             } else {
369             //  not leave eligible
370                 return false;
371             }
372         }
373         return false;
374     }
375 
376 }
377