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.web;
17  
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Map.Entry;
24  import java.util.Set;
25  
26  import javax.servlet.http.HttpServletRequest;
27  import javax.servlet.http.HttpServletResponse;
28  
29  import org.apache.commons.collections.CollectionUtils;
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.commons.lang.time.DateUtils;
32  import org.apache.log4j.Logger;
33  import org.apache.struts.action.ActionForm;
34  import org.apache.struts.action.ActionForward;
35  import org.apache.struts.action.ActionMapping;
36  import org.apache.struts.action.ActionRedirect;
37  import org.joda.time.DateTime;
38  import org.joda.time.Interval;
39  import org.kuali.kpme.core.accrualcategory.rule.AccrualCategoryRule;
40  import org.kuali.kpme.core.calendar.Calendar;
41  import org.kuali.kpme.core.calendar.entry.CalendarEntry;
42  import org.kuali.kpme.core.earncode.EarnCode;
43  import org.kuali.kpme.core.principal.PrincipalHRAttributes;
44  import org.kuali.kpme.core.service.HrServiceLocator;
45  import org.kuali.kpme.core.util.HrConstants;
46  import org.kuali.kpme.core.util.HrContext;
47  import org.kuali.kpme.core.web.KPMEAction;
48  import org.kuali.kpme.tklm.leave.block.LeaveBlock;
49  import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
50  import org.kuali.kpme.tklm.time.clocklog.ClockLog;
51  import org.kuali.kpme.tklm.time.service.TkServiceLocator;
52  import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
53  import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
54  import org.kuali.rice.kew.api.document.DocumentStatus;
55  import org.kuali.rice.krad.exception.AuthorizationException;
56  import org.kuali.rice.krad.util.GlobalVariables;
57  import org.kuali.rice.krad.util.KRADConstants;
58  import org.kuali.rice.krad.util.ObjectUtils;
59  
60  public class TimesheetSubmitAction extends KPMEAction {
61  
62  	private static final Logger LOG = Logger.getLogger(TimesheetSubmitAction.class);
63      @Override
64      protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
65          TimesheetSubmitActionForm tsaf = (TimesheetSubmitActionForm)form;
66      	
67      	String principalId = GlobalVariables.getUserSession().getPrincipalId();
68      	String documentId = tsaf.getDocumentId();
69      	TimesheetDocument timesheetDocument = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
70          if (!HrServiceLocator.getHRPermissionService().canEditCalendarDocument(principalId, timesheetDocument)
71          		&& !StringUtils.equals(tsaf.getAction(), HrConstants.DOCUMENT_ACTIONS.REFRESH)) {
72              throw new AuthorizationException(principalId, "TimesheetSubmitAction", "");
73          }
74      }
75  
76      public ActionForward approveTimesheet(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
77          TimesheetSubmitActionForm tsaf = (TimesheetSubmitActionForm)form;
78          List<String> errorList = new ArrayList<String>();
79          TimesheetDocument document = TkServiceLocator.getTimesheetService().getTimesheetDocument(tsaf.getDocumentId());
80          
81          // Switched to grab the target (chain, resolution: target -> backdoor -> actual) user.
82          // Approvals still using backdoor > actual
83          if (StringUtils.equals(tsaf.getAction(), HrConstants.DOCUMENT_ACTIONS.ROUTE)) {
84              if (DocumentStatus.INITIATED.getCode().equals(document.getDocumentHeader().getDocumentStatus())
85                      || DocumentStatus.SAVED.getCode().equals(document.getDocumentHeader().getDocumentStatus())) {
86              	
87              	boolean nonExemptLE = LmServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(document.getPrincipalId(),
88              				HrConstants.FLSA_STATUS_NON_EXEMPT, true);
89              	if(nonExemptLE) {
90              		Map<String,Set<LeaveBlock>> eligibilities = LmServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(document.getCalendarEntry(), document.getPrincipalId());
91              		PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(document.getPrincipalId(), document.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate());
92  					Calendar cal = pha.getLeaveCalObj();
93  					if(cal == null) {
94  						//non exempt leave eligible employee without a leave calendar?
95  						LOG.error("Principal is without a leave calendar");
96  						GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, "principal.without.leavecal");
97  						return mapping.findForward("basic");
98  //						throw new RuntimeException("Principal is without a leave calendar");
99                      }
100     				List<LeaveBlock> eligibleTransfers = new ArrayList<LeaveBlock>();
101     				List<LeaveBlock> eligiblePayouts = new ArrayList<LeaveBlock>();
102             		Interval interval = new Interval(document.getCalendarEntry().getBeginPeriodDate().getTime(), document.getCalendarEntry().getEndPeriodDate().getTime());
103 
104 	        		for(Entry<String,Set<LeaveBlock>> entry : eligibilities.entrySet()) {
105 	        			
106 	            		for(LeaveBlock lb : entry.getValue()) {
107 	            			if(interval.contains(lb.getLeaveDate().getTime())) {
108 	            				//maxBalanceViolations should, if a violation exists, return a leave block with leave date either current date, or the end period date - 1 days.
109 		        				AccrualCategoryRule aRule = lb.getAccrualCategoryRule();
110 	
111 		            			if(ObjectUtils.isNotNull(aRule)
112 		            					&& !StringUtils.equals(aRule.getMaxBalanceActionFrequency(),HrConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND)) {
113 		            				if(StringUtils.equals(aRule.getMaxBalanceActionFrequency(),HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
114 		            					DateTime rollOverDate = HrServiceLocator.getLeavePlanService().getRolloverDayOfLeavePlan(pha.getLeavePlan(), document.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate());
115 		            					//the final calendar period of the leave plan should end within this time sheet 
116 		            					if(interval.contains(rollOverDate.minusDays(1).getMillis())) {
117 		            						//only leave blocks belonging to the calendar entry being submitted may reach this point
118 		            						//if the infraction occurs before the relative end date of the leave plan year, then action will be executed.
119 			            					if(lb.getLeaveDate().before(rollOverDate.toDate())) {
120 						            			if(StringUtils.equals(aRule.getActionAtMaxBalance(),HrConstants.ACTION_AT_MAX_BALANCE.PAYOUT)) {
121 						            				eligiblePayouts.add(lb);
122 						            			}
123 						            			else if(StringUtils.equals(aRule.getActionAtMaxBalance(), HrConstants.ACTION_AT_MAX_BALANCE.TRANSFER)
124 						            					|| StringUtils.equals(aRule.getActionAtMaxBalance(), HrConstants.ACTION_AT_MAX_BALANCE.LOSE)) {
125 						            				eligibleTransfers.add(lb);
126 						            			}
127 			            					}
128 		            					}
129 		            				}
130 		            				if(StringUtils.equals(aRule.getMaxBalanceActionFrequency(),HrConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE)) {
131 		            					//a leave period should end within the time period.
132 		            					CalendarEntry leaveEntry = HrServiceLocator.getCalendarEntryService().getCurrentCalendarEntryByCalendarId(cal.getHrCalendarId(), lb.getLeaveLocalDate().toDateTimeAtStartOfDay());
133 		            					if(ObjectUtils.isNotNull(leaveEntry)) {
134 		            						//only leave blocks belonging to the calendar entry being submitted may reach this point.
135 		            						//if the infraction occurs before the end of the leave calendar entry, then action will be executed.
136 			            					if(interval.contains(DateUtils.addDays(leaveEntry.getEndPeriodDate(),-1).getTime())) {
137 
138 						            			if(StringUtils.equals(aRule.getActionAtMaxBalance(),HrConstants.ACTION_AT_MAX_BALANCE.PAYOUT)) {
139 						            				eligiblePayouts.add(lb);
140 						            			}
141 						            			else if(StringUtils.equals(aRule.getActionAtMaxBalance(), HrConstants.ACTION_AT_MAX_BALANCE.TRANSFER)
142 						            					|| StringUtils.equals(aRule.getActionAtMaxBalance(), HrConstants.ACTION_AT_MAX_BALANCE.LOSE)) {
143 						            				eligibleTransfers.add(lb);
144 						            			}
145 			            					}
146 		            					}
147 		            				}
148 		            			}
149 	            			}
150 	            		}
151 	        		}
152             		ActionRedirect transferRedirect = new ActionRedirect();
153             		ActionRedirect payoutRedirect = new ActionRedirect();
154             		if(!eligibleTransfers.isEmpty()) {
155                 		transferRedirect.setPath("/BalanceTransfer.do?"+request.getQueryString());
156                 		request.getSession().setAttribute("eligibilities", eligibleTransfers);
157                 		return transferRedirect;
158             		}
159             		if(!eligiblePayouts.isEmpty()) {
160                 		payoutRedirect.setPath("/LeavePayout.do?"+request.getQueryString());
161                 		request.getSession().setAttribute("eligibilities", eligiblePayouts);
162                 		return payoutRedirect;
163             		}
164             	}
165             	ClockLog clockLog = TkServiceLocator.getClockLogService().getLastClockLog(HrContext.getTargetPrincipalId());
166                 if(clockLog!=null){
167                 	String lastClockLogAction = clockLog.getClockAction();
168                 	if(lastClockLogAction.equals("CI") || lastClockLogAction.equals("LI")){
169                 		errorList.add("Please Clock Out/Lunch Out to submit timesheet for approval");
170                 	}else{
171                 		TkServiceLocator.getTimesheetService().routeTimesheet(HrContext.getTargetPrincipalId(), document);
172                 	}
173                 }else{ 
174                 	TkServiceLocator.getTimesheetService().routeTimesheet(HrContext.getTargetPrincipalId(), document);
175                 }
176             }
177         } else if (StringUtils.equals(tsaf.getAction(), HrConstants.DOCUMENT_ACTIONS.APPROVE)) {
178         	if(TkServiceLocator.getTimesheetService().isReadyToApprove(document)) {
179 	            if (document.getDocumentHeader().getDocumentStatus().equals(DocumentStatus.ENROUTE.getCode())) {
180 	                if(!isOverlapTimeBlocks(document)) {
181 	            		TkServiceLocator.getTimesheetService().approveTimesheet(HrContext.getPrincipalId(), document);
182 	            	} else {
183 	            		errorList.add("Timesheet "+document.getDocumentId()+ " could not be approved as it contains overlapping time blocks");
184 	            	}
185 	            }
186         	} else {
187         		//ERROR!!!
188         	}
189         } else if (StringUtils.equals(tsaf.getAction(), HrConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
190             if (document.getDocumentHeader().getDocumentStatus().equals(DocumentStatus.ENROUTE.getCode())) {
191            		TkServiceLocator.getTimesheetService().disapproveTimesheet(HrContext.getPrincipalId(), document);
192             }
193         }
194         
195         TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(document, document.getDocEndDate());
196         ActionRedirect rd = new ActionRedirect(mapping.findForward("timesheetRedirect"));
197         rd.addParameter("documentId", tsaf.getDocumentId());
198         if(CollectionUtils.isNotEmpty(errorList)) {
199         	rd.addParameter("actionMessages", errorList);
200         }
201         return rd;
202     }
203 
204     public ActionForward approveApprovalTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
205     	TimesheetSubmitActionForm tsaf = (TimesheetSubmitActionForm)form;
206     	List<String> errorList = new ArrayList<String>();
207         TimesheetDocument document = TkServiceLocator.getTimesheetService().getTimesheetDocument(tsaf.getDocumentId());
208 
209         // Switched to grab the target (chain, resolution: target -> backdoor -> actual) user.
210         // Approvals still using backdoor > actual
211         if (StringUtils.equals(tsaf.getAction(), HrConstants.DOCUMENT_ACTIONS.ROUTE)) {
212             if (document.getDocumentHeader().getDocumentStatus().equals(DocumentStatus.INITIATED.getCode())) {
213                 TkServiceLocator.getTimesheetService().routeTimesheet(HrContext.getTargetPrincipalId(), document);
214             }
215         } else if (StringUtils.equals(tsaf.getAction(), HrConstants.DOCUMENT_ACTIONS.APPROVE)) {
216         	if(TkServiceLocator.getTimesheetService().isReadyToApprove(document)) {
217 	            if (document.getDocumentHeader().getDocumentStatus().equals(DocumentStatus.ENROUTE.getCode())) {
218 	            	if(!isOverlapTimeBlocks(document)) {
219 	            		TkServiceLocator.getTimesheetService().approveTimesheet(HrContext.getPrincipalId(), document);
220 	            	} else {
221 	            		errorList.add("Timesheet "+document.getDocumentId()+ " could not be approved as it contains overlapping time blocks");
222 	            	}
223 	            }
224         	} else {
225         		//ERROR!!!
226         	}
227         } else if (StringUtils.equals(tsaf.getAction(), HrConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
228             if (document.getDocumentHeader().getDocumentStatus().equals(DocumentStatus.ENROUTE.getCode())) {
229                 TkServiceLocator.getTimesheetService().disapproveTimesheet(HrContext.getPrincipalId(), document);
230             }
231         }
232         HrContext.clearTargetUser();
233         ActionRedirect redirect =new ActionRedirect(mapping.findForward("approverRedirect"));
234         if(tsaf.getSelectedPayCalendarGroup() != null && StringUtils.isNotEmpty(tsaf.getSelectedPayCalendarGroup())) {
235         	redirect.addParameter("selectedPayCalendarGroup", tsaf.getSelectedPayCalendarGroup());
236         }
237         if(tsaf.getSelectedDept() != null && StringUtils.isNotEmpty(tsaf.getSelectedDept())) {
238         	redirect.addParameter("selectedDept", tsaf.getSelectedDept());
239         }
240         if(tsaf.getSelectedWorkArea() != null && StringUtils.isNotEmpty(tsaf.getSelectedWorkArea())) {
241         	redirect.addParameter("selectedWorkArea", tsaf.getSelectedWorkArea());
242         }
243         if(tsaf.getSelectedPayPeriod() != null && StringUtils.isNotEmpty(tsaf.getSelectedPayPeriod())) {
244         	redirect.addParameter("selectedPayPeriod", tsaf.getSelectedPayPeriod());
245         }
246         if(CollectionUtils.isNotEmpty(errorList)) {
247         	redirect.addParameter("errorMessageList", errorList);
248         }
249         return redirect;
250     }
251     
252     private boolean isOverlapTimeBlocks(TimesheetDocument tDoc) {
253     	boolean isOverLap = false;
254         Map<String, String> earnCodeTypeMap = new HashMap<String, String>();
255     	List<TimeBlock> timeBlocks = tDoc.getTimeBlocks();
256         for(TimeBlock tb1 : timeBlocks) {
257         	String earnCode = tb1.getEarnCode();
258         	String earnCodeType = null;
259         	if(earnCodeTypeMap.containsKey(earnCode)) {
260         		earnCodeType = earnCodeTypeMap.get(earnCode);
261         	} else {
262        	 		EarnCode earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(earnCode, tDoc.getAsOfDate());
263        	 		if(earnCodeObj != null) {
264        	 			earnCodeType = earnCodeObj.getEarnCodeType();
265        	 		}
266         	}
267        	 	if(earnCodeType != null && HrConstants.EARN_CODE_TIME.equals(earnCodeType)) {
268             	DateTime beginDate = tb1.getBeginDateTime();
269             	for(TimeBlock tb2 : timeBlocks){
270             		if(!tb1.getTkTimeBlockId().equals(tb2.getTkTimeBlockId())) {
271 	            		earnCode = tb2.getEarnCode();
272 	            		earnCodeType = null;
273 	            		if(earnCodeTypeMap.containsKey(earnCode)) {
274 	                		earnCodeType = earnCodeTypeMap.get(earnCode);
275 	                	} else {
276 	   	        	 		EarnCode earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(earnCode, tDoc.getAsOfDate());
277 	   	        	 		if(earnCodeObj != null) {
278 	   	        	 			earnCodeType = earnCodeObj.getEarnCodeType();
279 	   	        	 		}
280 	                	}
281 	            		if(earnCodeType != null && HrConstants.EARN_CODE_TIME.equals(earnCodeType)) {
282 	                		Interval blockInterval = new Interval(tb2.getBeginDateTime(), tb2.getEndDateTime());
283 	                		if(blockInterval.contains(beginDate.getMillis())) {
284 	   	        			    isOverLap= true;	
285 	   	        			    break;
286 	                		}
287 	            		}
288 	            	}
289             	}
290             	if(isOverLap){
291             		break;
292             	}
293        	 	}
294         }
295         return isOverLap;
296     }
297 }