View Javadoc

1   /**
2    * Copyright 2004-2013 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.hr.time.timesheet.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.kuali.hr.job.Job;
23  import org.kuali.hr.lm.LMConstants;
24  import org.kuali.hr.lm.leaveblock.LeaveBlock;
25  import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
26  import org.kuali.hr.time.assignment.Assignment;
27  import org.kuali.hr.time.calendar.CalendarEntries;
28  import org.kuali.hr.time.principal.PrincipalHRAttributes;
29  import org.kuali.hr.time.service.base.TkServiceLocator;
30  import org.kuali.hr.time.timeblock.TimeBlock;
31  import org.kuali.hr.time.timesheet.TimesheetDocument;
32  import org.kuali.hr.time.util.TKContext;
33  import org.kuali.hr.time.util.TKUser;
34  import org.kuali.hr.time.util.TKUtils;
35  import org.kuali.hr.time.util.TkConstants;
36  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
37  import org.kuali.rice.core.api.config.property.ConfigContext;
38  import org.kuali.rice.kew.api.KewApiServiceLocator;
39  import org.kuali.rice.kew.api.WorkflowDocument;
40  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
41  import org.kuali.rice.kew.api.exception.WorkflowException;
42  import org.kuali.rice.kew.api.note.Note;
43  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
44  import org.kuali.rice.kim.api.identity.principal.Principal;
45  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
46  
47  import java.math.BigDecimal;
48  import java.sql.Timestamp;
49  import java.util.ArrayList;
50  import java.util.Date;
51  import java.util.LinkedList;
52  import java.util.List;
53  
54  public class TimesheetServiceImpl implements TimesheetService {
55  
56      @SuppressWarnings("unused")
57      private static final Logger LOG = Logger.getLogger(TimesheetServiceImpl.class);
58  
59      @Override
60      public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument) {
61          routeTimesheet(principalId, timesheetDocument, TkConstants.DOCUMENT_ACTIONS.ROUTE);
62      }
63  
64      @Override
65      public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
66          timesheetAction(action, principalId, timesheetDocument);
67      }
68  
69      @Override
70      public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
71          timesheetAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, timesheetDocument);
72      }
73  
74      @Override
75      public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
76          timesheetAction(action, principalId, timesheetDocument);
77      }
78  
79      @Override
80      public void disapproveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
81          timesheetAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, timesheetDocument);
82      }
83  
84      protected void timesheetAction(String action, String principalId, TimesheetDocument timesheetDocument) {
85          WorkflowDocument wd = null;
86          if (timesheetDocument != null) {
87              String rhid = timesheetDocument.getDocumentId();
88              wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
89  
90              if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) {
91                  wd.route("Routing for Approval");
92              } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
93              	Note.Builder builder = Note.Builder.create(rhid, principalId);
94                  builder.setCreateDate(new DateTime());
95                  builder.setText("Routed via Employee Approval batch job");
96              	KewApiServiceLocator.getNoteService().createNote(builder.build());
97              	
98              	wd.route("Batch job routing timesheet");
99              } 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 }