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.detail.web;
17  
18  import java.math.BigDecimal;
19  import java.sql.Date;
20  import java.sql.Timestamp;
21  import java.text.SimpleDateFormat;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  import java.util.Properties;
31  import java.util.Set;
32  
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  
36  import org.apache.commons.collections.MapUtils;
37  import org.apache.commons.lang.StringUtils;
38  import org.apache.commons.lang.SystemUtils;
39  import org.apache.struts.action.ActionForm;
40  import org.apache.struts.action.ActionForward;
41  import org.apache.struts.action.ActionMapping;
42  import org.joda.time.DateTime;
43  import org.joda.time.Interval;
44  import org.joda.time.LocalDateTime;
45  import org.kuali.hr.lm.LMConstants;
46  import org.kuali.hr.lm.accrual.AccrualCategory;
47  import org.kuali.hr.lm.accrual.AccrualCategoryRule;
48  import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
49  import org.kuali.hr.lm.balancetransfer.validation.BalanceTransferValidationUtils;
50  import org.kuali.hr.lm.leaveblock.LeaveBlock;
51  import org.kuali.hr.lm.leavecalendar.validation.LeaveCalendarValidationUtil;
52  import org.kuali.hr.lm.util.LeaveBlockAggregate;
53  import org.kuali.hr.time.assignment.Assignment;
54  import org.kuali.hr.time.calendar.Calendar;
55  import org.kuali.hr.time.calendar.CalendarEntries;
56  import org.kuali.hr.time.calendar.TkCalendar;
57  import org.kuali.hr.time.earncode.EarnCode;
58  import org.kuali.hr.time.principal.PrincipalHRAttributes;
59  import org.kuali.hr.time.roles.TkUserRoles;
60  import org.kuali.hr.time.roles.UserRoles;
61  import org.kuali.hr.time.service.base.TkServiceLocator;
62  import org.kuali.hr.time.timeblock.TimeBlock;
63  import org.kuali.hr.time.timeblock.TimeBlockHistory;
64  import org.kuali.hr.time.timesheet.TimesheetDocument;
65  import org.kuali.hr.time.timesheet.web.TimesheetAction;
66  import org.kuali.hr.time.timesheet.web.TimesheetActionForm;
67  import org.kuali.hr.time.timesummary.AssignmentColumn;
68  import org.kuali.hr.time.timesummary.AssignmentRow;
69  import org.kuali.hr.time.timesummary.EarnCodeSection;
70  import org.kuali.hr.time.timesummary.EarnGroupSection;
71  import org.kuali.hr.time.timesummary.TimeSummary;
72  import org.kuali.hr.time.util.TKContext;
73  import org.kuali.hr.time.util.TKUser;
74  import org.kuali.hr.time.util.TKUtils;
75  import org.kuali.hr.time.util.TkConstants;
76  import org.kuali.hr.time.util.TkTimeBlockAggregate;
77  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
78  import org.kuali.rice.kew.api.document.DocumentStatus;
79  import org.kuali.rice.kew.service.KEWServiceLocator;
80  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
81  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
82  import org.kuali.rice.krad.exception.AuthorizationException;
83  import org.kuali.rice.krad.util.GlobalVariables;
84  import org.kuali.rice.krad.util.ObjectUtils;
85  import org.kuali.rice.krad.util.UrlFactory;
86  
87  public class TimeDetailAction extends TimesheetAction {
88  
89      @Override
90      protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
91          super.checkTKAuthorization(form, methodToCall); // Checks for read access first.
92          UserRoles roles = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId());
93          TimesheetDocument doc = TKContext.getCurrentTimesheetDocument();
94  
95          // Check for write access to Timeblock.
96          if (StringUtils.equals(methodToCall, "addTimeBlock") || StringUtils.equals(methodToCall, "deleteTimeBlock") || StringUtils.equals(methodToCall, "updateTimeBlock")) {
97              if (!roles.isDocumentWritable(doc)) {
98                  throw new AuthorizationException(roles.getPrincipalId(), "TimeDetailAction", "");
99              }
100         }
101     }
102 
103     @Override
104     public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
105         ActionForward forward = super.execute(mapping, form, request, response);
106         if (forward.getRedirect()) {
107             return forward;
108         }
109         if (TKContext.getCurrentTimesheetDocument() == null) {
110             return forward;
111         }
112         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
113         tdaf.setAssignmentDescriptions(TkServiceLocator.getAssignmentService().getAssignmentDescriptions(TKContext.getCurrentTimesheetDocument(), false));
114 
115         // Handle User preference / timezone information (pushed up from TkCalendar to avoid duplication)
116         // Set calendar
117         CalendarEntries payCalendarEntry = tdaf.getPayCalendarDates();
118         Calendar payCalendar = TkServiceLocator.getCalendarService().getCalendar(payCalendarEntry != null ? payCalendarEntry.getHrCalendarId() : null);
119         
120         //List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(Long.parseLong(tdaf.getTimesheetDocument().getDocumentHeader().getTimesheetDocumentId()));
121         List<TimeBlock> timeBlocks = TKContext.getCurrentTimesheetDocument().getTimeBlocks();
122         // get leave blocks
123         List<Assignment> timeAssignments = TKContext.getCurrentTimesheetDocument().getAssignments();
124         List<String> tAssignmentKeys = new ArrayList<String>();
125         for(Assignment assign : timeAssignments) {
126         	tAssignmentKeys.add(assign.getAssignmentKey());
127         }
128        
129         Date endDate = payCalendarEntry.getEndPeriodDate();
130         LocalDateTime tempLocalDate = payCalendarEntry.getEndLocalDateTime();
131         // if the end date of the calendar entry is the beginning time of a day, use the previous day as the end date to retrieve blocks
132         if(tempLocalDate.getHourOfDay() == 0 && tempLocalDate.getMinuteOfHour() == 0 && tempLocalDate.getMillisOfSecond() == 0) {
133         	endDate = new java.sql.Date(tempLocalDate.minusDays(1).toDate().getTime());
134         }
135         
136         List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(TKContext.getCurrentTimesheetDocument().getPrincipalId(), 
137         					payCalendarEntry.getBeginPeriodDate(), endDate, tAssignmentKeys);
138         List<LeaveBlock> balanceTransferLeaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(TKContext.getCurrentTimesheetDocument().getPrincipalId(),
139         		 payCalendarEntry.getBeginPeriodDate(), payCalendarEntry.getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
140         
141 //        List<String> warnings = tdaf.getWarnings();
142 /*        List<String> infoMessages = tdaf.getInfoMessages();
143         List<String> warningMessages = tdaf.getWarningMessages();
144         List<String> actionMessages = tdaf.getActionMessages();*/
145         
146         Map<String, Set<String>> allMessages = LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(balanceTransferLeaveBlocks);
147         Map<String, Set<String>> transactionalMessages = LeaveCalendarValidationUtil.validatePendingTransactions(TKContext.getTargetPrincipalId(),
148         		payCalendarEntry.getBeginPeriodDate(), payCalendarEntry.getEndPeriodDate());
149         
150         List<String> warnings = new ArrayList<String>();
151         //placing the following "validation" further down in this method will overwrite messages added prior to this call.
152         //allMessages.putAll(LeaveCalendarValidationUtil.validatePendingTransactions(viewPrincipal, payCalendarEntry.getBeginPeriodDate(), payCalendarEntry.getEndPeriodDate()));
153         
154         // add warning messages based on max carry over balances for each accrual category for non-exempt leave users
155         String viewPrincipal = TKContext.getTargetPrincipalId();
156         List<BalanceTransfer> losses = new ArrayList<BalanceTransfer>();
157         if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(viewPrincipal, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
158             PrincipalHRAttributes principalCalendar = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(viewPrincipal, payCalendarEntry.getEndPeriodDate());
159 
160 	        Interval calendarInterval = new Interval(payCalendarEntry.getBeginPeriodDate().getTime(), payCalendarEntry.getEndPeriodDate().getTime());
161 	        Map<String,Set<LeaveBlock>> maxBalInfractions = new HashMap<String,Set<LeaveBlock>>();
162 	        
163             if(ObjectUtils.isNotNull(principalCalendar)) {
164     	        maxBalInfractions = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(payCalendarEntry, viewPrincipal);
165     	        
166     	        for(Entry<String,Set<LeaveBlock>> entry : maxBalInfractions.entrySet()) {
167     	        	for(LeaveBlock lb : entry.getValue()) {
168         				if(calendarInterval.contains(lb.getLeaveDate().getTime())) {
169 	    	        		AccrualCategory accrualCat = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(lb.getAccrualCategory(), lb.getLeaveDate());
170 				        	AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(lb.getAccrualCategoryRuleId());
171 				        	if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
172 				        		DateTime aDate = null;
173 				        		if(StringUtils.equals(aRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
174 				        			aDate = TkServiceLocator.getLeavePlanService().getRolloverDayOfLeavePlan(principalCalendar.getLeavePlan(), lb.getLeaveDate());
175 				        		}
176 				        		else {
177 					        		Calendar cal = TkServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(viewPrincipal, lb.getLeaveDate(), true);
178 					        		CalendarEntries leaveEntry = TkServiceLocator.getCalendarEntriesService().getCurrentCalendarEntriesByCalendarId(cal.getHrCalendarId(), lb.getLeaveDate());
179 					        		aDate = new DateTime(leaveEntry.getEndPeriodDate());
180 				        		}
181 				        		aDate = aDate.minusDays(1);
182 				        		if(calendarInterval.contains(aDate.getMillis()) && aDate.toDate().compareTo(payCalendarEntry.getEndPeriodDate()) <= 0) {
183 					        		//may want to calculate summary for all rows, displayable or not, and determine displayability via tags.
184 					    			AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(aRule.getLmAccrualCategoryId());
185 					    			BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(viewPrincipal, accrualCategory, lb.getLeaveDate());
186 						        	
187 						        	BalanceTransfer loseTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(viewPrincipal, lb.getAccrualCategoryRuleId(), accruedBalance, lb.getLeaveDate());
188 						        	boolean valid = BalanceTransferValidationUtils.validateTransfer(loseTransfer);
189 						        	if(valid)
190 						        		losses.add(loseTransfer);
191 				        		}
192 				        	}
193 	
194 	
195 	    		        	// accrual categories within the leave plan that are hidden from the leave summary WILL appear.
196 	        				String message = "You have exceeded the maximum balance limit for '" + accrualCat.getAccrualCategory() + "' as of " + lb.getLeaveDate() + ". "+
197 	                    			"Depending upon the accrual category rules, leave over this limit may be forfeited.";
198                             //  leave blocks are sorted in getMaxBalanceViolations() method, so we just take the one with the earliest leave date for an accrual category.
199                             if(!StringUtils.contains(allMessages.get("warningMessages").toString(),"You have exceeded the maximum balance limit for '"+accrualCat.getAccrualCategory())) {
200 	                            allMessages.get("warningMessages").add(message);
201 	        				}
202         				}
203     	        	}
204     	        }
205         	}
206             tdaf.setForfeitures(losses);
207             
208         	if (principalCalendar != null) {
209 	        	Calendar calendar = TkServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(viewPrincipal, tdaf.getEndPeriodDateTime(), true);
210 					
211 				if (calendar != null) {
212 					List<CalendarEntries> leaveCalendarEntries = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesEndingBetweenBeginAndEndDate(calendar.getHrCalendarId(), tdaf.getBeginPeriodDateTime(), tdaf.getEndPeriodDateTime());
213 					
214 					List<AccrualCategory> accrualCategories = TkServiceLocator.getAccrualCategoryService().getActiveLeaveAccrualCategoriesForLeavePlan(principalCalendar.getLeavePlan(), new java.sql.Date(tdaf.getEndPeriodDateTime().getTime()));
215 					for (AccrualCategory accrualCategory : accrualCategories) {
216 						if (TkServiceLocator.getAccrualCategoryMaxCarryOverService().exceedsAccrualCategoryMaxCarryOver(accrualCategory.getAccrualCategory(), viewPrincipal, leaveCalendarEntries, tdaf.getEndPeriodDateTime())) {
217 							String message = "Your pending leave balance is greater than the annual max carry over for accrual category '" + accrualCategory.getAccrualCategory() + "' and upon approval, the excess balance will be lost.";
218 							if (!warnings.contains(message)) {
219 								warnings.add(message);
220 							}
221 						}
222 					}
223 				}
224 			}
225         }
226 /*		warnings.addAll(allMessages.get("infoMessages"));
227 		warnings.addAll(allMessages.get("actionMessages"));
228 		warnings.addAll(allMessages.get("warningMessages"));*/
229         allMessages.get("warningMessages").addAll(warnings);
230         
231         List<String> infoMessages = tdaf.getInfoMessages();
232         infoMessages.addAll(allMessages.get("infoMessages"));
233         infoMessages.addAll(transactionalMessages.get("infoMessages"));
234 
235         List<String> warningMessages = tdaf.getWarningMessages();
236         warningMessages.addAll(allMessages.get("warningMessages"));
237         warningMessages.addAll(transactionalMessages.get("warningMessages"));
238 
239         List<String> actionMessages = tdaf.getActionMessages();
240         actionMessages.addAll(allMessages.get("actionMessages"));
241         actionMessages.addAll(transactionalMessages.get("actionMessages"));
242 
243         tdaf.setInfoMessages(infoMessages);
244         tdaf.setWarningMessages(warningMessages);
245         tdaf.setActionMessages(actionMessages);
246 
247         this.assignStypeClassMapForTimeSummary(tdaf,timeBlocks, leaveBlocks);
248         
249         List<Interval> intervals = TKUtils.getFullWeekDaySpanForCalendarEntry(payCalendarEntry);
250         LeaveBlockAggregate lbAggregate = new LeaveBlockAggregate(leaveBlocks, payCalendarEntry, intervals);
251         TkTimeBlockAggregate tbAggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry, payCalendar, true,intervals);
252         // use both time aggregate and leave aggregate to populate the calendar
253         TkCalendar cal = TkCalendar.getCalendar(tbAggregate, lbAggregate);
254         cal.assignAssignmentStyle(tdaf.getAssignStyleClassMap());
255         tdaf.setTkCalendar(cal);
256      
257         this.populateCalendarAndPayPeriodLists(request, tdaf);
258 
259         tdaf.setTimeBlockString(ActionFormUtils.getTimeBlocksJson(tbAggregate.getFlattenedTimeBlockList()));
260         tdaf.setLeaveBlockString(ActionFormUtils.getLeaveBlocksJson(lbAggregate.getFlattenedLeaveBlockList()));
261 
262         tdaf.setOvertimeEarnCodes(TkServiceLocator.getEarnCodeService().getOvertimeEarnCodesStrs(TKContext.getCurrentTimesheetDocument().getAsOfDate()));
263 
264         if (StringUtils.equals(TKContext.getCurrentTimesheetDocument().getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())) {
265         	tdaf.setWorkingOnItsOwn("true");
266         }
267         
268         tdaf.setDocEditable("false");
269         if (TKUser.isSystemAdmin()) {
270             tdaf.setDocEditable("true");
271         } else {
272         	String documentStatus = TKContext.getCurrentTimesheetDocument().getDocumentHeader().getDocumentStatus();
273             boolean docFinal = DocumentStatus.FINAL.getCode().equals(documentStatus)
274                     || DocumentStatus.CANCELED.getCode().equals(documentStatus)
275                     || DocumentStatus.DISAPPROVED.getCode().equals(documentStatus);
276             if (!docFinal) {
277             	if(StringUtils.equals(TKContext.getCurrentTimesheetDocument().getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())
278 	            		|| TKUser.isSystemAdmin()
279 	            		|| TKUser.isLocationAdmin()
280 	            		|| TKUser.isDepartmentAdmin()
281 	            		|| TKUser.isReviewer()
282 	            		|| TKUser.isApprover()) {
283                     tdaf.setDocEditable("true");
284                 }
285             	
286 	            //if the timesheet has been approved by at least one of the approvers, the employee should not be able to edit it
287 	            if (StringUtils.equals(TKContext.getCurrentTimesheetDocument().getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())
288 	            		&& TKContext.getCurrentTimesheetDocument().getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE)) {
289 		        	Collection actions = KEWServiceLocator.getActionTakenService().findByDocIdAndAction(TKContext.getCurrentTimesheetDocument().getDocumentHeader().getDocumentId(), TkConstants.DOCUMENT_ACTIONS.APPROVE);
290 	        		if(!actions.isEmpty()) {
291 	        			tdaf.setDocEditable("false");  
292 	        		}
293 		        }
294             }
295         }
296 
297         return forward;
298     }
299 
300     // use lists of time blocks and leave blocks to build the style class map and assign css class to associated summary rows
301 	private void assignStypeClassMapForTimeSummary(TimeDetailActionForm tdaf, List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks) throws Exception {
302 		TimeSummary ts = TkServiceLocator.getTimeSummaryService().getTimeSummary(TKContext.getCurrentTimesheetDocument());
303         tdaf.setAssignStyleClassMap(ActionFormUtils.buildAssignmentStyleClassMap(timeBlocks, leaveBlocks));
304         Map<String, String> aMap = tdaf.getAssignStyleClassMap();
305         // set css classes for each assignment row
306         for (EarnGroupSection earnGroupSection : ts.getSections()) {
307             for (EarnCodeSection section : earnGroupSection.getEarnCodeSections()) {
308                 for (AssignmentRow assignRow : section.getAssignmentsRows()) {
309                 	String assignmentCssStyle = MapUtils.getString(aMap, assignRow.getAssignmentKey());
310                 	assignRow.setCssClass(assignmentCssStyle);
311                 	for (AssignmentColumn assignmentColumn : assignRow.getAssignmentColumns()) {
312                 		assignmentColumn.setCssClass(assignmentCssStyle);
313                 	}
314                 }
315             }
316 
317         }
318         tdaf.setTimeSummary(ts);
319       //  ActionFormUtils.validateHourLimit(tdaf);
320         ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
321         ActionFormUtils.addUnapprovedIPWarningFromClockLog(tdaf);
322 	}
323 
324 	private void populateCalendarAndPayPeriodLists(HttpServletRequest request, TimeDetailActionForm tdaf) {
325 		String viewPrincipal = TKContext.getTargetPrincipalId();
326 		List<TimesheetDocumentHeader> documentHeaders = (List<TimesheetDocumentHeader>) TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeadersForPrincipalId(TKContext.getTargetPrincipalId());
327         SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
328         if(tdaf.getCalendarYears().isEmpty()) {
329         	// get calendar year drop down list contents
330 	        Set<String> yearSet = new HashSet<String>();
331 	        
332 	        for(TimesheetDocumentHeader tdh : documentHeaders) {
333 	        	yearSet.add(sdf.format(tdh.getBeginDate()));
334 	        }
335 	        List<String> yearList = new ArrayList<String>(yearSet);
336 	        Collections.sort(yearList);
337 	        Collections.reverse(yearList);	// newest on top
338 	        tdaf.setCalendarYears(yearList);
339         }
340         // if selected calendar year is passed in
341         if(request.getParameter("selectedCY")!= null) {
342         	tdaf.setSelectedCalendarYear(request.getParameter("selectedCY").toString());
343         }
344         // if there is no selected calendr year, use the year of current pay calendar entry
345         if(StringUtils.isEmpty(tdaf.getSelectedCalendarYear())) {
346         	tdaf.setSelectedCalendarYear(sdf.format(tdaf.getPayCalendarDates().getBeginPeriodDate()));
347         }
348         if(tdaf.getPayPeriodsMap().isEmpty()) {
349 	        List<CalendarEntries> payPeriodList = new ArrayList<CalendarEntries>();
350 	        for(TimesheetDocumentHeader tdh : documentHeaders) {
351 	        	if(sdf.format(tdh.getBeginDate()).equals(tdaf.getSelectedCalendarYear())) {
352                     CalendarEntries pe = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
353                     //CalendarEntries pe = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(tdh.getBeginDate(), tdh.getEndDate());
354 	        		payPeriodList.add(pe);
355 	        	}
356 	        }
357 	        tdaf.setPayPeriodsMap(ActionFormUtils.getPayPeriodsMap(payPeriodList, viewPrincipal));
358         }
359         if(request.getParameter("selectedPP")!= null) {
360         	tdaf.setSelectedPayPeriod(request.getParameter("selectedPP").toString());
361         }
362         if(StringUtils.isEmpty(tdaf.getSelectedPayPeriod())) {
363         	tdaf.setSelectedPayPeriod(tdaf.getPayCalendarDates().getHrCalendarEntriesId());
364         }
365 	}
366 
367 
368     /**
369      * This method involves creating an object-copy of every TimeBlock on the
370      * time sheet for overtime re-calculation.
371      *
372      * @throws Exception
373      */
374     public ActionForward deleteTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
375         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
376         
377 		String principalId = TKContext.getPrincipalId();
378 		String targetPrincipalId = TKContext.getTargetPrincipalId();
379 		String documentId = tdaf.getDocumentId();
380 		
381         //Grab timeblock to be deleted from form
382         List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
383         TimeBlock deletedTimeBlock = null;
384         for (TimeBlock tb : timeBlocks) {
385             if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
386                 deletedTimeBlock = tb;
387                 break;
388             }
389         }
390         if (deletedTimeBlock == null) {
391             return mapping.findForward("basic");
392         }
393         //Remove from the list of timeblocks
394         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(tdaf.getTimesheetDocument().getTimeBlocks().size());
395         for (TimeBlock b : tdaf.getTimesheetDocument().getTimeBlocks()) {
396             referenceTimeBlocks.add(b.copy());
397         }
398 
399         // simple pointer, for clarity
400         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
401         newTimeBlocks.remove(deletedTimeBlock);
402 
403         //Delete timeblock
404         TkServiceLocator.getTimeBlockService().deleteTimeBlock(deletedTimeBlock);
405         // Add a row to the history table
406         TimeBlockHistory tbh = new TimeBlockHistory(deletedTimeBlock);
407         tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
408         TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
409         
410         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
411         List<String> assignmentKeys = new ArrayList<String>();
412         for (Assignment assignment : assignments) {
413         	assignmentKeys.add(assignment.getAssignmentKey());
414         }
415         List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
416 
417         //reset time block
418         TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
419         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getPayCalendarDates(), tdaf.getTimesheetDocument(), TKContext.getPrincipalId());
420         TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, TKContext.getPrincipalId());
421         
422         generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
423 
424         return mapping.findForward("basic");
425     }
426 
427     /**
428      * This method involves creating an object-copy of every TimeBlock on the
429      * time sheet for overtime re-calculation.
430      * Based on the form's timeBlockId or leaveBlockId, existing Time/Leave blocks will be deleted and new ones created
431      *
432      * @throws Exception
433      */
434     public ActionForward addTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
435         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
436         
437 		String principalId = TKContext.getPrincipalId();
438 		String targetPrincipalId = TKContext.getTargetPrincipalId();
439 		String documentId = tdaf.getDocumentId();
440         
441         if(StringUtils.isNotEmpty(tdaf.getTkTimeBlockId())) {
442         	// the user is changing an existing time block, so need to delete this time block
443         	this.removeOldTimeBlock(tdaf);
444         } else if(StringUtils.isNotEmpty(tdaf.getLmLeaveBlockId())) {
445         	// the user is changing an existing leave block, so need to delete this leave block
446         	this.removeOldLeaveBlock(tdaf.getLmLeaveBlockId());
447         }
448         if(StringUtils.isNotEmpty(tdaf.getSelectedEarnCode())) {
449         	EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument().getAsOfDate());
450         	if(ec != null && (ec.getLeavePlan() != null || ec.getEligibleForAccrual().equals("N"))) {	// leave blocks changes
451         		this.changeLeaveBlocks(tdaf);
452         	} else {	// time blocks changes
453         		this.changeTimeBlocks(tdaf);
454         	}
455         }
456         
457        // ActionFormUtils.validateHourLimit(tdaf);
458         ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
459         
460         generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
461 
462         return mapping.findForward("basic");
463     }
464     
465     private void removeOldTimeBlock(TimeDetailActionForm tdaf) {
466 	  if (tdaf.getTkTimeBlockId() != null) {
467 	      TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
468 	      if (tb != null) {
469 	          TimeBlockHistory tbh = new TimeBlockHistory(tb);
470 	          TkServiceLocator.getTimeBlockService().deleteTimeBlock(tb);
471 	
472 	          // mark the original timeblock as deleted in the history table
473 			  tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
474 			  TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
475 	
476 			  // delete the timeblock from the memory
477 	          tdaf.getTimesheetDocument().getTimeBlocks().remove(tb);
478 	      }
479 	  }
480     }
481     
482     private void removeOldLeaveBlock(String lbId) {
483   	  if (lbId != null) {
484   	      LeaveBlock lb = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lbId);
485   	      if (lb != null) {
486   	          TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(lbId, TKContext.getPrincipalId());
487   	      }
488   	  }
489     }
490     
491     // add/update leave blocks 
492 	private void changeLeaveBlocks(TimeDetailActionForm tdaf) {
493 		DateTime beginDate = null;
494 		DateTime endDate = null;
495 		
496 		if(tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
497 			beginDate = new DateTime(TKUtils.convertDateStringToTimestamp(tdaf.getStartDate(), tdaf.getStartTime()));
498 			endDate = new DateTime(TKUtils.convertDateStringToTimestamp(tdaf.getEndDate(), tdaf.getEndTime()));
499 		} else {
500 			beginDate = new DateTime(TKUtils.convertDateStringToTimestamp(tdaf.getStartDate()));
501 			endDate = new DateTime(TKUtils.convertDateStringToTimestamp(tdaf.getEndDate()));
502 		}
503 		
504 		String selectedEarnCode = tdaf.getSelectedEarnCode();
505 		BigDecimal leaveAmount = tdaf.getLeaveAmount();
506 		
507 		String desc = "";	// there's no description field in time calendar pop window
508 		String spanningWeeks = tdaf.getSpanningWeeks();
509 		Assignment currentAssignment = TkServiceLocator.getAssignmentService().getAssignment(tdaf.getTimesheetDocument(), tdaf.getSelectedAssignment());
510 		TkServiceLocator.getLeaveBlockService().addLeaveBlocks(beginDate, endDate, tdaf.getPayCalendarDates(), selectedEarnCode, leaveAmount, desc, currentAssignment, 
511 				spanningWeeks, LMConstants.LEAVE_BLOCK_TYPE.TIME_CALENDAR, TKContext.getTargetPrincipalId());
512 		
513         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
514         List<String> assignmentKeys = new ArrayList<String>();
515         for (Assignment assignment : assignments) {
516         	assignmentKeys.add(assignment.getAssignmentKey());
517         }
518         List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(TKContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
519 
520 		// A bad hack to apply rules to all timeblocks on timesheet
521 		List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
522         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getPayCalendarDates(), tdaf.getTimesheetDocument(), TKContext.getPrincipalId());
523         TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks, newTimeBlocks, TKContext.getPrincipalId());
524 	}
525 	
526     // add/update time blocks
527 	private void changeTimeBlocks(TimeDetailActionForm tdaf) {
528 		Timestamp overtimeBeginTimestamp = null;
529         Timestamp overtimeEndTimestamp = null;
530         boolean isClockLogCreated = false;
531         
532         // This is for updating a timeblock or changing
533         // If tkTimeBlockId is not null and the new timeblock is valid, delete the existing timeblock and a new one will be created after submitting the form.
534         if (tdaf.getTkTimeBlockId() != null) {
535             TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
536             if (tb != null) {
537 	            isClockLogCreated = tb.getClockLogCreated();
538 	            if (StringUtils.isNotEmpty(tdaf.getOvertimePref())) {
539                     //TODO:  This doesn't do anything!!! these variables are never used.  Should they be?
540 	                overtimeBeginTimestamp = tb.getBeginTimestamp();
541 	                overtimeEndTimestamp = tb.getEndTimestamp();
542 	            }
543             }
544             // old time block is deleted from addTimeBlock method
545             // this.removeOldTimeBlock(tdaf);
546         }
547 
548         Assignment currentAssignment = TkServiceLocator.getAssignmentService().getAssignment(tdaf.getTimesheetDocument(), tdaf.getSelectedAssignment());
549 
550 
551         // Surgery point - Need to construct a Date/Time with Appropriate Timezone.
552         Timestamp startTime = TKUtils.convertDateStringToTimestamp(tdaf.getStartDate(), tdaf.getStartTime());
553         Timestamp endTime = TKUtils.convertDateStringToTimestamp(tdaf.getEndDate(), tdaf.getEndTime());
554 
555         // We need a  cloned reference set so we know whether or not to
556         // persist any potential changes without making hundreds of DB calls.
557         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(tdaf.getTimesheetDocument().getTimeBlocks().size());
558         for (TimeBlock tb : tdaf.getTimesheetDocument().getTimeBlocks()) {
559             referenceTimeBlocks.add(tb.copy());
560         }
561 
562         // This is just a reference, for code clarity, the above list is actually
563         // separate at the object level.
564         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
565         DateTime startTemp = new DateTime(startTime);
566         DateTime endTemp = new DateTime(endTime);
567         // KPME-1446 add spanningweeks to the calls below 
568         if (StringUtils.equals(tdaf.getAcrossDays(), "y")
569                 && !(endTemp.getDayOfYear() - startTemp.getDayOfYear() <= 1
570                 && endTemp.getHourOfDay() == 0)) {
571             List<TimeBlock> timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocksSpanDates(currentAssignment,
572                     tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime,
573                     endTime, tdaf.getHours(), tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()), tdaf.getSpanningWeeks(), TKContext.getPrincipalId());
574             for (TimeBlock tb : timeBlocksToAdd) {
575                 if (!newTimeBlocks.contains(tb)) {
576                     newTimeBlocks.add(tb);
577                 }
578             }
579         } else {
580             List<TimeBlock> timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocks(currentAssignment,
581                     tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime,
582                     endTime, tdaf.getHours(), tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()), TKContext.getPrincipalId());
583             for (TimeBlock tb : timeBlocksToAdd) {
584                 if (!newTimeBlocks.contains(tb)) {
585                     newTimeBlocks.add(tb);
586                 }
587             }
588         }
589 
590         //reset time block
591         TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
592 
593         // apply overtime pref
594         // I changed start and end times comparison below. it used to be overtimeBeginTimestamp and overtimeEndTimestamp but
595         // for some reason, they're always null because, we have removed the time block before getting here. KPME-2162
596         for (TimeBlock tb : newTimeBlocks) {
597             if (tb.getBeginTimestamp().equals(startTime) && tb.getEndTimestamp().equals(endTime) && StringUtils.isNotEmpty(tdaf.getOvertimePref())) {
598                 tb.setOvertimePref(tdaf.getOvertimePref());
599             }
600 
601         }
602         
603         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
604         List<String> assignmentKeys = new ArrayList<String>();
605         for (Assignment assignment : assignments) {
606         	assignmentKeys.add(assignment.getAssignmentKey());
607         }
608         List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(TKContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
609 
610         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getPayCalendarDates(), tdaf.getTimesheetDocument(), TKContext.getPrincipalId());
611         TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, TKContext.getPrincipalId());
612 	}
613 
614     public ActionForward updateTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
615 
616         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
617         Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(tdaf.getTimesheetDocument(), tdaf.getSelectedAssignment());
618 
619         //Grab timeblock to be updated from form
620         List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
621         TimeBlock updatedTimeBlock = null;
622         for (TimeBlock tb : timeBlocks) {
623             if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
624                 updatedTimeBlock = tb;
625                 tb.setJobNumber(assignment.getJobNumber());
626                 tb.setWorkArea(assignment.getWorkArea());
627                 tb.setTask(assignment.getTask());
628                 break;
629             }
630         }
631 
632         Set<String> earnCodes = new HashSet<String>();
633         if (updatedTimeBlock != null) {
634             List<EarnCode> validEarnCodes = TkServiceLocator.getEarnCodeService().getEarnCodesForTime(assignment, updatedTimeBlock.getBeginDate(), true);
635             for (EarnCode e : validEarnCodes) {
636                 earnCodes.add(e.getEarnCode());
637             }
638         }
639 
640         if (updatedTimeBlock != null
641                 && earnCodes.contains(updatedTimeBlock.getEarnCode())) {
642             TkServiceLocator.getTimeBlockService().updateTimeBlock(updatedTimeBlock);
643 
644             TimeBlockHistory tbh = new TimeBlockHistory(updatedTimeBlock);
645             tbh.setActionHistory(TkConstants.ACTIONS.UPDATE_TIME_BLOCK);
646             TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
647         }
648         tdaf.setMethodToCall("addTimeBlock");
649         return mapping.findForward("basic");
650     }
651 
652 
653     public ActionForward actualTimeInquiry(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
654         return mapping.findForward("ati");
655     }
656 
657     public ActionForward deleteLunchDeduction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
658 
659         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
660         String timeHourDetailId = tdaf.getTkTimeHourDetailId();
661         TkServiceLocator.getTimeBlockService().deleteLunchDeduction(timeHourDetailId);
662 
663         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
664         
665         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
666         List<String> assignmentKeys = new ArrayList<String>();
667         for (Assignment assignment : assignments) {
668         	assignmentKeys.add(assignment.getAssignmentKey());
669         }
670         List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(TKContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
671 
672         TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
673         
674         // KPME-1340
675         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getPayCalendarDates(), tdaf.getTimesheetDocument(), TKContext.getPrincipalId());
676         TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks);
677         TKContext.getCurrentTimesheetDocument().setTimeBlocks(newTimeBlocks);
678         
679         return mapping.findForward("basic");
680     }
681       
682   public ActionForward gotoCurrentPayPeriod(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
683 	  String viewPrincipal = TKUser.getCurrentTargetPersonId();
684 	  Date currentDate = TKUtils.getTimelessDate(null);
685       CalendarEntries pce = TkServiceLocator.getCalendarService().getCurrentCalendarDates(viewPrincipal, currentDate);
686       TimesheetDocument td = TkServiceLocator.getTimesheetService().openTimesheetDocument(viewPrincipal, pce);
687       setupDocumentOnFormContext((TimesheetActionForm)form, td);
688 	  return mapping.findForward("basic");
689   }
690   
691   //Triggered by changes of pay period drop down list, reload the whole page based on the selected pay period
692   public ActionForward changeCalendarYear(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
693 	  
694 	  TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
695 	  if(request.getParameter("selectedCY") != null) {
696 		  tdaf.setSelectedCalendarYear(request.getParameter("selectedCY").toString());
697 	  }
698 	  return mapping.findForward("basic");
699   }
700   
701   //Triggered by changes of pay period drop down list, reload the whole page based on the selected pay period
702   public ActionForward changePayPeriod(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
703 	  TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
704 	  if(request.getParameter("selectedPP") != null) {
705 		  tdaf.setSelectedPayPeriod(request.getParameter("selectedPP").toString());
706           CalendarEntries pce = TkServiceLocator.getCalendarEntriesService()
707 		  	.getCalendarEntries(request.getParameter("selectedPP").toString());
708 		  if(pce != null) {
709 			  String viewPrincipal = TKUser.getCurrentTargetPersonId();
710 			  TimesheetDocument td = TkServiceLocator.getTimesheetService().openTimesheetDocument(viewPrincipal, pce);
711 			  setupDocumentOnFormContext((TimesheetActionForm)form, td);
712 		  }
713 	  }
714 	  return mapping.findForward("basic");
715   }
716   
717   public ActionForward deleteLeaveBlock(ActionMapping mapping,ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
718 	  TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
719 	  
720 	  String principalId = TKContext.getPrincipalId();
721 	  String targetPrincipalId = TKContext.getTargetPrincipalId();
722 	  String documentId = tdaf.getDocumentId();
723 	  String leaveBlockId = tdaf.getLmLeaveBlockId();
724 
725       LeaveBlock blockToDelete = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
726       if (blockToDelete != null && TkServiceLocator.getPermissionsService().canDeleteLeaveBlock(blockToDelete)) {
727 		    TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlockId, TKContext.getPrincipalId());
728       
729 		    generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
730       }
731 
732       // if the leave block is NOT eligible for accrual, rerun accrual service for the leave calendar the leave block is on
733       EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(blockToDelete.getEarnCode(), blockToDelete.getLeaveDate());
734       if(ec != null && ec.getEligibleForAccrual().equals("N")) {
735     	  CalendarEntries ce = TkServiceLocator.getCalendarService()
736 					.getCurrentCalendarDatesForLeaveCalendar(blockToDelete.getPrincipalId(), blockToDelete.getLeaveDate());
737     	  if(ce != null) {
738     		  TkServiceLocator.getLeaveAccrualService().runAccrual(blockToDelete.getPrincipalId(), ce.getBeginPeriodDate(), ce.getEndPeriodDate(), false);
739     	  }
740       }
741 		
742       return mapping.findForward("basic");
743 	}
744   
745 	private void generateTimesheetChangedNotification(String principalId, String targetPrincipalId, String documentId) {
746 		if (!StringUtils.equals(principalId, targetPrincipalId)) {
747 			EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
748 			if (person != null && person.getDefaultName() != null) {
749 				String subject = "Timesheet Modification Notice";
750 				StringBuilder message = new StringBuilder();
751 				message.append("Your Timesheet was changed by ");
752 				message.append(person.getDefaultName().getCompositeNameUnmasked());
753 				message.append(" on your behalf.");
754 				message.append(SystemUtils.LINE_SEPARATOR);
755 				message.append(getTimesheetURL(documentId));
756 				
757 				TkServiceLocator.getKPMENotificationService().sendNotification(subject, message.toString(), targetPrincipalId);
758 			}
759 		}
760 	}
761 	
762 	@SuppressWarnings("deprecation")
763 	private String getTimesheetURL(String documentId) {
764 		Properties params = new Properties();
765 		params.put("documentId", documentId);
766 		return UrlFactory.parameterizeUrl(getApplicationBaseUrl() + "/TimeDetail.do", params);
767 	}
768 
769 }