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.earncode.EarnCode;
29  import org.kuali.hr.time.principal.PrincipalHRAttributes;
30  import org.kuali.hr.time.service.base.TkServiceLocator;
31  import org.kuali.hr.time.timeblock.TimeBlock;
32  import org.kuali.hr.time.timesheet.TimesheetDocument;
33  import org.kuali.hr.time.util.TKContext;
34  import org.kuali.hr.time.util.TKUser;
35  import org.kuali.hr.time.util.TKUtils;
36  import org.kuali.hr.time.util.TkConstants;
37  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
38  import org.kuali.rice.core.api.config.property.ConfigContext;
39  import org.kuali.rice.kew.api.KewApiServiceLocator;
40  import org.kuali.rice.kew.api.WorkflowDocument;
41  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
42  import org.kuali.rice.kew.api.exception.WorkflowException;
43  import org.kuali.rice.kew.api.note.Note;
44  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
45  import org.kuali.rice.kim.api.identity.principal.Principal;
46  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
47  
48  import java.math.BigDecimal;
49  import java.sql.Timestamp;
50  import java.util.ArrayList;
51  import java.util.Date;
52  import java.util.LinkedList;
53  import java.util.List;
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 (TKUser.getCurrentTargetRoles().isSystemAdmin() &&
102                         !TKUser.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 (TKUser.getCurrentTargetRoles().isSystemAdmin()
116                         && !TKUser.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             EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
144             String principalName = person != null && person.getDefaultName() != null ? person.getDefaultName().getCompositeName() : 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             if (timesheetDocument != null) {
156                 timesheetDocument.setCalendarEntry(calendarDates);
157             }
158         }
159 
160         if (timesheetDocument != null) {
161             timesheetDocument.setTimeSummary(TkServiceLocator.getTimeSummaryService().getTimeSummary(timesheetDocument));
162         }
163         return timesheetDocument;
164     }
165 
166     public void loadHolidaysOnTimesheet(TimesheetDocument timesheetDocument, String principalId, Date beginDate, Date endDate) {
167         PrincipalHRAttributes principalCalendar = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, new java.sql.Date(beginDate.getTime()));
168         if (principalCalendar != null && StringUtils.isNotEmpty(principalCalendar.getLeavePlan())) {
169         	List<SystemScheduledTimeOff> sstoList = TkServiceLocator.getSysSchTimeOffService()
170         		.getSystemScheduledTimeOffForPayPeriod(principalCalendar.getLeavePlan(), beginDate, endDate);
171         	Assignment sstoAssign = TkServiceLocator.getAssignmentService().getAssignmentToApplyScheduledTimeOff(timesheetDocument, TKUtils.getTimelessDate(endDate));
172         	if (sstoAssign != null) {
173         		for(SystemScheduledTimeOff ssto : sstoList) {
174                   BigDecimal sstoCalcHours = TkServiceLocator.getSysSchTimeOffService().calculateSysSchTimeOffHours(sstoAssign.getJob(), ssto.getAmountofTime());
175                   TimeBlock timeBlock = TkServiceLocator.getTimeBlockService().createTimeBlock(timesheetDocument, new Timestamp(ssto.getScheduledTimeOffDate().getTime()),
176                           new Timestamp(ssto.getScheduledTimeOffDate().getTime()), sstoAssign, TkConstants.HOLIDAY_EARN_CODE, sstoCalcHours, BigDecimal.ZERO, false, false, TKContext.getPrincipalId());
177                   timesheetDocument.getTimeBlocks().add(timeBlock);
178               }
179 	            //If system scheduled time off are loaded will need to save them to the database
180 		        if (CollectionUtils.isNotEmpty(sstoList)) {
181 		           TkServiceLocator.getTimeBlockService().saveTimeBlocks(new LinkedList<TimeBlock>(), timesheetDocument.getTimeBlocks(), TKContext.getPrincipalId());
182 		        }
183         	}
184         }
185     }
186 
187     protected TimesheetDocument initiateWorkflowDocument(String principalId, Date payBeginDate,  Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException {
188         TimesheetDocument timesheetDocument = null;
189         WorkflowDocument workflowDocument = null;
190 
191         workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title);
192 
193         String status = workflowDocument.getStatus().getCode();
194         TimesheetDocumentHeader documentHeader = new TimesheetDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status);
195 
196         documentHeader.setDocumentId(workflowDocument.getDocumentId().toString());
197         documentHeader.setDocumentStatus("I");
198 
199         TkServiceLocator.getTimesheetDocumentHeaderService().saveOrUpdate(documentHeader);
200         timesheetDocument = new TimesheetDocument(documentHeader);
201         timesheetDocument.setCalendarEntry(calendarEntries);
202         loadTimesheetDocumentData(timesheetDocument, principalId, calendarEntries);
203         TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(timesheetDocument, payEndDate);
204 
205         if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
206         	deleteNonApprovedLeaveBlocks(principalId, calendarEntries.getBeginPeriodDate(), calendarEntries.getEndPeriodDate());
207         }
208         
209         return timesheetDocument;
210     }
211     
212     private void deleteNonApprovedLeaveBlocks(String principalId, Date beginDate, Date endDate) {
213     	String batchUserPrincipalId = getBatchUserPrincipalId();
214         
215         if (batchUserPrincipalId != null) {
216 	    	List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
217 	
218 	    	for (LeaveBlock leaveBlock : leaveBlocks) {
219 	    		if (!StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.APPROVED)) {
220 	    			TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
221 	    		}
222 	    	}
223         } else {
224         	String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
225         	LOG.error("Could not delete leave request blocks due to missing batch user " + principalName);
226         }
227     }
228     
229     private String getBatchUserPrincipalId() {
230     	String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
231         Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName);
232         return principal == null ? null : principal.getPrincipalId();
233     }
234 
235     public List<TimeBlock> getPrevDocumentTimeBlocks(String principalId, Date payBeginDate) {
236         TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, payBeginDate);
237         if (prevTdh == null) {
238             return new ArrayList<TimeBlock>();
239         }
240         return TkServiceLocator.getTimeBlockService().getTimeBlocks(prevTdh.getDocumentId());
241     }
242 
243     @Override
244     public TimesheetDocument getTimesheetDocument(String documentId) {
245         TimesheetDocument timesheetDocument = null;
246         TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
247 
248         if (tdh != null) {
249             timesheetDocument = new TimesheetDocument(tdh);
250             CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
251             loadTimesheetDocumentData(timesheetDocument, tdh.getPrincipalId(), pce);
252 
253             timesheetDocument.setCalendarEntry(pce);
254         } else {
255             LOG.error("Could not find TimesheetDocumentHeader for DocumentID: " + documentId);
256             return null;
257         }
258         return timesheetDocument;
259     }
260 
261     protected void loadTimesheetDocumentData(TimesheetDocument tdoc, String principalId, CalendarEntries payCalEntry) {
262         tdoc.setAssignments(TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, payCalEntry));
263         if (payCalEntry != null) {
264             tdoc.setJobs(TkServiceLocator.getJobService().getJobs(principalId, TKUtils.getTimelessDate(payCalEntry.getEndPeriodDate())));
265         }
266         tdoc.setTimeBlocks(TkServiceLocator.getTimeBlockService().getTimeBlocks(tdoc.getDocumentHeader().getDocumentId()));
267     }
268 
269     public boolean isSynchronousUser() {
270         List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(TKUser.getCurrentTargetPersonId(), TKUtils.getCurrentDate());
271         boolean isSynchronousUser = true;
272         for (Assignment assignment : assignments) {
273             isSynchronousUser &= assignment.isSynchronous();
274         }
275         return isSynchronousUser;
276     }
277 
278     //this is an admin function used for testing
279     public void deleteTimesheet(String documentId) {
280         TkServiceLocator.getTimeBlockService().deleteTimeBlocksAssociatedWithDocumentId(documentId);
281         TkServiceLocator.getTimesheetDocumentHeaderService().deleteTimesheetHeader(documentId);
282     }
283 
284     public TimeBlock resetWorkedHours(TimeBlock timeBlock, Date asOfDate) {
285     	EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(timeBlock.getEarnCode(), new java.sql.Date(asOfDate.getTime()));
286     	
287         if (timeBlock.getBeginTime() != null && timeBlock.getEndTime() != null && StringUtils.equals(timeBlock.getEarnCodeType(), TkConstants.EARN_CODE_TIME)) {
288             BigDecimal hours = TKUtils.getHoursBetween(timeBlock.getBeginTime().getTime(), timeBlock.getEndTime().getTime());
289             
290             //If earn code has an inflate min hours check if it is greater than zero
291             //and compare if the hours specified is less than min hours awarded for this
292             //earn code
293             if (earnCodeObj.getInflateMinHours() != null) {
294                 if ((earnCodeObj.getInflateMinHours().compareTo(BigDecimal.ZERO) != 0) &&
295                         earnCodeObj.getInflateMinHours().compareTo(hours) > 0) {
296                     hours = earnCodeObj.getInflateMinHours();
297                 }
298             }
299             //If earn code has an inflate factor multiple hours specified by the factor
300             if (earnCodeObj.getInflateFactor() != null) {
301                 if ((earnCodeObj.getInflateFactor().compareTo(new BigDecimal(1.0)) != 0)
302                 		&& (earnCodeObj.getInflateFactor().compareTo(BigDecimal.ZERO)!= 0) ) {
303                     hours = earnCodeObj.getInflateFactor().multiply(hours, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE);
304                 }
305             }
306             
307             timeBlock.setHours(hours);
308         }
309         return timeBlock;
310     }
311 
312     @Override
313     public void resetTimeBlock(List<TimeBlock> timeBlocks, Date asOfDate) {
314         for (TimeBlock tb : timeBlocks) {
315             resetWorkedHours(tb, asOfDate);
316         }
317         TkServiceLocator.getTimeBlockService().resetTimeHourDetail(timeBlocks);
318     }
319 
320 	@Override
321 	public boolean isReadyToApprove(TimesheetDocument document) {
322         if (document == null) {
323             return false;
324         }
325         List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
326         		document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
327         leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
328         		document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
329         for(LeaveBlock lb : leaveBlocks) {
330         	if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) &&
331         			!StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED))
332         		return false;
333         }
334         return true;
335 /*        List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(),
336                 document.getCalendarEntry().getBeginPeriodDate(),
337                 document.getCalendarEntry().getEndPeriodDate());
338         if (!CollectionUtils.isEmpty(balanceTransfers))   {
339 	        for(BalanceTransfer balanceTransfer : balanceTransfers) {
340 	        	if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
341 	        		return false;
342 	            if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus())
343 	                    && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) {
344 	                return false;
345 	            }
346 	        }
347         }
348         List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(),
349         		document.getCalendarEntry().getBeginPeriodDate(),
350         		document.getCalendarEntry().getEndPeriodDate());
351         if (!CollectionUtils.isEmpty(leavePayouts)) {
352         	for(LeavePayout payout : leavePayouts) {
353 	        	if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
354 	        		return false;
355 	            if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus())
356 	                    && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) {
357 	                return false;
358 	            }
359         	}
360         }
361         return true;*/
362 	}
363 
364 }