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.time.timesheet.service;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.collections.MapUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.log4j.Logger;
22  import org.joda.time.DateTime;
23  import org.joda.time.DateTimeZone;
24  import org.joda.time.Interval;
25  import org.joda.time.LocalDate;
26  import org.joda.time.format.DateTimeFormat;
27  import org.kuali.kpme.core.api.accrualcategory.AccrualCategory;
28  import org.kuali.kpme.core.api.assignment.Assignment;
29  import org.kuali.kpme.core.api.block.CalendarBlockPermissions;
30  import org.kuali.kpme.core.api.calendar.entry.CalendarEntry;
31  import org.kuali.kpme.core.api.earncode.EarnCode;
32  import org.kuali.kpme.core.api.earncode.EarnCodeContract;
33  import org.kuali.kpme.core.api.earncode.security.EarnCodeSecurity;
34  import org.kuali.kpme.core.api.earncode.security.EarnCodeSecurityContract;
35  import org.kuali.kpme.core.api.job.JobContract;
36  import org.kuali.kpme.core.api.permission.HRPermissionService;
37  import org.kuali.kpme.core.api.principal.PrincipalHRAttributes;
38  import org.kuali.kpme.core.batch.BatchJobUtil;
39  import org.kuali.kpme.core.earncode.security.EarnCodeType;
40  import org.kuali.kpme.core.role.KPMERole;
41  import org.kuali.kpme.core.service.HrServiceLocator;
42  import org.kuali.kpme.core.util.HrConstants;
43  import org.kuali.kpme.core.util.HrContext;
44  import org.kuali.kpme.core.util.TKUtils;
45  import org.kuali.kpme.tklm.api.common.TkConstants;
46  import org.kuali.kpme.tklm.api.leave.block.LeaveBlock;
47  import org.kuali.kpme.tklm.api.leave.timeoff.SystemScheduledTimeOffContract;
48  import org.kuali.kpme.tklm.api.time.timeblock.TimeBlock;
49  import org.kuali.kpme.tklm.common.LMConstants;
50  import org.kuali.kpme.tklm.common.WorkflowTagSupport;
51  import org.kuali.kpme.tklm.leave.block.LeaveBlockAggregate;
52  import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
53  import org.kuali.kpme.tklm.time.flsa.FlsaDay;
54  import org.kuali.kpme.tklm.time.flsa.FlsaWeek;
55  import org.kuali.kpme.tklm.time.rules.timecollection.TimeCollectionRule;
56  import org.kuali.kpme.tklm.time.service.TkServiceLocator;
57  import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
58  import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
59  import org.kuali.kpme.tklm.time.workflow.TimesheetDocumentHeader;
60  import org.kuali.rice.core.api.mo.ModelObjectUtils;
61  import org.kuali.rice.kew.api.KewApiServiceLocator;
62  import org.kuali.rice.kew.api.WorkflowDocument;
63  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
64  import org.kuali.rice.kew.api.action.ActionRequest;
65  import org.kuali.rice.kew.api.exception.WorkflowException;
66  import org.kuali.rice.kew.api.note.Note;
67  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
68  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
69  import org.kuali.rice.krad.util.GlobalVariables;
70  
71  import java.math.BigDecimal;
72  import java.util.*;
73  
74  public class TimesheetServiceImpl implements TimesheetService {
75  
76      private static final Logger LOG = Logger.getLogger(TimesheetServiceImpl.class);
77      private static final ModelObjectUtils.Transformer<TimeBlock, TimeBlock.Builder> toTimeBlockBuilder =
78              new ModelObjectUtils.Transformer<TimeBlock, TimeBlock.Builder>() {
79                  public TimeBlock.Builder transform(TimeBlock input) {
80                      return TimeBlock.Builder.create(input);
81                  };
82              };
83      private HRPermissionService hrPermissionService;
84  
85      @Override
86      public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument) {
87          routeTimesheet(principalId, timesheetDocument, HrConstants.DOCUMENT_ACTIONS.ROUTE);
88      }
89  
90      @Override
91      public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
92          timesheetAction(action, principalId, timesheetDocument);
93      }
94  
95      @Override
96      public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
97          timesheetAction(HrConstants.DOCUMENT_ACTIONS.APPROVE, principalId, timesheetDocument);
98      }
99  
100     @Override
101     public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
102         timesheetAction(action, principalId, timesheetDocument);
103     }
104 
105     @Override
106     public void disapproveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
107         timesheetAction(HrConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, timesheetDocument);
108     }
109 
110     protected void timesheetAction(String action, String principalId, TimesheetDocument timesheetDocument) {
111         WorkflowDocument wd = null;
112         if (timesheetDocument != null) {
113             //TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(timesheetDocument, timesheetDocument.getAsOfDate());
114             String rhid = timesheetDocument.getDocumentId();
115             wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
116             List<ActionRequest> actionRequests = KewApiServiceLocator.getWorkflowDocumentService().getPendingActionRequests(rhid);
117             if (StringUtils.equals(action, HrConstants.DOCUMENT_ACTIONS.ROUTE)) {
118                 wd.route("Routing for Approval");
119             } else if (StringUtils.equals(action, HrConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
120             	Note.Builder builder = Note.Builder.create(rhid, principalId);
121                 builder.setCreateDate(new DateTime());
122                 builder.setText("Routed via Employee Approval batch job");
123             	KewApiServiceLocator.getNoteService().createNote(builder.build());
124 
125             	wd.route("Batch job routing timesheet");
126             } else if (StringUtils.equals(action, HrConstants.DOCUMENT_ACTIONS.APPROVE)) {
127                 if (HrServiceLocator.getHRPermissionService().canSuperUserAdministerCalendarDocument(GlobalVariables.getUserSession().getPrincipalId(), timesheetDocument)
128                 		&& !HrServiceLocator.getHRPermissionService().canApproveCalendarDocument(GlobalVariables.getUserSession().getPrincipalId(), timesheetDocument)) {
129                     wd.superUserBlanketApprove("Superuser approving timesheet.");
130                 } else {
131                     wd.approve("Approving timesheet.");
132                 }
133             } else if (StringUtils.equals(action, HrConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) {
134                 boolean approverFlag = false;
135                 for (ActionRequest ar : actionRequests) {
136                 	if(StringUtils.equals(ar.getQualifiedRoleNameLabel(), KPMERole.APPROVER.getRoleName())) {
137                 		approverFlag = true;
138                 		break;
139                 	}
140                 }
141                 // if there's still action requested to be taken by approver, then approve the document with this SupervisorApproval batch job
142                 // otherwise, don't take any actions
143                 if(approverFlag) {
144 	            	// supervisor approval job should take approve action but not finalize the document if there's payroll processor set up for this doc
145 	            	Note.Builder builder = Note.Builder.create(rhid, principalId);
146 	           	 	builder.setCreateDate(new DateTime());
147 	           	 	builder.setText("Approved via Supervisor Approval batch job");
148 	           	 	KewApiServiceLocator.getNoteService().createNote(builder.build());
149 	           	 	wd.approve("Supervisor Batch job approving timesheet on behalf of approvers.");
150                 }
151             } else if (StringUtils.equals(action, HrConstants.BATCH_JOB_ACTIONS.PAYROLL_JOB_APPROVE)) {
152             	boolean payrollProcessorFlag = false;
153                 for (ActionRequest ar : actionRequests) {
154                 	if(StringUtils.equals(ar.getQualifiedRoleNameLabel(), KPMERole.PAYROLL_PROCESSOR.getRoleName())) {
155                 		payrollProcessorFlag = true;
156                  		break;
157                  	}
158                  }
159                  if(payrollProcessorFlag) {
160 	            	Note.Builder builder = Note.Builder.create(rhid, principalId);
161 	           	 	builder.setCreateDate(new DateTime());
162 	           	 	builder.setText("Approved via Payroll Processor Approval batch job");
163 	           	 	KewApiServiceLocator.getNoteService().createNote(builder.build());
164 	            	wd.approve("Payroll Processor Batch job approving timesheet on behalf of payroll processors.");
165                  }
166             } else if (StringUtils.equals(action, HrConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
167                 if (HrServiceLocator.getHRPermissionService().canSuperUserAdministerCalendarDocument(GlobalVariables.getUserSession().getPrincipalId(), timesheetDocument)
168                 		&& !HrServiceLocator.getHRPermissionService().canApproveCalendarDocument(GlobalVariables.getUserSession().getPrincipalId(), timesheetDocument)) {
169                     wd.superUserDisapprove("Superuser disapproving timesheet.");
170                 } else {
171                     wd.disapprove("Disapproving timesheet.");
172                 }
173             }
174             clearTimesheetTimeblockPermissions(timesheetDocument);
175         }
176     }
177 
178     @Override
179     public TimesheetDocument openTimesheetDocument(String principalId, CalendarEntry calendarDates) throws WorkflowException {
180         TimesheetDocument timesheetDocument = null;
181 
182         DateTime begin = calendarDates.getBeginPeriodFullDateTime();
183         DateTime end = calendarDates.getEndPeriodFullDateTime();
184 
185         TimesheetDocumentHeader header = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, begin, end);
186 
187         if (header == null) {
188             List<Assignment> activeAssignments = HrServiceLocator.getAssignmentService().getAllAssignmentsByCalEntryForTimeCalendar(principalId, calendarDates);
189             //HrServiceLocator.getAssignmentService().getAssignmentMap(principalId, TKUtils.getTimelessDate(payCalendarDates.getEndPeriodDate()));
190             if (CollectionUtils.isEmpty(activeAssignments)) {
191                 LOG.warn("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodFullDateTime());
192                 return null;
193                 //throw new RuntimeException("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
194             }
195 
196             EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
197             String principalName = person != null && person.getDefaultName() != null ? person.getDefaultName().getCompositeName() : StringUtils.EMPTY;
198             String endDateString = TKUtils.formatDate(end.toLocalDate());
199             String timesheetDocumentTitle = TimesheetDocument.TIMESHEET_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + endDateString;
200 
201             timesheetDocument = this.initiateWorkflowDocument(principalId, begin, end, calendarDates, TimesheetDocument.TIMESHEET_DOCUMENT_TYPE, timesheetDocumentTitle);
202             //timesheetDocument.setPayCalendarEntry(calendarDates);
203             //this.loadTimesheetDocumentData(timesheetDocument, principalId, calendarDates);
204             //TODO switch this to scheduled time offs
205             //this.loadHolidaysOnTimesheet(timesheetDocument, principalId, begin, end);
206         } else {
207             timesheetDocument = this.getTimesheetDocument(header.getDocumentId());
208             if (timesheetDocument != null) {
209             	timesheetDocument.setCalendarEntry(calendarDates);
210             }
211         }
212 
213         //if (timesheetDocument != null) {
214         //	timesheetDocument.setTimeSummary(TkServiceLocator.getTimeSummaryService().getTimeSummary(timesheetDocument));
215         //}
216 
217         return timesheetDocument;
218     }
219 
220     public void loadHolidaysOnTimesheet(TimesheetDocument timesheetDocument, String principalId, LocalDate beginDate, LocalDate endDate) {
221         PrincipalHRAttributes principalCalendar = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, beginDate);
222         if (principalCalendar != null && StringUtils.isNotEmpty(principalCalendar.getLeavePlan())) {
223         	List<? extends SystemScheduledTimeOffContract> sstoList = LmServiceLocator.getSysSchTimeOffService()
224         		.getSystemScheduledTimeOffForPayPeriod(principalCalendar.getLeavePlan(), beginDate, endDate);
225             Assignment sstoAssign = getAssignmentToApplyScheduledTimeOff(timesheetDocument.getPrincipalId(), timesheetDocument.getAllAssignments(), endDate);
226         	if (sstoAssign != null) {
227         		for(SystemScheduledTimeOffContract ssto : sstoList) {
228                   BigDecimal sstoCalcHours = LmServiceLocator.getSysSchTimeOffService().calculateSysSchTimeOffHours(sstoAssign.getJob(), ssto.getAmountofTime());
229                   TimeBlock timeBlock = TkServiceLocator.getTimeBlockService().createTimeBlock(timesheetDocument.getPrincipalId(), timesheetDocument.getDocumentId(), ssto.getScheduledTimeOffLocalDate().toDateTimeAtStartOfDay(),
230                           ssto.getScheduledTimeOffLocalDate().toDateTimeAtStartOfDay(), sstoAssign, HrConstants.HOLIDAY_EARN_CODE, sstoCalcHours, BigDecimal.ZERO, false, false, HrContext.getPrincipalId());
231                   timesheetDocument.getTimeBlocks().add(timeBlock);
232               }
233 	            //If system scheduled time off are loaded will need to save them to the database
234 		        if (CollectionUtils.isNotEmpty(sstoList)) {
235 		           TkServiceLocator.getTimeBlockService().saveOrUpdateTimeBlocks(Collections.<TimeBlock>emptyList(), timesheetDocument.getTimeBlocks(), HrContext.getPrincipalId());
236 		        }
237         	}
238         }
239     }
240 
241     private Assignment getAssignmentToApplyScheduledTimeOff(String principalId, List<Assignment> assignments, LocalDate endDate) {
242 		JobContract primaryJob = HrServiceLocator.getJobService().getPrimaryJob(principalId, endDate);
243 		for(Assignment assign : assignments){
244 			if(assign.getJobNumber().equals(primaryJob.getJobNumber())){
245 				return assign;
246 			}
247 		}
248 		return null;
249 	}
250 
251 	protected TimesheetDocument initiateWorkflowDocument(String principalId, DateTime payBeginDate,  DateTime payEndDate, CalendarEntry calendarEntry, String documentType, String title) throws WorkflowException {
252         TimesheetDocument timesheetDocument = null;
253         WorkflowDocument workflowDocument = null;
254 
255         workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title);
256 
257         String status = workflowDocument.getStatus().getCode();
258         TimesheetDocumentHeader documentHeader = new TimesheetDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate.toDate(), payEndDate.toDate(), status);
259 
260         documentHeader.setDocumentId(workflowDocument.getDocumentId());
261         documentHeader.setDocumentStatus("I");
262 
263         TkServiceLocator.getTimesheetDocumentHeaderService().saveOrUpdate(documentHeader);
264         timesheetDocument = new TimesheetDocument(documentHeader);
265         timesheetDocument.setCalendarEntry(calendarEntry);
266         loadTimesheetDocumentData(timesheetDocument, principalId, calendarEntry);
267         TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(timesheetDocument, payEndDate.toLocalDate());
268 
269         if (LmServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, HrConstants.FLSA_STATUS_NON_EXEMPT, true)) {
270         	deleteNonApprovedLeaveBlocks(principalId, calendarEntry.getBeginPeriodFullDateTime().toLocalDate(), calendarEntry.getEndPeriodFullDateTime().toLocalDate());
271         }
272 
273         return timesheetDocument;
274     }
275 
276     private void deleteNonApprovedLeaveBlocks(String principalId, LocalDate beginDate, LocalDate endDate) {
277     	String batchUserPrincipalId = BatchJobUtil.getBatchUserPrincipalId();
278 
279         if (batchUserPrincipalId != null) {
280 	    	List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
281 
282 	    	for (LeaveBlock leaveBlock : leaveBlocks) {
283 	    		if (!StringUtils.equals(leaveBlock.getRequestStatus(), HrConstants.REQUEST_STATUS.APPROVED)) {
284                     LmServiceLocator.getLeaveRequestDocumentService().suCancelLeave(
285                             leaveBlock.getLeaveRequestDocumentId(), batchUserPrincipalId);
286                     LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
287 	    		}
288 	    	}
289         } else {
290         	String principalName = BatchJobUtil.getBatchUserPrincipalName();
291         	LOG.error("Could not delete leave request blocks due to missing batch user " + principalName);
292         }
293     }
294 
295 
296     public List<TimeBlock> getPrevDocumentTimeBlocks(String principalId, DateTime payBeginDate) {
297         TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, payBeginDate);
298         if (prevTdh == null) {
299             return new ArrayList<TimeBlock>();
300         }
301         return TkServiceLocator.getTimeBlockService().getTimeBlocks(prevTdh.getDocumentId());
302     }
303 
304     @Override
305     public TimesheetDocument getTimesheetDocument(String documentId) {
306         TimesheetDocument timesheetDocument = null;
307         TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
308 
309         if (tdh != null) {
310             timesheetDocument = new TimesheetDocument(tdh);
311             CalendarEntry pce =  HrServiceLocator.getCalendarEntryService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDateTime(), HrConstants.PAY_CALENDAR_TYPE);
312             loadTimesheetDocumentData(timesheetDocument, tdh.getPrincipalId(), pce);
313 
314             timesheetDocument.setCalendarEntry(pce);
315         }
316 
317         return timesheetDocument;
318     }
319 
320     protected void loadTimesheetDocumentData(TimesheetDocument tdoc, String principalId, CalendarEntry payCalEntry) {
321     	//tdoc.setAssignments(HrServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, payCalEntry));
322     	tdoc.setAssignments(HrServiceLocator.getAssignmentService().getAssignmentHistoryForCalendarEntry(principalId, payCalEntry));
323     	if (payCalEntry != null) {
324     		tdoc.setJobs(HrServiceLocator.getJobService().getJobs(principalId, payCalEntry.getEndPeriodFullDateTime().toLocalDate()));
325     	}
326     	tdoc.setTimeBlocks(TkServiceLocator.getTimeBlockService().getTimeBlocks(tdoc.getDocumentHeader().getDocumentId()));
327     }
328 
329     public boolean isSynchronousUser() {
330         List<Assignment> assignments = HrServiceLocator.getAssignmentService().getAssignments(HrContext.getTargetPrincipalId(), LocalDate.now());
331         boolean isSynchronousUser = true;
332         for (Assignment assignment : assignments) {
333         	if(assignment.getJob() != null) {
334 	        	TimeCollectionRule tcr = TkServiceLocator.getTimeCollectionRuleService().getTimeCollectionRule(assignment.getDept(), assignment.getWorkArea(), assignment.getJob().getHrPayType(), assignment.getGroupKeyCode(), LocalDate.now());
335 	            isSynchronousUser &= tcr == null || tcr.isClockUserFl();
336         	}
337         }
338         return isSynchronousUser;
339     }
340 
341     //this is an admin function used for testing
342     public void deleteTimesheet(String documentId) {
343         TkServiceLocator.getTimeBlockService().deleteTimeBlocksAssociatedWithDocumentId(documentId);
344         TkServiceLocator.getTimesheetDocumentHeaderService().deleteTimesheetHeader(documentId);
345     }
346 
347     protected void resetWorkedHours(TimeBlock.Builder previousTimeBlock, TimeBlock.Builder timeBlock, LocalDate asOfDate) {
348     	EarnCodeContract earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(timeBlock.getEarnCode(), asOfDate);
349         if (timeBlock.getBeginTime() != null && timeBlock.getEndTime() != null && StringUtils.equals(timeBlock.getEarnCodeType(), HrConstants.EARN_CODE_TIME)) {
350             BigDecimal hours = TKUtils.getHoursBetween(timeBlock.getBeginDateTime().getMillis(), timeBlock.getEndDateTime().getMillis());
351 
352             //If earn code has an inflate min hours check if it is greater than zero
353             //and compare if the hours specified is less than min hours awarded for this
354             //earn code
355             if (earnCodeObj.getInflateMinHours() != null) {
356             	if ((earnCodeObj.getInflateMinHours().compareTo(BigDecimal.ZERO) != 0) &&
357             			earnCodeObj.getInflateMinHours().compareTo(hours) > 0) {
358                     //if previous timeblock has no gap then assume its one block if the same earn code and divide inflated hours accordingly
359                     if(previousTimeBlock != null && StringUtils.equals(earnCodeObj.getEarnCode(),previousTimeBlock.getEarnCode()) &&
360                             (timeBlock.getBeginDateTime().getMillis() - previousTimeBlock.getEndDateTime().getMillis() == 0L)) {
361                         BigDecimal prevTimeBlockHours = TKUtils.getHoursBetween(previousTimeBlock.getBeginDateTime().getMillis(), previousTimeBlock.getEndDateTime().getMillis());
362                         previousTimeBlock.setHours(prevTimeBlockHours);
363                     }
364                 }
365             }
366 
367             timeBlock.setHours(hours);
368         }
369     }
370 
371     @Override
372     public List<TimeBlock> resetTimeBlock(List<TimeBlock> timeBlocks, LocalDate asOfDate) {
373         TimeBlock.Builder previous = null;
374         List<TimeBlock.Builder> builders = ModelObjectUtils.transform(timeBlocks, toTimeBlockBuilder);
375         for (TimeBlock.Builder tb : builders) {
376             resetWorkedHours(previous, tb, asOfDate);
377             previous = tb;
378         }
379         return TkServiceLocator.getTimeBlockService().resetTimeHourDetail(ModelObjectUtils.<TimeBlock>buildImmutableCopy(builders));
380     }
381 
382 	@Override
383 	public boolean isReadyToApprove(TimesheetDocument document) {
384         if (document == null) {
385             return false;
386         }
387         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
388         		document.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate(), document.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
389         leaveBlocks.addAll(LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
390         		document.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate(), document.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
391         for(LeaveBlock lb : leaveBlocks) {
392         	if(!StringUtils.equals(lb.getRequestStatus(),HrConstants.REQUEST_STATUS.APPROVED) &&
393         			!StringUtils.equals(lb.getRequestStatus(), HrConstants.REQUEST_STATUS.DISAPPROVED))
394         		return false;
395         }
396         return true;
397 	}
398 
399     @Override
400     public List<EarnCode> getEarnCodesForTime(Assignment a, LocalDate asOfDate, boolean includeRegularEarnCode) {
401         //getEarnCodesForTime and getEarnCodesForLeave have some overlapping logic, but they were separated so that they could follow their own distinct logic, so consolidation of logic is not desirable.
402 
403         if (a == null) {
404         	LOG.error("No assignment parameter.");
405         	return null;
406 //        	throw new RuntimeException("No assignment parameter.");
407         }
408         JobContract job = a.getJob();
409         if (job == null || job.getPayTypeObj() == null) {
410         	LOG.error("Null job or null job pay type on assignment.");
411         	return null;
412 //        	throw new RuntimeException("Null job or null job pay type on assignment.");
413         }
414 
415         List<EarnCode> earnCodes = new LinkedList<EarnCode>();
416         String earnTypeCode = EarnCodeType.TIME.getCode();
417 
418         TimeCollectionRule tcr = null;
419         if(a.getJob() != null) {
420             tcr = TkServiceLocator.getTimeCollectionRuleService().getTimeCollectionRule(a.getDept(), a.getWorkArea(), a.getJob().getHrPayType(), a.getGroupKeyCode(), asOfDate);
421         }
422         boolean isClockUser = tcr == null || tcr.isClockUserFl();
423         boolean isUsersTimesheet = StringUtils.equals(HrContext.getPrincipalId(),a.getPrincipalId());
424 
425         // Reg earn codes will typically not be defined in the earn code security table
426         EarnCode regularEarnCode = HrServiceLocator.getEarnCodeService().getEarnCode(job.getPayTypeObj().getRegEarnCode(), asOfDate);
427         if (regularEarnCode == null) {
428         	LOG.error("No regular earn code defined for job pay type.");
429         	return null;
430 //            throw new RuntimeException("No regular earn code defined for job pay type.");
431         } else {
432             //  if you are a clock user and this is your timesheet and you are processing the reg earn code, do not add this earn code. Use the clock in/out mechanism.
433         	if (!isClockUser || !isUsersTimesheet || includeRegularEarnCode) {
434                 earnCodes.add(regularEarnCode);
435             }
436         }
437 
438         List<String> listAccrualCategories = new LinkedList<String>();
439         String accrualCategory;
440 
441         //  first make a list of the accrual categories available to the user's Leave Plan (yes, leave plan), for later comparison.
442         PrincipalHRAttributes principalHRAttributes = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(job.getPrincipalId(), asOfDate);
443         boolean fmlaEligible = principalHRAttributes.isFmlaEligible();
444         boolean workersCompEligible = principalHRAttributes.isWorkersCompEligible();
445 
446         String leavePlan = principalHRAttributes.getLeavePlan();
447         if (leavePlan != null) {
448             for (AccrualCategory accrualCategories : HrServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(leavePlan, asOfDate)) {
449                 accrualCategory = accrualCategories.getAccrualCategory();
450                 if(accrualCategory != null) {
451                     listAccrualCategories.add(accrualCategory);
452                 }
453             }
454         }
455 
456         //  get all earn codes by user security, then we'll filter on accrual category first as we process them.
457         List<EarnCodeSecurity> decs = HrServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), asOfDate, job.getGroupKey().getGroupKeyCode());
458         for (EarnCodeSecurity dec : decs) {
459 
460             boolean addEarnCode = HrServiceLocator.getEarnCodeService().addEarnCodeBasedOnEmployeeApproverSettings(dec, a, asOfDate);
461             if (addEarnCode) {
462 
463                 //  allow types Time AND Both
464                 if (earnTypeCode.equals(dec.getEarnCodeType()) || EarnCodeType.BOTH.getCode().equals(dec.getEarnCodeType())) {
465                     EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(dec.getEarnCode(), asOfDate);
466 
467                     //  make sure we got something back from the earn code dao
468                     if (ec != null) {
469                     	// make sure the earn code's leave plan matches the user's leave plan
470                     	// if user has a leave plan, we show earn codes with a matching leave plan and all earn codes without a leave plan
471                     	// if user doe not have a leave plan, we show earn codes that don't have a leave plan
472                     	if( (StringUtils.isNotBlank(leavePlan) && StringUtils.isBlank(ec.getLeavePlan()))
473                     			|| (StringUtils.isNotBlank(leavePlan) && StringUtils.isNotBlank(ec.getLeavePlan()) && StringUtils.equals(leavePlan, ec.getLeavePlan()))
474     							|| (StringUtils.isBlank(leavePlan) && StringUtils.isBlank(ec.getLeavePlan()))) {
475 	                        //  if the user's fmla flag is Yes, that means we are not restricting codes based on this flag, so any code is shown.
476 	                        //    if the fmla flag on a code is yes they can see it.    (allow)
477 	                        //    if the fmla flag on a code is no they should see it.  (allow)
478 	                        //  if the user's fmla flag is No,
479 	                        //    they can see any codes which are fmla=no.             (allow)
480 	                        //    they can not see codes with fmla=yes.                 (exclude earn code)
481 	                        //  the fmla earn codes=no do not require any exclusion
482 	                        //  the only action required is if the fmla user flag=no: exclude those codes with fmla=yes.
483 	                        if ( (fmlaEligible || ec.getFmla().equals("N")) ) {
484 	                        	if (StringUtils.isEmpty(ec.getAccrualCategory())
485 	                        		|| (listAccrualCategories.contains(ec.getAccrualCategory())
486 	                         	 		&& HrConstants.ACCRUAL_BALANCE_ACTION.USAGE.equals(ec.getAccrualBalanceAction()))) {
487 	                            // go on, we are allowing these three combinations: YY, YN, NN
488 
489 	                                //  apply the same logic as FMLA to the Worker Compensation flags.
490 	                                if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) {
491 	                                    // go on, we are allowing these three combinations: YY, YN, NN.
492 
493 	                                    //  determine if the holiday earn code should be displayed.
494 	                                    if ( showEarnCodeIfHoliday(ec, dec) ) {
495 	                                        //  non-Holiday earn code will go on, Holiday earn code must meet some requirements in the method.
496 	                                    	// KPME-2556
497 	                                        //if ( !StringUtils.equals(regularEarnCode.toString(), dec.getEarnCode()) ) {
498 	                                    	if (!StringUtils.equals(regularEarnCode.getEarnCode(), dec.getEarnCode()) ) {
499 	                                            //  add earn code if it is not the reg earn code.
500 	                                            earnCodes.add(ec);
501 	                                        }
502 	                                    }
503 	                                }
504 	                            }
505 	                        }
506                     	}
507                     }
508                 }
509             }
510         }
511 
512         return earnCodes;
513     }
514 
515     public List<EarnCode> getEarnCodesForTime(Assignment a, LocalDate asOfDate) {
516     	return getEarnCodesForTime(a, asOfDate, false);
517 	}
518 
519     private boolean showEarnCodeIfHoliday(EarnCode earnCode, EarnCodeSecurityContract security) {
520         if (earnCode.getEarnCode().equals(HrConstants.HOLIDAY_EARN_CODE)) {
521             if (security.isApprover() || HrContext.isSystemAdmin()) {
522                 return true;
523             } else {
524                 return false;
525             }
526         } else {
527             return true;
528         }
529     }
530 
531     public HRPermissionService getHRPermissionService() {
532         if (hrPermissionService == null) {
533             hrPermissionService = HrServiceLocator.getHRPermissionService();
534         }
535         return hrPermissionService;
536     }
537 
538     private void clearTimesheetTimeblockPermissions(TimesheetDocument doc) {
539         for (TimeBlock tb : doc.getTimeBlocks()) {
540             getHRPermissionService().updateTimeBlockPermissions(CalendarBlockPermissions.newInstance(tb.getTkTimeBlockId()));
541         }
542     }
543 
544     public boolean isTimesheetValid(TimesheetDocument td) {
545         boolean isTimeSheetValid = true;
546 
547         if (WorkflowTagSupport.isTimesheetApprovalButtonsDisplaying(td.getDocumentId())) {
548             if (CollectionUtils.isNotEmpty(validateHours(td)) || CollectionUtils.isNotEmpty(validateHours(td))) {
549                     isTimeSheetValid = false ;
550                 }
551         }
552         return isTimeSheetValid;
553     }
554 
555     public List<String> validateTimeBlock(TimesheetDocument td) {
556         List<String> errors = new ArrayList<String>();
557         if (td != null) {
558 
559             Map<String, String> earnCodeTypeMap = new HashMap<String, String>();
560 
561 
562             for (TimeBlock timeBlock : td.getTimeBlocks()) {
563                 String earnCode = timeBlock.getEarnCode();
564                 if (earnCodeTypeMap.containsKey(earnCode)) {
565                     continue;
566                 } else {
567                     EarnCodeContract earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(earnCode, td.getAsOfDate());
568                     if (earnCodeObj != null) {
569                         earnCodeTypeMap.put(earnCodeObj.getEarnCode(),earnCodeObj.getEarnCodeType());
570                     }
571                 }
572             }
573 
574             for (TimeBlock timeBlock : td.getTimeBlocks()) {
575                 DateTime beginDate = timeBlock.getBeginDateTime();
576                 String earnCodeType = earnCodeTypeMap.get(timeBlock.getEarnCode());
577                 if (earnCodeType != null && HrConstants.EARN_CODE_TIME.equals(earnCodeType)) {
578                     String timeBlockDesc = "TimeBlock (" + timeBlock.getTkTimeBlockId() + ") on " + DateTimeFormat.forPattern("EEE MMM d").print(timeBlock.getBeginDateTime()) + " from " + timeBlock.getBeginTimeDisplayTimeOnlyString() + " - " + timeBlock.getEndTimeDisplayTimeOnlyString();
579                     for (TimeBlock compareTimeBlock : td.getTimeBlocks()) {
580                     	if(compareTimeBlock.getTkTimeBlockId()!=null && timeBlock.getTkTimeBlockId()!=null){
581                     		if (compareTimeBlock.getTkTimeBlockId().equals(timeBlock.getTkTimeBlockId())) {
582                     			continue;
583                     		}
584                     	}
585                         String compareEarnCodeType = earnCodeTypeMap.get(compareTimeBlock.getEarnCode());
586                         if (compareEarnCodeType != null && HrConstants.EARN_CODE_TIME.equals(compareEarnCodeType)) {
587                             String compareTimeBlockDesc = "TimeBlock (" + compareTimeBlock.getTkTimeBlockId() + ") on " + DateTimeFormat.forPattern("EEE MMM d").print(compareTimeBlock.getBeginDateTime()) + " from " + compareTimeBlock.getBeginTimeDisplayTimeOnlyString() + " - " + compareTimeBlock.getEndTimeDisplayTimeOnlyString();
588                             Interval compareTimeBlockInterval = new Interval(compareTimeBlock.getBeginDateTime(), compareTimeBlock.getEndDateTime());
589                             if (compareTimeBlockInterval.contains(beginDate.getMillis())) {
590                                 errors.add("Error : [" + timeBlockDesc + " overlaps with " + compareTimeBlockDesc + ".]" );
591                             }
592                         }
593                     }
594 
595                     List<String> assignmentKeyList = new ArrayList<String>();
596 
597 
598                     for (Assignment assignment : td.getAssignmentMap().get(timeBlock.getBeginDateTime().toLocalDate())) {
599                         assignmentKeyList.add(assignment.getAssignmentKey());
600                     }
601                     if (!assignmentKeyList.contains(timeBlock.getAssignmentKey())) {
602                         errors.add("Error: [" + timeBlockDesc + " contains an invalid assignment.]");
603                     }
604                 }
605             }
606         }
607         return errors;
608     }
609 
610     public List<String> validateHours(TimesheetDocument timesheetDocument) {
611         List<String> errors = new ArrayList<String>();
612 
613 
614         if (timesheetDocument != null && WorkflowTagSupport.isTimesheetApprovalButtonsDisplaying(timesheetDocument.getDocumentId())) {
615             DateTimeZone userTimeZone = DateTimeZone.forID(HrServiceLocator.getTimezoneService().getUserTimezone(timesheetDocument.getPrincipalId()));
616 
617             if (userTimeZone == null) {
618                 userTimeZone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
619             }
620             String assignmentDesc = "";
621             for (Assignment assignment : timesheetDocument.getAllAssignments()) {
622                 //get standard hours for job on assignment
623                 BigDecimal standardHours = assignment.getJob().getStandardHours();
624                 LocalDate jobStartDate = assignment.getJob().getEffectiveLocalDate();
625                 //if standard hours is 0 no validation is needed.
626                 if (standardHours.compareTo(new BigDecimal(0)) == 0) {
627                     continue;
628                 }
629 
630                 //create a aggregate of timeblocks for current assignment in the loop
631                 List<TimeBlock> assignmentTimeBlocks = new ArrayList<TimeBlock>();
632                 for (TimeBlock timeBlock : timesheetDocument.getTimeBlocks()) {
633                     if (timeBlock.getAssignmentKey().equals(assignment.getAssignmentKey())) {
634                         assignmentTimeBlocks.add(timeBlock);
635                     }
636                 }
637 
638                 TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(assignmentTimeBlocks, timesheetDocument.getCalendarEntry(), HrServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true);
639 
640                 //create an aggregate of leaveblocks for current assignment in the loop
641                 List<String> assigmentKeyList = new ArrayList<String>();
642                 assigmentKeyList.add(assignment.getAssignmentKey());
643 
644                 List<LeaveBlock> leaveBlocks =  LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(),
645                         timesheetDocument.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate(), timesheetDocument.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate(), assigmentKeyList);
646                 LeaveBlockAggregate leaveBlockAggregate = new LeaveBlockAggregate(leaveBlocks, timesheetDocument.getCalendarEntry());
647 
648                 //combine timeBlocks and leave blocks for selected assignment
649                 tkTimeBlockAggregate = TkTimeBlockAggregate.combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
650 
651                 Map<String, BigDecimal> flsaWeekTotal =  getAssignmentHoursToFlsaWeekMap(tkTimeBlockAggregate, timesheetDocument.getPrincipalId(), assignment.getAssignmentKey(), jobStartDate, userTimeZone);
652 
653                 for (Map.Entry<String, BigDecimal> entry : flsaWeekTotal.entrySet()) {
654                     if (standardHours.compareTo(entry.getValue()) > 0) {
655                         errors.add("Error: [" + assignment.getAssignmentDescription() + " expected " + standardHours + " hours for " + entry.getKey() + " only " + entry.getValue() + " hours were entered.]");
656                     }
657                 }
658             }
659 
660         }
661         return errors;
662     }
663 
664     private Map<String, BigDecimal> getAssignmentHoursToFlsaWeekMap(TkTimeBlockAggregate tkTimeBlockAggregate, String principalId, String assignmentKey, LocalDate jobStartDate, DateTimeZone userTimeZone) {
665 
666         Map<String, BigDecimal> hoursToFlsaWeekMap = new LinkedHashMap<String, BigDecimal>();
667         List<List<FlsaWeek>> flsaWeeks = tkTimeBlockAggregate.getFlsaWeeks(userTimeZone, principalId);
668 
669 
670         int weekCount = 1;
671         for (List<FlsaWeek> flsaWeekParts : flsaWeeks) {
672             boolean printWeek = true;
673             BigDecimal weekTotal = new BigDecimal(0.00);
674             FlsaWeek lastWeekPart = CollectionUtils.isNotEmpty(flsaWeekParts) ? flsaWeekParts.get(flsaWeekParts.size() - 1) : null;
675             for (FlsaWeek flsaWeekPart : flsaWeekParts) {
676 
677                 //if flsa week doesn't end during this pay period do not validate.
678                 if (flsaWeekPart == lastWeekPart) {
679                     Integer lastFlsaDayOfWeek = flsaWeekPart.getFlsaDays().get(flsaWeekPart.getFlsaDays().size() - 1).getFlsaDate().getDayOfWeek();
680 
681                     Integer flsaWeekEndDayOfWeek = TkConstants.FLSA_WEEK_END_DAY.get(tkTimeBlockAggregate.getPayCalendar().getFlsaBeginDay());
682 
683                     if (lastFlsaDayOfWeek.compareTo(flsaWeekEndDayOfWeek) != 0) {
684                         printWeek = false;
685                         weekCount++;
686                         continue;
687                     }
688                 }
689 
690                 //if flsa week starts before effective date of the job on the assignment do not validate.
691                 if (flsaWeekPart.getFlsaDays().get(0).getFlsaDate().toLocalDate().isBefore(jobStartDate) ) {
692                     printWeek = false;
693                     weekCount++;
694                     continue;
695                 }
696 
697                 for (FlsaDay flsaDay : flsaWeekPart.getFlsaDays()) {
698 
699                     for (TimeBlock timeBlock : flsaDay.getAppliedTimeBlocks()) {
700                         if (assignmentKey != null) {
701                             if (timeBlock.getAssignmentKey().compareTo(assignmentKey) == 0) {
702                                 weekTotal = weekTotal.add(timeBlock.getHours(), HrConstants.MATH_CONTEXT);
703                             } else {
704                                 weekTotal = weekTotal.add(new BigDecimal("0"), HrConstants.MATH_CONTEXT);
705                             }
706                         } else {
707                             weekTotal = weekTotal.add(timeBlock.getHours(), HrConstants.MATH_CONTEXT);
708                         }
709                     }
710                 }
711             }
712 
713             if (printWeek) {
714                 hoursToFlsaWeekMap.put("Week " + weekCount++, weekTotal);
715             }
716         }
717 
718         return hoursToFlsaWeekMap;
719     }
720 }