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.detail.web;
17  
18  import java.math.BigDecimal;
19  import java.sql.Timestamp;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.Properties;
28  import java.util.Set;
29  
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.apache.commons.collections.MapUtils;
34  import org.apache.commons.lang.ObjectUtils;
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.commons.lang.SystemUtils;
37  import org.apache.struts.action.ActionForm;
38  import org.apache.struts.action.ActionForward;
39  import org.apache.struts.action.ActionMapping;
40  import org.joda.time.DateTime;
41  import org.joda.time.Interval;
42  import org.joda.time.LocalDate;
43  import org.kuali.kpme.core.accrualcategory.AccrualCategory;
44  import org.kuali.kpme.core.accrualcategory.rule.AccrualCategoryRule;
45  import org.kuali.kpme.core.assignment.Assignment;
46  import org.kuali.kpme.core.assignment.AssignmentDescriptionKey;
47  import org.kuali.kpme.core.calendar.Calendar;
48  import org.kuali.kpme.core.calendar.entry.CalendarEntry;
49  import org.kuali.kpme.core.earncode.EarnCode;
50  import org.kuali.kpme.core.principal.PrincipalHRAttributes;
51  import org.kuali.kpme.core.service.HrServiceLocator;
52  import org.kuali.kpme.core.util.HrConstants;
53  import org.kuali.kpme.core.util.HrContext;
54  import org.kuali.kpme.core.util.TKUtils;
55  import org.kuali.kpme.tklm.common.LMConstants;
56  import org.kuali.kpme.tklm.common.TkConstants;
57  import org.kuali.kpme.tklm.leave.block.LeaveBlock;
58  import org.kuali.kpme.tklm.leave.block.LeaveBlockAggregate;
59  import org.kuali.kpme.tklm.leave.calendar.validation.LeaveCalendarValidationUtil;
60  import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
61  import org.kuali.kpme.tklm.leave.transfer.BalanceTransfer;
62  import org.kuali.kpme.tklm.leave.transfer.validation.BalanceTransferValidationUtils;
63  import org.kuali.kpme.tklm.time.calendar.TkCalendar;
64  import org.kuali.kpme.tklm.time.detail.validation.TimeDetailValidationUtil;
65  import org.kuali.kpme.tklm.time.service.TkServiceLocator;
66  import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
67  import org.kuali.kpme.tklm.time.timeblock.TimeBlockHistory;
68  import org.kuali.kpme.tklm.time.timehourdetail.TimeHourDetail;
69  import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
70  import org.kuali.kpme.tklm.time.timesheet.web.TimesheetAction;
71  import org.kuali.kpme.tklm.time.timesummary.AssignmentColumn;
72  import org.kuali.kpme.tklm.time.timesummary.AssignmentRow;
73  import org.kuali.kpme.tklm.time.timesummary.EarnCodeSection;
74  import org.kuali.kpme.tklm.time.timesummary.EarnGroupSection;
75  import org.kuali.kpme.tklm.time.timesummary.TimeSummary;
76  import org.kuali.kpme.tklm.time.util.TkContext;
77  import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
78  import org.kuali.rice.kew.api.KewApiServiceLocator;
79  import org.kuali.rice.kew.api.document.DocumentStatus;
80  import org.kuali.rice.kew.service.KEWServiceLocator;
81  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
82  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
83  import org.kuali.rice.krad.exception.AuthorizationException;
84  import org.kuali.rice.krad.util.GlobalVariables;
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);
92          
93          TimeDetailActionForm timeDetailActionForm = (TimeDetailActionForm) form;
94  
95          String principalId = GlobalVariables.getUserSession().getPrincipalId();
96      	TimesheetDocument timesheetDocument = TkServiceLocator.getTimesheetService().getTimesheetDocument(timeDetailActionForm.getDocumentId());
97          if (StringUtils.equals(methodToCall, "addTimeBlock") 
98          		|| StringUtils.equals(methodToCall, "deleteTimeBlock") 
99          		|| StringUtils.equals(methodToCall, "updateTimeBlock")) {
100             if (!HrServiceLocator.getHRPermissionService().canEditCalendarDocument(principalId, timesheetDocument)) {
101                 throw new AuthorizationException(principalId, "TimeDetailAction", "");
102             }
103         }
104     }
105 
106     @Override
107     public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
108         ActionForward forward = super.execute(mapping, form, request, response);
109 
110         TimeDetailActionForm timeDetailActionForm = (TimeDetailActionForm) form;
111 
112         CalendarEntry calendarEntry = timeDetailActionForm.getCalendarEntry();
113         TimesheetDocument timesheetDocument = timeDetailActionForm.getTimesheetDocument();
114 
115 
116         if (calendarEntry != null && timesheetDocument != null) {
117 			List<String> assignmentKeys = new ArrayList<String>();
118 	        for (Assignment assignment : timesheetDocument.getAssignments()) {
119 	        	assignmentKeys.add(assignment.getAssignmentKey());
120 	        }
121 
122 	        timeDetailActionForm.setAssignmentDescriptions(timeDetailActionForm.getTimesheetDocument().getAssignmentDescriptions(false));
123 
124 	        timeDetailActionForm.setDocEditable("false");
125 	        if (HrContext.isSystemAdmin()) {
126 	            timeDetailActionForm.setDocEditable("true");
127 	        } else {
128 	        	DocumentStatus documentStatus = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(timeDetailActionForm.getDocumentId());
129 	            if (!DocumentStatus.FINAL.equals(documentStatus) 
130 	            		&& !DocumentStatus.CANCELED.getCode().equals(documentStatus)
131 	     	 	 	 	&& !DocumentStatus.DISAPPROVED.getCode().equals(documentStatus)) {
132 	            	if(StringUtils.equals(timesheetDocument.getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())
133                             || HrContext.isSystemAdmin()
134                             || TkContext.isLocationAdmin()
135                             || HrContext.isReviewer()
136                             || HrContext.isAnyApprover()
137                             || HrContext.isAnyPayrollProcessor()) {
138                         timeDetailActionForm.setDocEditable("true");
139                     }
140 	            	
141 		            //if the timesheet has been approved by at least one of the approvers, the employee should not be able to edit it
142 		            if (StringUtils.equals(timesheetDocument.getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())
143 		            		&& timesheetDocument.getDocumentHeader().getDocumentStatus().equals(HrConstants.ROUTE_STATUS.ENROUTE)) {
144 			        	Collection actions = KEWServiceLocator.getActionTakenService().findByDocIdAndAction(timesheetDocument.getDocumentHeader().getDocumentId(), HrConstants.DOCUMENT_ACTIONS.APPROVE);
145 		        		if (!actions.isEmpty()) {
146 		        			timeDetailActionForm.setDocEditable("false");  
147 		        		}
148 			        }
149 	            } else if (DocumentStatus.FINAL.equals(documentStatus)) {
150 	            	if(HrContext.isSystemAdmin()) {
151 	            	  timeDetailActionForm.setNotesEditable(Boolean.TRUE);
152 	            	} else { 
153 	            	  timeDetailActionForm.setNotesEditable(Boolean.FALSE);
154 	            	}
155 	            }
156 	        }
157 
158 	        List<TimeBlock> timeBlocks = TkServiceLocator.getTimesheetService().getTimesheetDocument(timeDetailActionForm.getDocumentId()).getTimeBlocks();
159             List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(),
160 					calendarEntry.getBeginPeriodFullDateTime().toLocalDate(), calendarEntry.getEndPeriodFullDateTime().toLocalDate(), assignmentKeys);
161 
162 
163 	        timeDetailActionForm.getTimesheetDocument().setTimeBlocks(timeBlocks);
164 	        assignStypeClassMapForTimeSummary(timeDetailActionForm,timeBlocks, leaveBlocks);
165 
166 	        Calendar payCalendar = HrServiceLocator.getCalendarService().getCalendar(calendarEntry != null ? calendarEntry.getHrCalendarId() : null);
167 
168             List<Interval> intervals = TKUtils.getFullWeekDaySpanForCalendarEntry(calendarEntry);
169 	        LeaveBlockAggregate lbAggregate = new LeaveBlockAggregate(leaveBlocks, calendarEntry, intervals);
170 	        TkTimeBlockAggregate tbAggregate = new TkTimeBlockAggregate(timeBlocks, calendarEntry, payCalendar, true,intervals);
171 
172 	        // use both time aggregate and leave aggregate to populate the calendar
173 	        TkCalendar cal = TkCalendar.getCalendar(tbAggregate, lbAggregate);
174 	        cal.assignAssignmentStyle(timeDetailActionForm.getAssignStyleClassMap());
175 	        timeDetailActionForm.setTkCalendar(cal);
176 
177 	        timeDetailActionForm.setTimeBlockString(ActionFormUtils.getTimeBlocksJson(tbAggregate.getFlattenedTimeBlockList()));
178 	        timeDetailActionForm.setLeaveBlockString(ActionFormUtils.getLeaveBlocksJson(lbAggregate.getFlattenedLeaveBlockList()));
179 
180 	        timeDetailActionForm.setOvertimeEarnCodes(HrServiceLocator.getEarnCodeService().getOvertimeEarnCodesStrs(timesheetDocument.getAsOfDate()));
181 	
182 	        if (StringUtils.equals(timesheetDocument.getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())) {
183 	        	timeDetailActionForm.setWorkingOnItsOwn("true");
184 	        }
185 
186 	        setMessages(timeDetailActionForm);
187 
188         }
189         
190         return forward;
191     }
192 
193     // use lists of time blocks and leave blocks to build the style class map and assign css class to associated summary rows
194 	private void assignStypeClassMapForTimeSummary(TimeDetailActionForm tdaf, List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks) throws Exception {
195 		TimeSummary ts = TkServiceLocator.getTimeSummaryService().getTimeSummary(tdaf.getTimesheetDocument());
196         tdaf.setAssignStyleClassMap(ActionFormUtils.buildAssignmentStyleClassMap(timeBlocks, leaveBlocks));
197         Map<String, String> aMap = tdaf.getAssignStyleClassMap();
198         // set css classes for each assignment row
199         for (EarnGroupSection earnGroupSection : ts.getSections()) {
200             for (EarnCodeSection section : earnGroupSection.getEarnCodeSections()) {
201                 for (AssignmentRow assignRow : section.getAssignmentsRows()) {
202                 	String assignmentCssStyle = MapUtils.getString(aMap, assignRow.getAssignmentKey());
203                 	assignRow.setCssClass(assignmentCssStyle);
204                 	for (AssignmentColumn assignmentColumn : assignRow.getAssignmentColumns().values()) {
205                 		assignmentColumn.setCssClass(assignmentCssStyle);
206                 	}
207                 }
208             }
209 
210         }
211         tdaf.setTimeSummary(ts);
212         //ActionFormUtils.validateHourLimit(tdaf);
213         ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
214         ActionFormUtils.addUnapprovedIPWarningFromClockLog(tdaf);
215 	}
216 
217 	protected void setMessages(TimeDetailActionForm timeDetailActionForm) {
218         String principalId = HrContext.getTargetPrincipalId();
219 		TimesheetDocument timesheetDocument = timeDetailActionForm.getTimesheetDocument();
220 		CalendarEntry calendarEntry = timeDetailActionForm.getCalendarEntry();
221 		
222         List<LeaveBlock> balanceTransferLeaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(timesheetDocument.getPrincipalId(),
223        		 calendarEntry.getBeginPeriodFullDateTime().toLocalDate(), calendarEntry.getEndPeriodFullDateTime().toLocalDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
224        
225         Map<String, Set<String>> allMessages = LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(balanceTransferLeaveBlocks);
226        
227         // add warning messages based on max carry over balances for each accrual category for non-exempt leave users
228         List<BalanceTransfer> losses = new ArrayList<BalanceTransfer>();
229         if (LmServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, HrConstants.FLSA_STATUS_NON_EXEMPT, true)) {
230         	PrincipalHRAttributes principalCalendar = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, calendarEntry.getEndPeriodFullDateTime().toLocalDate());
231 
232         	Interval calendarInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(), calendarEntry.getEndPeriodDate().getTime());
233         	Map<String,Set<LeaveBlock>> maxBalInfractions = new HashMap<String,Set<LeaveBlock>>();
234 	        
235         	if (principalCalendar != null) {
236         		maxBalInfractions = LmServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry, principalId);
237    	        
238         		for (Entry<String,Set<LeaveBlock>> entry : maxBalInfractions.entrySet()) {
239         			for (LeaveBlock lb : entry.getValue()) {
240         				if (calendarInterval.contains(lb.getLeaveDate().getTime())) {
241 	    	        		AccrualCategory accrualCat = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(lb.getAccrualCategory(), lb.getLeaveLocalDate());
242 				        	AccrualCategoryRule aRule = HrServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(lb.getAccrualCategoryRuleId());
243 				        	if (StringUtils.equals(aRule.getActionAtMaxBalance(),HrConstants.ACTION_AT_MAX_BALANCE.LOSE)) {
244 				        		DateTime aDate = null;
245 				        		if (StringUtils.equals(aRule.getMaxBalanceActionFrequency(), HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
246 				        			aDate = HrServiceLocator.getLeavePlanService().getRolloverDayOfLeavePlan(principalCalendar.getLeavePlan(), lb.getLeaveLocalDate());
247 				        		} else {
248 					        		Calendar cal = HrServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(principalId, new LocalDate(lb.getLeaveDate()), true);
249 					        		CalendarEntry leaveEntry = HrServiceLocator.getCalendarEntryService().getCurrentCalendarEntryByCalendarId(cal.getHrCalendarId(), new DateTime(lb.getLeaveDate()));
250 					        		aDate = new DateTime(leaveEntry.getEndPeriodDate());
251 				        		}
252 				        		aDate = aDate.minusDays(1);
253 				        		if (calendarInterval.contains(aDate.getMillis()) && aDate.toDate().compareTo(calendarEntry.getEndPeriodDate()) <= 0) {
254 					        		//may want to calculate summary for all rows, displayable or not, and determine displayability via tags.
255 					    			AccrualCategory accrualCategory = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(aRule.getLmAccrualCategoryId());
256 					    			BigDecimal accruedBalance = LmServiceLocator.getAccrualService().getAccruedBalanceForPrincipal(principalId, accrualCategory, lb.getLeaveLocalDate());
257 						        	
258 						        	BalanceTransfer loseTransfer = LmServiceLocator.getBalanceTransferService().initializeTransfer(principalId, lb.getAccrualCategoryRuleId(), accruedBalance, lb.getLeaveLocalDate());
259 						        	boolean valid = BalanceTransferValidationUtils.validateTransfer(loseTransfer);
260 						        	if (valid) {
261 						        		//validation occurs again before the "transfer" action occurs that submits the forfeiture.
262 						        		losses.add(loseTransfer);
263 						        	}
264 				        		}
265 				        	}
266 	
267 	    		        	// accrual categories within the leave plan that are hidden from the leave summary WILL appear.
268 	        				String message = "You have exceeded the maximum balance limit for '" + accrualCat.getAccrualCategory() + "' as of " + lb.getLeaveDate() + ". "
269 	        						+ "Depending upon the accrual category rules, leave over this limit may be forfeited.";
270 	        				//  leave blocks are sorted in getMaxBalanceViolations() method, so we just take the one with the earliest leave date for an accrual category.
271 	        				if (!StringUtils.contains(allMessages.get("warningMessages").toString(), "You have exceeded the maximum balance limit for '" + accrualCat.getAccrualCategory())) {
272                         	   allMessages.get("warningMessages").add(message);
273 	        				}
274         				}
275         			}
276         		}
277         	}
278         	timeDetailActionForm.setForfeitures(losses);
279         	
280             Map<String, Set<String>> transactionalMessages = LeaveCalendarValidationUtil.validatePendingTransactions(HrContext.getTargetPrincipalId(), calendarEntry.getBeginPeriodFullDateTime().toLocalDate(), calendarEntry.getEndPeriodFullDateTime().toLocalDate());
281             allMessages.get("infoMessages").addAll(transactionalMessages.get("infoMessages"));
282             allMessages.get("warningMessages").addAll(transactionalMessages.get("warningMessages"));
283             allMessages.get("actionMessages").addAll(transactionalMessages.get("actionMessages"));
284            
285         	if (principalCalendar != null) {
286         	   Calendar calendar = HrServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(principalId, calendarEntry.getEndPeriodFullDateTime().toLocalDate(), true);
287 					
288 				if (calendar != null) {
289 					List<CalendarEntry> leaveCalendarEntries = HrServiceLocator.getCalendarEntryService().getCalendarEntriesEndingBetweenBeginAndEndDate(calendar.getHrCalendarId(), calendarEntry.getBeginPeriodFullDateTime(), calendarEntry.getEndPeriodFullDateTime());
290 					
291 					List<AccrualCategory> accrualCategories = HrServiceLocator.getAccrualCategoryService().getActiveLeaveAccrualCategoriesForLeavePlan(principalCalendar.getLeavePlan(), calendarEntry.getEndPeriodFullDateTime().toLocalDate());
292 					for (AccrualCategory accrualCategory : accrualCategories) {
293 						if (LmServiceLocator.getAccrualCategoryMaxCarryOverService().exceedsAccrualCategoryMaxCarryOver(accrualCategory.getAccrualCategory(), principalId, leaveCalendarEntries, calendarEntry.getEndPeriodFullDateTime().toLocalDate())) {
294 							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.";
295 							if (!allMessages.get("warningMessages").contains(message)) {
296 		                        allMessages.get("warningMessages").add(message);
297 							}
298 						}
299 					}
300 				}
301 			}
302         }
303         
304         List<String> infoMessages = timeDetailActionForm.getInfoMessages();
305         infoMessages.addAll(allMessages.get("infoMessages"));
306 
307         List<String> warningMessages = timeDetailActionForm.getWarningMessages();
308         warningMessages.addAll(allMessages.get("warningMessages"));
309 
310         List<String> actionMessages = timeDetailActionForm.getActionMessages();
311         actionMessages.addAll(allMessages.get("actionMessages"));
312 
313         timeDetailActionForm.setInfoMessages(infoMessages);
314         timeDetailActionForm.setWarningMessages(warningMessages);
315         timeDetailActionForm.setActionMessages(actionMessages);
316 	}
317 
318     /**
319      * This method involves creating an object-copy of every TimeBlock on the
320      * time sheet for overtime re-calculation.
321      *
322      * @throws Exception
323      */
324     public ActionForward deleteTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
325         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
326         
327         String principalId = HrContext.getPrincipalId();
328         String targetPrincipalId = HrContext.getTargetPrincipalId();
329         String documentId = tdaf.getDocumentId();
330 	 	 	
331         //Grab timeblock to be deleted from form
332         List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
333         TimeBlock deletedTimeBlock = null;
334         for (TimeBlock tb : timeBlocks) {
335             if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
336                 deletedTimeBlock = tb;
337                 break;
338             }
339         }
340         if (deletedTimeBlock == null) {
341             return mapping.findForward("basic");
342         }
343         //Remove from the list of timeblocks
344         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(tdaf.getTimesheetDocument().getTimeBlocks().size());
345         for (TimeBlock b : tdaf.getTimesheetDocument().getTimeBlocks()) {
346             referenceTimeBlocks.add(b.copy());
347         }
348 
349         // simple pointer, for clarity
350         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
351         newTimeBlocks.remove(deletedTimeBlock);
352 
353         //Delete timeblock
354         TkServiceLocator.getTimeBlockService().deleteTimeBlock(deletedTimeBlock);
355         // Add a row to the history table
356         TimeBlockHistory tbh = new TimeBlockHistory(deletedTimeBlock);
357         tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
358         TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
359 
360         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
361         List<String> assignmentKeys = new ArrayList<String>();
362         for (Assignment assignment : assignments) {
363             assignmentKeys.add(assignment.getAssignmentKey());
364         }
365         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
366 
367         //reset time block
368         TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
369         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
370         TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, HrContext.getPrincipalId());
371 
372         generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
373         
374         return mapping.findForward("basic");
375     }
376 
377     /**
378      * This method involves creating an object-copy of every TimeBlock on the
379      * time sheet for overtime re-calculation.
380      * Based on the form's timeBlockId or leaveBlockId, existing Time/Leave blocks will be deleted and new ones created
381      *
382      * @throws Exception
383      */
384     public ActionForward addTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
385         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
386         
387         if(StringUtils.isNotEmpty(tdaf.getLmLeaveBlockId())) {
388         	List<String> errors = TimeDetailValidationUtil.validateLeaveEntry(tdaf);
389         	if(errors.isEmpty()) {
390         		//KPME-2832: validate leave entry prior to save.
391         		//This duplicates validation done on submissions that went through TimeDetailWSAction, i.e. typical time calendar transactions.
392         		this.updateLeaveBlock(tdaf);
393         	}
394         	else {
395         		tdaf.setErrorMessages(errors);
396         	}
397         	return mapping.findForward("basic");
398         }
399         
400         if(StringUtils.isNotEmpty(tdaf.getSelectedEarnCode())) {
401         	EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(tdaf.getSelectedEarnCode(), TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate()).toLocalDate());
402         	if(ec != null && ec.getLeavePlan() != null) {
403         		//leave blocks changes
404             	List<String> errors = TimeDetailValidationUtil.validateLeaveEntry(tdaf);
405             	if(errors.isEmpty()) {
406             		//KPME-2832: validate leave entry prior to save.
407             		//This duplicates validation done on submissions that went through TimeDetailWSAction, i.e. typical time calendar transactions.
408             		this.changeLeaveBlocks(tdaf);
409             	}
410             	else {
411             		tdaf.setErrorMessages(errors);
412             	}
413             } else {
414             	// time blocks changes
415                 List<String> errors = TimeDetailValidationUtil.validateTimeEntryDetails(tdaf);
416                 if(errors.isEmpty()) {
417             		//KPME-2832: validate leave entry prior to save.
418             		//This duplicates validation done on submissions that went through TimeDetailWSAction, i.e. typical time calendar transactions.
419                 	this.changeTimeBlocks(tdaf);
420                 }
421                 else {
422                 	tdaf.setErrorMessages(errors);
423                 }
424         	}
425         }
426         
427        // ActionFormUtils.validateHourLimit(tdaf);
428         ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
429         return mapping.findForward("basic");
430     }
431     
432     private void removeOldTimeBlock(TimeDetailActionForm tdaf) {
433 	  if (tdaf.getTkTimeBlockId() != null) {
434 	      TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
435 	      if (tb != null) {
436 	          TimeBlockHistory tbh = new TimeBlockHistory(tb);
437 	          TkServiceLocator.getTimeBlockService().deleteTimeBlock(tb);
438 	
439 	          // mark the original timeblock as deleted in the history table
440 			  tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
441 			  TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
442 	
443 			  // delete the timeblock from the memory
444 	          tdaf.getTimesheetDocument().getTimeBlocks().remove(tb);
445 	      }
446 	  }
447     }
448     
449     private void removeOldLeaveBlock(String lbId) {
450   	  if (lbId != null) {
451   	      LeaveBlock lb = LmServiceLocator.getLeaveBlockService().getLeaveBlock(lbId);
452   	      if (lb != null) {
453   	          LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(lbId, HrContext.getPrincipalId());
454   	      }
455   	  }
456     }
457     
458 	/**
459 	 * 
460 	 * Callers must first run Time Entry validations on tdaf.
461 	 * 
462 	 * @param tdaf
463 	 */
464     // add/update leave blocks 
465 	private void changeLeaveBlocks(TimeDetailActionForm tdaf) {
466 		DateTime beginDate = null;
467 		DateTime endDate = null;
468 		
469 		if(tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
470 			beginDate = TKUtils.convertDateStringToDateTime(tdaf.getStartDate(), tdaf.getStartTime());
471 			endDate = TKUtils.convertDateStringToDateTime(tdaf.getEndDate(), tdaf.getEndTime());
472 		} else {
473 			// should not apply time zone to dates when user's changing an hour entry
474 			beginDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getStartDate());
475 			endDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate());
476 		}
477 		
478 		String selectedEarnCode = tdaf.getSelectedEarnCode();
479 		BigDecimal leaveAmount = tdaf.getLeaveAmount();
480 		
481 		String desc = "";	// there's no description field in time calendar pop window
482 		String spanningWeeks = tdaf.getSpanningWeeks();
483         Assignment currentAssignment = tdaf.getTimesheetDocument().getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));
484 
485         LmServiceLocator.getLeaveBlockService().addLeaveBlocks(beginDate, endDate, tdaf.getCalendarEntry(), selectedEarnCode, leaveAmount, desc, currentAssignment,
486                 spanningWeeks, LMConstants.LEAVE_BLOCK_TYPE.TIME_CALENDAR, HrContext.getTargetPrincipalId());
487 
488         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
489         List<String> assignmentKeys = new ArrayList<String>();
490         for (Assignment assignment : assignments) {
491           	assignmentKeys.add(assignment.getAssignmentKey());
492         }
493         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
494 
495         // A bad hack to apply rules to all timeblocks on timesheet
496         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
497         // We need a  cloned reference set so we know whether or not to
498         // persist any potential changes without making hundreds of DB calls.
499         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks.size());
500         for (TimeBlock tb : newTimeBlocks) {
501             referenceTimeBlocks.add(tb.copy());
502         }
503 		TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
504 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, HrContext.getPrincipalId());
505         generateTimesheetChangedNotification(HrContext.getPrincipalId(), HrContext.getTargetPrincipalId(), tdaf.getDocumentId());
506 	}
507 	
508 	/**
509 	 * 
510 	 * Callers must first run Time Entry validations on tdaf.
511 	 * 
512 	 * @param tdaf
513 	 */
514 	// add/update time blocks
515 	private void changeTimeBlocks(TimeDetailActionForm tdaf) {
516 		boolean isClockLogCreated = false;
517         String clockLogBeginId = null;
518         String clockLogEndId = null;
519         tdaf.getDocumentId();
520 
521         
522         // This is for updating a timeblock or changing
523         // 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.
524         if (tdaf.getTkTimeBlockId() != null) {
525             TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
526             if (tb != null) {
527 	            isClockLogCreated = tb.getClockLogCreated();
528                 clockLogBeginId = tb.getClockLogBeginId();
529                 clockLogEndId = tb.getClockLogEndId();
530             }
531         }
532 
533         Assignment currentAssignment = tdaf.getTimesheetDocument().getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));
534 
535         // Surgery point - Need to construct a Date/Time with Appropriate Timezone.
536         DateTime startTime = null;
537         DateTime endTime = null;
538         if(tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
539             startTime = TKUtils.convertDateStringToDateTime(tdaf.getStartDate(), tdaf.getStartTime());
540             endTime = TKUtils.convertDateStringToDateTime(tdaf.getEndDate(), tdaf.getEndTime());
541             //KPME-2737
542             if (HrContext.isAnyAdmin() || HrContext.isAnyApprover() || HrContext.isAnyPayrollProcessor()) {
543                 startTime = TkServiceLocator.getGracePeriodService().processGracePeriodRule(startTime, LocalDate.fromDateFields(tdaf.getBeginCalendarEntryDate()));
544                 endTime = TkServiceLocator.getGracePeriodService().processGracePeriodRule(endTime, LocalDate.fromDateFields(tdaf.getBeginCalendarEntryDate()));
545             }
546         } else {
547             // should not apply time zone to dates when user's changing an hour entry
548             startTime = TKUtils.formatDateTimeStringNoTimezone(tdaf.getStartDate());
549             endTime = TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate());
550         }
551 
552         // This is just a reference, for code clarity, the below list is actually
553         // separate at the object level.
554         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
555 
556         // We need a  cloned reference set so we know whether or not to
557         // persist any potential changes without making hundreds of DB calls.
558         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks.size());
559         for (TimeBlock tb : newTimeBlocks) {
560             referenceTimeBlocks.add(tb.copy());
561         }
562 
563         List<TimeBlock> timeBlocksToAdd = null;
564         // KPME-1446 add spanningweeks to the calls below 
565         if (StringUtils.equals(tdaf.getAcrossDays(), "y")
566                 && !(endTime.getDayOfYear() - startTime.getDayOfYear() <= 1
567                 && endTime.getHourOfDay() == 0)) {
568 
569             timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocksSpanDates(currentAssignment,
570                     tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime,
571                     endTime, tdaf.getHours(), tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()),
572                     tdaf.getSpanningWeeks(), HrContext.getPrincipalId(), clockLogBeginId, clockLogEndId);
573            
574         } else {
575             timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocks(currentAssignment,
576                     tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime,
577                     endTime, tdaf.getHours(), tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()),
578                     HrContext.getPrincipalId(), clockLogBeginId, clockLogEndId);
579         }
580         
581         TimeBlock existingTimeBlock = null;
582         TimeBlock timeBlockToUpdate = null;
583         
584         
585         if (tdaf.getTkTimeBlockId() != null) {
586         	timeBlockToUpdate = timeBlocksToAdd.get(0);
587         	TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(tdaf.getTkTimeBlockId());
588         	timeBlockToUpdate.setTkTimeBlockId(tdaf.getTkTimeBlockId());
589         }
590         
591         List<TimeBlock> finalNewTimeBlocks = new ArrayList<TimeBlock>();
592         
593         for (TimeBlock tb : newTimeBlocks) {
594         	if(!ObjectUtils.equals(tb.getTkTimeBlockId(), tdaf.getTkTimeBlockId())) {
595         		finalNewTimeBlocks.add(tb);
596         	} else {
597         		existingTimeBlock = tb;
598         		existingTimeBlock.copy(timeBlockToUpdate);
599         		finalNewTimeBlocks.add(existingTimeBlock);
600         	}
601         }
602         
603         for (TimeBlock tb : timeBlocksToAdd) {
604         	if(tdaf.getTkTimeBlockId() != null) {
605 	        	if(!ObjectUtils.equals(tb.getTkTimeBlockId(), tdaf.getTkTimeBlockId())) {
606 	        		finalNewTimeBlocks.add(tb);
607 	        	}
608         	} else {
609         		finalNewTimeBlocks.add(tb);
610         	}
611         }
612 
613         //reset time block
614         TkServiceLocator.getTimesheetService().resetTimeBlock(finalNewTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
615 
616         // apply overtime pref
617         // I changed start and end times comparison below. it used to be overtimeBeginTimestamp and overtimeEndTimestamp but
618         // for some reason, they're always null because, we have removed the time block before getting here. KPME-2162
619         if(StringUtils.isNotEmpty(tdaf.getOvertimePref())) {
620 	        for (TimeBlock tb : finalNewTimeBlocks) {
621 	        	if ((StringUtils.isNotEmpty(tdaf.getTkTimeBlockId()) && tdaf.getTkTimeBlockId().equals(tb.getTkTimeBlockId()))
622 	        		|| (tb.getBeginTimestamp().equals(startTime) && tb.getEndTimestamp().equals(endTime))) {
623 	                tb.setOvertimePref(tdaf.getOvertimePref());
624 	            } 
625 	        }
626         }
627 
628 		List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
629         List<String> assignmentKeys = new ArrayList<String>();
630         for (Assignment assignment : assignments) {
631             	assignmentKeys.add(assignment.getAssignmentKey());
632         }
633 
634         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
635 
636         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, finalNewTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
637 
638         TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, finalNewTimeBlocks, HrContext.getPrincipalId());
639         
640         generateTimesheetChangedNotification(HrContext.getPrincipalId(), HrContext.getTargetPrincipalId(), tdaf.getDocumentId());
641 
642 	}
643 	
644 	/**
645 	 * 
646 	 * Callers must first run Time Entry validations on tdaf.
647 	 * 
648 	 * @param tdaf
649 	 */
650 	// KPME-2386
651 	private void updateLeaveBlock(TimeDetailActionForm tdaf) throws Exception {
652 
653 		String principalId = HrContext.getPrincipalId();
654 		String targetPrincipalId = HrContext.getTargetPrincipalId();
655 
656 		String selectedEarnCode = tdaf.getSelectedEarnCode();
657 		String leaveBlockId = tdaf.getLmLeaveBlockId();
658 		
659 		LeaveBlock updatedLeaveBlock = null;
660 		updatedLeaveBlock = LmServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
661         if (updatedLeaveBlock.isEditable()) {
662             if (!updatedLeaveBlock.getLeaveAmount().equals(tdaf.getLeaveAmount())) {
663                 updatedLeaveBlock.setLeaveAmount(tdaf.getLeaveAmount());
664             }
665             
666             DateTime beginDate = null;
667     		DateTime endDate = null;
668             
669             EarnCode earnCode =  HrServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, updatedLeaveBlock.getLeaveLocalDate()); // selectedEarnCode = hrEarnCodeId
670             if(earnCode != null && earnCode.getRecordMethod().equalsIgnoreCase(HrConstants.EARN_CODE_TIME)) {
671             	if(tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
672         			beginDate = TKUtils.convertDateStringToDateTimeWithoutZone(tdaf.getStartDate(), tdaf.getStartTime());
673         			endDate   = TKUtils.convertDateStringToDateTimeWithoutZone(tdaf.getEndDate(), tdaf.getEndTime());
674         		}  else {
675         			beginDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getStartDate());
676         			endDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate());
677         		}
678             	updatedLeaveBlock.setBeginTimestamp(new Timestamp(beginDate.getMillis()));
679             	updatedLeaveBlock.setEndTimestamp(new Timestamp(endDate.getMillis()));
680             	updatedLeaveBlock.setLeaveAmount(TKUtils.getHoursBetween(beginDate.getMillis(), endDate.getMillis()));
681             }
682             
683             if (!updatedLeaveBlock.getEarnCode().equals(earnCode.getEarnCode())) {
684                 updatedLeaveBlock.setEarnCode(earnCode.getEarnCode());
685             }
686             
687             LmServiceLocator.getLeaveBlockService().updateLeaveBlock(updatedLeaveBlock, principalId);
688         }
689         
690         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
691         List<String> assignmentKeys = new ArrayList<String>();
692         for (Assignment assignment : assignments) {
693             assignmentKeys.add(assignment.getAssignmentKey());
694         }
695         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(targetPrincipalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
696 
697         // A bad hack to apply rules to all timeblocks on timesheet
698 		List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
699         // We need a  cloned reference set so we know whether or not to
700         // persist any potential changes without making hundreds of DB calls.
701         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks.size());
702         for (TimeBlock tb : newTimeBlocks) {
703             referenceTimeBlocks.add(tb.copy());
704         }
705 		TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), principalId);
706 		//should we validate time blocks altered by rules service before saving? i.o.w. disallow leave block changes that would otherwise invalidate certain time entries?
707 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, principalId);
708    	 	generateTimesheetChangedNotification(principalId, targetPrincipalId, tdaf.getDocumentId());
709 
710     }
711 
712 	//No time blocks should be saved directly in this action forward without first validating the entry.
713     public ActionForward updateTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
714 
715         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
716         Assignment assignment = tdaf.getTimesheetDocument().getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));
717 
718         //Grab timeblock to be updated from form
719         List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
720         TimeBlock updatedTimeBlock = null;
721         for (TimeBlock tb : timeBlocks) {
722             if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
723                 updatedTimeBlock = tb;
724                 tb.setJobNumber(assignment.getJobNumber());
725                 tb.setWorkArea(assignment.getWorkArea());
726                 tb.setTask(assignment.getTask());
727                 break;
728             }
729         }
730 
731         Set<String> earnCodes = new HashSet<String>();
732         if (updatedTimeBlock != null) {
733             List<EarnCode> validEarnCodes = TkServiceLocator.getTimesheetService().getEarnCodesForTime(assignment, updatedTimeBlock.getBeginDateTime().toLocalDate(), true);
734             for (EarnCode e : validEarnCodes) {
735                 earnCodes.add(e.getEarnCode());
736             }
737         }
738 
739         if (updatedTimeBlock != null
740         		&& earnCodes.contains(updatedTimeBlock.getEarnCode())) {
741         	TkServiceLocator.getTimeBlockService().updateTimeBlock(updatedTimeBlock);
742 
743         	TimeBlockHistory tbh = new TimeBlockHistory(updatedTimeBlock);
744         	tbh.setActionHistory(TkConstants.ACTIONS.UPDATE_TIME_BLOCK);
745         	TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
746         }
747         
748         //addTimeBlock handles validation and creation of object. Do not save time blocks directly in this method without validating the entry!
749         tdaf.setMethodToCall("addTimeBlock");
750 
751         return mapping.findForward("basic");
752     }
753 
754 
755     public ActionForward actualTimeInquiry(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
756         return mapping.findForward("ati");
757     }
758 
759     public ActionForward deleteLunchDeduction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
760 
761         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
762         String timeHourDetailId = tdaf.getTkTimeHourDetailId();
763         
764         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
765         TimeHourDetail thd = TkServiceLocator.getTimeHourDetailService().getTimeHourDetail(timeHourDetailId);
766         for(TimeBlock tb : newTimeBlocks) {
767         	if(tb.getTkTimeBlockId().equals(thd.getTkTimeBlockId())) {
768 	        	// mark the lunch deleted as Y
769 	            tb.setLunchDeleted(true);
770         	}
771         }
772        // remove the related time hour detail row with the lunch deduction
773        TkServiceLocator.getTimeHourDetailService().removeTimeHourDetail(thd.getTkTimeHourDetailId());
774         
775        List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
776        List<String> assignmentKeys = new ArrayList<String>();
777        for (Assignment assignment : assignments) {
778            	assignmentKeys.add(assignment.getAssignmentKey());
779        }
780        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
781 
782        TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
783         
784         // KPME-1340
785         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
786         TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks);
787         tdaf.getTimesheetDocument().setTimeBlocks(newTimeBlocks);
788         
789         return mapping.findForward("basic");
790     }
791   
792   public ActionForward deleteLeaveBlock(ActionMapping mapping,ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
793 	  TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
794 	  
795 	  String principalId = HrContext.getPrincipalId();
796 	  String targetPrincipalId = HrContext.getTargetPrincipalId();
797 	  String documentId = tdaf.getDocumentId();
798 	  String leaveBlockId = tdaf.getLmLeaveBlockId();
799 
800       LeaveBlock blockToDelete = LmServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
801       if (blockToDelete != null && LmServiceLocator.getLMPermissionService().canDeleteLeaveBlock(HrContext.getPrincipalId(), blockToDelete)) {
802 		    LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlockId, HrContext.getPrincipalId());
803 
804           // A bad hack to apply rules to all timeblocks on timesheet
805           List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
806           // We need a  cloned reference set so we know whether or not to
807           // persist any potential changes without making hundreds of DB calls.
808           List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks.size());
809           for (TimeBlock tb : newTimeBlocks) {
810               referenceTimeBlocks.add(tb.copy());
811           }
812           List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
813           List<String> assignmentKeys = new ArrayList<String>();
814           for (Assignment assignment : assignments) {
815               assignmentKeys.add(assignment.getAssignmentKey());
816           }
817           List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
818 
819           //reset time block
820           TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
821           TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
822           TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, HrContext.getPrincipalId());
823           generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
824       }
825 
826       // if the leave block is NOT eligible for accrual, rerun accrual service for the leave calendar the leave block is on
827       EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(blockToDelete.getEarnCode(), blockToDelete.getLeaveLocalDate());
828       if(ec != null && ec.getEligibleForAccrual().equals("N")) {
829     	  CalendarEntry ce = HrServiceLocator.getCalendarEntryService()
830 					.getCurrentCalendarDatesForLeaveCalendar(blockToDelete.getPrincipalId(), blockToDelete.getLeaveLocalDate().toDateTimeAtStartOfDay());
831     	  if(ce != null) {
832     		  LmServiceLocator.getLeaveAccrualService().runAccrual(blockToDelete.getPrincipalId(), ce.getBeginPeriodFullDateTime().toDateTime(), ce.getEndPeriodFullDateTime().toDateTime(), false);
833     	  }
834       }
835 		
836       return mapping.findForward("basic");
837 	}
838   
839 	private void generateTimesheetChangedNotification(String principalId, String targetPrincipalId, String documentId) {
840 		if (!StringUtils.equals(principalId, targetPrincipalId)) {
841 			EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
842 			if (person != null && person.getDefaultName() != null) {
843 				String subject = "Timesheet Modification Notice";
844 				StringBuilder message = new StringBuilder();
845 				message.append("Your Timesheet was changed by ");
846 				message.append(person.getDefaultName().getCompositeNameUnmasked());
847 				message.append(" on your behalf.");
848 				message.append(SystemUtils.LINE_SEPARATOR);
849 				message.append(getTimesheetURL(documentId));
850 
851 				HrServiceLocator.getKPMENotificationService().sendNotification(subject, message.toString(), targetPrincipalId);
852 			}
853 		}
854 	}
855 
856 	@SuppressWarnings("deprecation")
857 	private String getTimesheetURL(String documentId) {
858 		Properties params = new Properties();
859 		params.put("documentId", documentId);
860 		return UrlFactory.parameterizeUrl(getApplicationBaseUrl() + "/TimeDetail.do", params);
861 	}
862 
863 }