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