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