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 java.math.BigDecimal;
19  import java.sql.Timestamp;
20  import java.util.ArrayList;
21  import java.util.Date;
22  import java.util.LinkedList;
23  import java.util.List;
24  
25  import org.apache.commons.collections.CollectionUtils;
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.log4j.Logger;
28  import org.joda.time.DateTime;
29  import org.kuali.hr.job.Job;
30  import org.kuali.hr.lm.LMConstants;
31  import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
32  import org.kuali.hr.lm.leaveblock.LeaveBlock;
33  import org.kuali.hr.lm.leavepayout.LeavePayout;
34  import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
35  import org.kuali.hr.time.assignment.Assignment;
36  import org.kuali.hr.time.calendar.CalendarEntries;
37  import org.kuali.hr.time.principal.PrincipalHRAttributes;
38  import org.kuali.hr.time.service.base.TkServiceLocator;
39  import org.kuali.hr.time.timeblock.TimeBlock;
40  import org.kuali.hr.time.timesheet.TimesheetDocument;
41  import org.kuali.hr.time.util.TKContext;
42  import org.kuali.hr.time.util.TKUser;
43  import org.kuali.hr.time.util.TKUtils;
44  import org.kuali.hr.time.util.TkConstants;
45  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
46  import org.kuali.rice.core.api.config.property.ConfigContext;
47  import org.kuali.rice.kew.api.KewApiServiceLocator;
48  import org.kuali.rice.kew.api.WorkflowDocument;
49  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
50  import org.kuali.rice.kew.api.exception.WorkflowException;
51  import org.kuali.rice.kew.api.note.Note;
52  import org.kuali.rice.kim.api.identity.Person;
53  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
54  
55  public class TimesheetServiceImpl implements TimesheetService {
56  
57      @SuppressWarnings("unused")
58      private static final Logger LOG = Logger.getLogger(TimesheetServiceImpl.class);
59  
60      @Override
61      public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument) {
62          routeTimesheet(principalId, timesheetDocument, TkConstants.DOCUMENT_ACTIONS.ROUTE);
63      }
64  
65      @Override
66      public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
67          timesheetAction(action, principalId, timesheetDocument);
68      }
69  
70      @Override
71      public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
72          timesheetAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, timesheetDocument);
73      }
74  
75      @Override
76      public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
77          timesheetAction(action, principalId, timesheetDocument);
78      }
79  
80      @Override
81      public void disapproveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
82          timesheetAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, timesheetDocument);
83      }
84  
85      protected void timesheetAction(String action, String principalId, TimesheetDocument timesheetDocument) {
86          WorkflowDocument wd = null;
87          if (timesheetDocument != null) {
88              String rhid = timesheetDocument.getDocumentId();
89              wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
90  
91              if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) {
92                  wd.route("Routing for Approval");
93              } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
94              	Note.Builder builder = Note.Builder.create(rhid, principalId);
95                  builder.setCreateDate(new DateTime());
96                  builder.setText("Routed via Employee Approval batch job");
97              	KewApiServiceLocator.getNoteService().createNote(builder.build());
98              	
99              	wd.route("Batch job routing timesheet");
100             } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.APPROVE)) {
101                 if (TKContext.getUser().getCurrentTargetRoles().isSystemAdmin() &&
102                         !TKContext.getUser().getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) {
103                     wd.superUserBlanketApprove("Superuser approving timesheet.");
104                 } else {
105                     wd.approve("Approving timesheet.");
106                 }
107             } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) {
108             	Note.Builder builder = Note.Builder.create(rhid, principalId);
109            	 	builder.setCreateDate(new DateTime());
110            	 	builder.setText("Approved via Supervisor Approval batch job");
111            	 	KewApiServiceLocator.getNoteService().createNote(builder.build());
112             	
113             	wd.superUserBlanketApprove("Batch job approving timesheet.");
114             } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
115                 if (TKContext.getUser().getCurrentTargetRoles().isSystemAdmin()
116                         && !TKContext.getUser().getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) {
117                     wd.superUserDisapprove("Superuser disapproving timesheet.");
118                 } else {
119                     wd.disapprove("Disapproving timesheet.");
120                 }
121             }
122         }
123     }
124 
125     @Override
126     public TimesheetDocument openTimesheetDocument(String principalId, CalendarEntries calendarDates) throws WorkflowException {
127         TimesheetDocument timesheetDocument = null;
128 
129         Date begin = calendarDates.getBeginPeriodDateTime();
130         Date end = calendarDates.getEndPeriodDateTime();
131 
132         TimesheetDocumentHeader header = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, begin, end);
133 
134         if (header == null) {
135             List<Assignment> activeAssignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, calendarDates);
136             //TkServiceLocator.getAssignmentService().getAssignments(principalId, TKUtils.getTimelessDate(payCalendarDates.getEndPeriodDate()));
137             if (activeAssignments.size() == 0) {
138                 LOG.warn("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
139                 return null;
140                 //throw new RuntimeException("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
141             }
142             
143             Person person = KimApiServiceLocator.getPersonService().getPerson(principalId);
144             String principalName = person != null ? person.getName() : StringUtils.EMPTY;
145             String endDateString = TKUtils.formatDate(new java.sql.Date(end.getTime()));
146             String timesheetDocumentTitle = TimesheetDocument.TIMESHEET_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + endDateString;
147             
148             timesheetDocument = this.initiateWorkflowDocument(principalId, begin, end, calendarDates, TimesheetDocument.TIMESHEET_DOCUMENT_TYPE, timesheetDocumentTitle);
149             //timesheetDocument.setPayCalendarEntry(calendarDates);
150             //this.loadTimesheetDocumentData(timesheetDocument, principalId, calendarDates);
151             //TODO switch this to scheduled time offs
152             //this.loadHolidaysOnTimesheet(timesheetDocument, principalId, begin, end);
153         } else {
154             timesheetDocument = this.getTimesheetDocument(header.getDocumentId());
155             timesheetDocument.setCalendarEntry(calendarDates);
156         }
157 
158         timesheetDocument.setTimeSummary(TkServiceLocator.getTimeSummaryService().getTimeSummary(timesheetDocument));
159         return timesheetDocument;
160     }
161 
162     public void loadHolidaysOnTimesheet(TimesheetDocument timesheetDocument, String principalId, Date beginDate, Date endDate) {
163         PrincipalHRAttributes principalCalendar = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, new java.sql.Date(beginDate.getTime()));
164         if (principalCalendar != null && StringUtils.isNotEmpty(principalCalendar.getLeavePlan())) {
165         	List<SystemScheduledTimeOff> sstoList = TkServiceLocator.getSysSchTimeOffService()
166         		.getSystemScheduledTimeOffForPayPeriod(principalCalendar.getLeavePlan(), beginDate, endDate);
167         	Assignment sstoAssign = TkServiceLocator.getAssignmentService().getAssignmentToApplyScheduledTimeOff(timesheetDocument, TKUtils.getTimelessDate(endDate));
168         	if (sstoAssign != null) {
169         		for(SystemScheduledTimeOff ssto : sstoList) {
170                   BigDecimal sstoCalcHours = TkServiceLocator.getSysSchTimeOffService().calculateSysSchTimeOffHours(sstoAssign.getJob(), ssto.getAmountofTime());
171                   TimeBlock timeBlock = TkServiceLocator.getTimeBlockService().createTimeBlock(timesheetDocument, new Timestamp(ssto.getScheduledTimeOffDate().getTime()),
172                           new Timestamp(ssto.getScheduledTimeOffDate().getTime()), sstoAssign, TkConstants.HOLIDAY_EARN_CODE, sstoCalcHours, BigDecimal.ZERO, false, false, TKContext.getPrincipalId());
173                   timesheetDocument.getTimeBlocks().add(timeBlock);
174               }
175 	            //If system scheduled time off are loaded will need to save them to the database
176 		        if (CollectionUtils.isNotEmpty(sstoList)) {
177 		           TkServiceLocator.getTimeBlockService().saveTimeBlocks(new LinkedList<TimeBlock>(), timesheetDocument.getTimeBlocks(), TKContext.getPrincipalId());
178 		        }
179         	}
180         }
181     }
182 
183     protected TimesheetDocument initiateWorkflowDocument(String principalId, Date payBeginDate,  Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException {
184         TimesheetDocument timesheetDocument = null;
185         WorkflowDocument workflowDocument = null;
186 
187         workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title);
188 
189         String status = workflowDocument.getStatus().getCode();
190         TimesheetDocumentHeader documentHeader = new TimesheetDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status);
191 
192         documentHeader.setDocumentId(workflowDocument.getDocumentId().toString());
193         documentHeader.setDocumentStatus("I");
194 
195         TkServiceLocator.getTimesheetDocumentHeaderService().saveOrUpdate(documentHeader);
196         timesheetDocument = new TimesheetDocument(documentHeader);
197         timesheetDocument.setCalendarEntry(calendarEntries);
198         loadTimesheetDocumentData(timesheetDocument, principalId, calendarEntries);
199         TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(timesheetDocument, payEndDate);
200 
201         if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
202         	deleteNonApprovedLeaveBlocks(principalId, calendarEntries.getBeginPeriodDate(), calendarEntries.getEndPeriodDate());
203         }
204         
205         return timesheetDocument;
206     }
207     
208     private void deleteNonApprovedLeaveBlocks(String principalId, Date beginDate, Date endDate) {
209     	String batchUserPrincipalId = getBatchUserPrincipalId();
210         
211         if (batchUserPrincipalId != null) {
212 	    	List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
213 	
214 	    	for (LeaveBlock leaveBlock : leaveBlocks) {
215 	    		if (!StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.APPROVED)) {
216 	    			TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
217 	    		}
218 	    	}
219         } else {
220         	String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
221         	LOG.error("Could not delete leave request blocks due to missing batch user " + principalName);
222         }
223     }
224     
225     private String getBatchUserPrincipalId() {
226     	String principalId = null;
227     	
228     	String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
229         Person person = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName);
230         if (person != null) {
231         	principalId = person.getPrincipalId();
232         }
233         
234         return principalId;
235     }
236 
237     public List<TimeBlock> getPrevDocumentTimeBlocks(String principalId, Date payBeginDate) {
238         TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, payBeginDate);
239         if (prevTdh == null) {
240             return new ArrayList<TimeBlock>();
241         }
242         return TkServiceLocator.getTimeBlockService().getTimeBlocks(prevTdh.getDocumentId());
243     }
244 
245     @Override
246     public TimesheetDocument getTimesheetDocument(String documentId) {
247         TimesheetDocument timesheetDocument = null;
248         TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
249 
250         if (tdh != null) {
251             timesheetDocument = new TimesheetDocument(tdh);
252             CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
253             loadTimesheetDocumentData(timesheetDocument, tdh.getPrincipalId(), pce);
254 
255             timesheetDocument.setCalendarEntry(pce);
256         } else {
257             throw new RuntimeException("Could not find TimesheetDocumentHeader for DocumentID: " + documentId);
258         }
259         return timesheetDocument;
260     }
261 
262     protected void loadTimesheetDocumentData(TimesheetDocument tdoc, String principalId, CalendarEntries payCalEntry) {
263         List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, payCalEntry);
264         List<Job> jobs = TkServiceLocator.getJobService().getJobs(principalId, TKUtils.getTimelessDate(payCalEntry.getEndPeriodDate()));
265         List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(tdoc.getDocumentHeader().getDocumentId());
266 
267         tdoc.setAssignments(assignments);
268         tdoc.setJobs(jobs);
269         tdoc.setTimeBlocks(timeBlocks);
270     }
271 
272     public boolean isSynchronousUser() {
273         List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(TKUser.getCurrentTargetPerson().getPrincipalId(), TKUtils.getCurrentDate());
274         boolean isSynchronousUser = true;
275         for (Assignment assignment : assignments) {
276             isSynchronousUser &= assignment.isSynchronous();
277         }
278         return isSynchronousUser;
279     }
280 
281     //this is an admin function used for testing
282     public void deleteTimesheet(String documentId) {
283         TkServiceLocator.getTimeBlockService().deleteTimeBlocksAssociatedWithDocumentId(documentId);
284         TkServiceLocator.getTimesheetDocumentHeaderService().deleteTimesheetHeader(documentId);
285     }
286 
287     public TimeBlock resetWorkedHours(TimeBlock timeBlock) {
288         if (timeBlock.getBeginTime() != null && timeBlock.getEndTime() != null && StringUtils.equals(timeBlock.getEarnCodeType(), TkConstants.EARN_CODE_TIME)) {
289             BigDecimal hours = TKUtils.getHoursBetween(timeBlock.getBeginTime().getTime(), timeBlock.getEndTime().getTime());
290             timeBlock.setHours(hours);
291         }
292         return timeBlock;
293     }
294 
295     @Override
296     public void resetTimeBlock(List<TimeBlock> timeBlocks) {
297         for (TimeBlock tb : timeBlocks) {
298             resetWorkedHours(tb);
299         }
300         TkServiceLocator.getTimeBlockService().resetTimeHourDetail(timeBlocks);
301     }
302 
303 	@Override
304 	public boolean isReadyToApprove(TimesheetDocument document) {
305         if (document == null) {
306             return false;
307         }
308         List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
309         		document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
310         leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
311         		document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
312         for(LeaveBlock lb : leaveBlocks) {
313         	if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) &&
314         			!StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED))
315         		return false;
316         }
317         return true;
318 /*        List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(),
319                 document.getCalendarEntry().getBeginPeriodDate(),
320                 document.getCalendarEntry().getEndPeriodDate());
321         if (!CollectionUtils.isEmpty(balanceTransfers))   {
322 	        for(BalanceTransfer balanceTransfer : balanceTransfers) {
323 	        	if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
324 	        		return false;
325 	            if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus())
326 	                    && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) {
327 	                return false;
328 	            }
329 	        }
330         }
331         List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(),
332         		document.getCalendarEntry().getBeginPeriodDate(),
333         		document.getCalendarEntry().getEndPeriodDate());
334         if (!CollectionUtils.isEmpty(leavePayouts)) {
335         	for(LeavePayout payout : leavePayouts) {
336 	        	if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
337 	        		return false;
338 	            if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus())
339 	                    && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) {
340 	                return false;
341 	            }
342         	}
343         }
344         return true;*/
345 	}
346 
347 }