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.apache.struts.action.ActionRedirect;
41  import org.joda.time.DateTime;
42  import org.joda.time.Interval;
43  import org.joda.time.LocalDate;
44  import org.kuali.kpme.core.accrualcategory.AccrualCategory;
45  import org.kuali.kpme.core.accrualcategory.rule.AccrualCategoryRule;
46  import org.kuali.kpme.core.assignment.Assignment;
47  import org.kuali.kpme.core.assignment.AssignmentDescriptionKey;
48  import org.kuali.kpme.core.calendar.Calendar;
49  import org.kuali.kpme.core.calendar.entry.CalendarEntry;
50  import org.kuali.kpme.core.earncode.EarnCode;
51  import org.kuali.kpme.core.principal.PrincipalHRAttributes;
52  import org.kuali.kpme.core.service.HrServiceLocator;
53  import org.kuali.kpme.core.util.HrConstants;
54  import org.kuali.kpme.core.util.HrContext;
55  import org.kuali.kpme.core.util.TKUtils;
56  import org.kuali.kpme.tklm.common.LMConstants;
57  import org.kuali.kpme.tklm.common.TkConstants;
58  import org.kuali.kpme.tklm.leave.block.LeaveBlock;
59  import org.kuali.kpme.tklm.leave.block.LeaveBlockAggregate;
60  import org.kuali.kpme.tklm.leave.calendar.validation.LeaveCalendarValidationUtil;
61  import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
62  import org.kuali.kpme.tklm.leave.summary.LeaveSummary;
63  import org.kuali.kpme.tklm.leave.summary.LeaveSummaryRow;
64  import org.kuali.kpme.tklm.leave.transfer.BalanceTransfer;
65  import org.kuali.kpme.tklm.leave.transfer.validation.BalanceTransferValidationUtils;
66  import org.kuali.kpme.tklm.time.calendar.TkCalendar;
67  import org.kuali.kpme.tklm.time.detail.validation.TimeDetailValidationUtil;
68  import org.kuali.kpme.tklm.time.service.TkServiceLocator;
69  import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
70  import org.kuali.kpme.tklm.time.timeblock.TimeBlockHistory;
71  import org.kuali.kpme.tklm.time.timehourdetail.TimeHourDetail;
72  import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
73  import org.kuali.kpme.tklm.time.timesheet.web.TimesheetAction;
74  import org.kuali.kpme.tklm.time.timesummary.AssignmentColumn;
75  import org.kuali.kpme.tklm.time.timesummary.AssignmentRow;
76  import org.kuali.kpme.tklm.time.timesummary.EarnCodeSection;
77  import org.kuali.kpme.tklm.time.timesummary.EarnGroupSection;
78  import org.kuali.kpme.tklm.time.timesummary.TimeSummary;
79  import org.kuali.kpme.tklm.time.util.TkContext;
80  import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
81  import org.kuali.rice.kew.api.KewApiServiceLocator;
82  import org.kuali.rice.kew.api.document.DocumentStatus;
83  import org.kuali.rice.kew.service.KEWServiceLocator;
84  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
85  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
86  import org.kuali.rice.krad.exception.AuthorizationException;
87  import org.kuali.rice.krad.util.GlobalVariables;
88  import org.kuali.rice.krad.util.UrlFactory;
89  
90  public class TimeDetailAction extends TimesheetAction {
91  
92      @Override
93      protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
94          super.checkTKAuthorization(form, methodToCall);
95          
96          TimeDetailActionForm timeDetailActionForm = (TimeDetailActionForm) form;
97  
98          String principalId = GlobalVariables.getUserSession().getPrincipalId();
99      	TimesheetDocument timesheetDocument = TkServiceLocator.getTimesheetService().getTimesheetDocument(timeDetailActionForm.getDocumentId());
100         if (StringUtils.equals(methodToCall, "addTimeBlock") 
101         		|| StringUtils.equals(methodToCall, "deleteTimeBlock") 
102         		|| StringUtils.equals(methodToCall, "updateTimeBlock")) {
103             if (!HrServiceLocator.getHRPermissionService().canEditCalendarDocument(principalId, timesheetDocument)) {
104                 throw new AuthorizationException(principalId, "TimeDetailAction", "");
105             }
106         }
107     }
108 
109     @Override
110     public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
111         ActionForward forward = super.execute(mapping, form, request, response);
112         TimeDetailActionForm timeDetailActionForm = (TimeDetailActionForm) form;
113 
114         CalendarEntry calendarEntry = timeDetailActionForm.getCalendarEntry();
115         TimesheetDocument timesheetDocument = timeDetailActionForm.getTimesheetDocument();
116 
117         if (calendarEntry != null && timesheetDocument != null) {
118 			List<String> assignmentKeys = new ArrayList<String>();
119 	        for (Assignment assignment : timesheetDocument.getAssignments()) {
120 	        	assignmentKeys.add(assignment.getAssignmentKey());
121 	        }
122 
123 	        timeDetailActionForm.setAssignmentDescriptions(timeDetailActionForm.getTimesheetDocument().getAssignmentDescriptions(false));
124 
125 	        timeDetailActionForm.setDocEditable("false");
126 	        if (HrContext.isSystemAdmin()) {
127 	            timeDetailActionForm.setDocEditable("true");
128 	        } else {
129 	        	DocumentStatus documentStatus = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(timeDetailActionForm.getDocumentId());
130 	            if (!DocumentStatus.FINAL.equals(documentStatus) 
131 	            		&& !DocumentStatus.CANCELED.getCode().equals(documentStatus)
132 	     	 	 	 	&& !DocumentStatus.DISAPPROVED.getCode().equals(documentStatus)) {
133 	            	if(StringUtils.equals(timesheetDocument.getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())
134                             || HrContext.isSystemAdmin()
135                             || TkContext.isLocationAdmin()
136                             || HrContext.isReviewer()
137                             || HrContext.isAnyApprover()
138                             || HrContext.isAnyPayrollProcessor()) {
139                         timeDetailActionForm.setDocEditable("true");
140                     }
141 	            	
142 		            //if the timesheet has been approved by at least one of the approvers, the employee should not be able to edit it
143 		            if (StringUtils.equals(timesheetDocument.getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())
144 		            		&& timesheetDocument.getDocumentHeader().getDocumentStatus().equals(HrConstants.ROUTE_STATUS.ENROUTE)) {
145 			        	Collection actions = KEWServiceLocator.getActionTakenService().findByDocIdAndAction(timesheetDocument.getDocumentHeader().getDocumentId(), HrConstants.DOCUMENT_ACTIONS.APPROVE);
146 		        		if (!actions.isEmpty()) {
147 		        			timeDetailActionForm.setDocEditable("false");  
148 		        		}
149 			        }
150 	            } else if (DocumentStatus.FINAL.equals(documentStatus)) {
151 	            	if(HrContext.isSystemAdmin()) {
152 	            	  timeDetailActionForm.setNotesEditable(Boolean.TRUE);
153 	            	} else { 
154 	            	  timeDetailActionForm.setNotesEditable(Boolean.FALSE);
155 	            	}
156 	            }
157 	        }
158 
159 	        List<TimeBlock> timeBlocks = TkServiceLocator.getTimesheetService().getTimesheetDocument(timeDetailActionForm.getDocumentId()).getTimeBlocks();
160             List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(),
161 					calendarEntry.getBeginPeriodFullDateTime().toLocalDate(), calendarEntry.getEndPeriodFullDateTime().toLocalDate(), assignmentKeys);
162 
163 
164 	        timeDetailActionForm.getTimesheetDocument().setTimeBlocks(timeBlocks);
165 	        assignStypeClassMapForTimeSummary(timeDetailActionForm,timeBlocks, leaveBlocks);
166 
167 	        Calendar payCalendar = HrServiceLocator.getCalendarService().getCalendar(calendarEntry != null ? calendarEntry.getHrCalendarId() : null);
168 
169             List<Interval> intervals = TKUtils.getFullWeekDaySpanForCalendarEntry(calendarEntry);
170 	        LeaveBlockAggregate lbAggregate = new LeaveBlockAggregate(leaveBlocks, calendarEntry, intervals);
171 	        TkTimeBlockAggregate tbAggregate = new TkTimeBlockAggregate(timeBlocks, calendarEntry, payCalendar, true,intervals);
172 
173 	        // use both time aggregate and leave aggregate to populate the calendar
174 	        TkCalendar cal = TkCalendar.getCalendar(tbAggregate, lbAggregate);
175 	        cal.assignAssignmentStyle(timeDetailActionForm.getAssignStyleClassMap());
176 	        timeDetailActionForm.setTkCalendar(cal);
177 
178 	        timeDetailActionForm.setTimeBlockString(ActionFormUtils.getTimeBlocksJson(tbAggregate.getFlattenedTimeBlockList()));
179 	        timeDetailActionForm.setLeaveBlockString(ActionFormUtils.getLeaveBlocksJson(lbAggregate.getFlattenedLeaveBlockList()));
180 
181 	        timeDetailActionForm.setOvertimeEarnCodes(HrServiceLocator.getEarnCodeService().getOvertimeEarnCodesStrs(timesheetDocument.getAsOfDate()));
182 	
183 	        if (StringUtils.equals(timesheetDocument.getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())) {
184 	        	timeDetailActionForm.setWorkingOnItsOwn("true");
185 	        }
186 
187 	        setMessages(timeDetailActionForm);
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, calendarEntry.getBeginPeriodDate(), calendarEntry.getEndPeriodDate());
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 = lb.getAccrualCategoryObj();
242 				        	AccrualCategoryRule aRule = lb.getAccrualCategoryRule();
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 //					    			BigDecimal leaveBalance = LmServiceLocator.getLeaveSummaryService().getLeaveBalanceForAccrCatUpToDate(principalId, startDate, endDate, accrualCategory, usageEndDate)
259 					    			
260 					    			
261 						        	BalanceTransfer loseTransfer = LmServiceLocator.getBalanceTransferService().initializeTransfer(principalId, aRule.getLmAccrualCategoryRuleId(), accruedBalance, lb.getLeaveLocalDate());
262 						        	boolean valid = BalanceTransferValidationUtils.validateTransfer(loseTransfer);
263 						        	if (valid) {
264 						        		//validation occurs again before the "transfer" action occurs that submits the forfeiture.
265 						        		losses.add(loseTransfer);
266 						        	}
267 				        		}
268 				        	}
269 	
270 	    		        	// accrual categories within the leave plan that are hidden from the leave summary WILL appear.
271 	        				String message = "You have exceeded the maximum balance limit for '" + accrualCat.getAccrualCategory() + "' as of " + lb.getLeaveDate() + ". "
272 	        						+ "Depending upon the accrual category rules, leave over this limit may be forfeited.";
273 	        				//  leave blocks are sorted in getMaxBalanceViolations() method, so we just take the one with the earliest leave date for an accrual category.
274 	        				if (!StringUtils.contains(allMessages.get("warningMessages").toString(), "You have exceeded the maximum balance limit for '" + accrualCat.getAccrualCategory())) {
275                         	   allMessages.get("warningMessages").add(message);
276 	        				}
277         				}
278         			}
279         		}
280         	}
281         	timeDetailActionForm.setForfeitures(losses);
282         	
283             Map<String, Set<String>> transactionalMessages = LeaveCalendarValidationUtil.validatePendingTransactions(HrContext.getTargetPrincipalId(), calendarEntry.getBeginPeriodFullDateTime().toLocalDate(), calendarEntry.getEndPeriodFullDateTime().toLocalDate());
284             allMessages.get("infoMessages").addAll(transactionalMessages.get("infoMessages"));
285             allMessages.get("warningMessages").addAll(transactionalMessages.get("warningMessages"));
286             allMessages.get("actionMessages").addAll(transactionalMessages.get("actionMessages"));
287            
288             
289             LeaveSummary leaveSummary = null;
290 			try {
291 				leaveSummary = LmServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId, calendarEntry);
292 			} catch (Exception e) {
293 				// TODO Auto-generated catch block
294 				e.printStackTrace();
295 			}
296             
297         	if (principalCalendar != null) {
298         	   Calendar calendar = HrServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(principalId, calendarEntry.getEndPeriodFullDateTime().toLocalDate(), true);
299 					
300 				if (calendar != null) {
301 					List<CalendarEntry> leaveCalendarEntries = HrServiceLocator.getCalendarEntryService().getCalendarEntriesEndingBetweenBeginAndEndDate(calendar.getHrCalendarId(), calendarEntry.getBeginPeriodFullDateTime(), calendarEntry.getEndPeriodFullDateTime());
302 					
303 					List<AccrualCategory> accrualCategories = HrServiceLocator.getAccrualCategoryService().getActiveLeaveAccrualCategoriesForLeavePlan(principalCalendar.getLeavePlan(), calendarEntry.getEndPeriodFullDateTime().toLocalDate());
304 					for (AccrualCategory accrualCategory : accrualCategories) {
305 						if (LmServiceLocator.getAccrualCategoryMaxCarryOverService().exceedsAccrualCategoryMaxCarryOver(accrualCategory.getAccrualCategory(), principalId, leaveCalendarEntries, calendarEntry.getEndPeriodFullDateTime().toLocalDate())) {
306 							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.";
307 							if (!allMessages.get("warningMessages").contains(message)) {
308 		                        allMessages.get("warningMessages").add(message);
309 							}
310 						}
311 					}
312 					
313 					// check for the negative Accrual balance for the category.
314 					if(leaveSummary != null && leaveSummary.getLeaveSummaryRows().size() > 0) {
315 						for(LeaveSummaryRow summaryRow : leaveSummary.getLeaveSummaryRows()) {
316 							if(summaryRow.getLeaveBalance() != null && summaryRow.getLeaveBalance().compareTo(BigDecimal.ZERO) < 0) {
317 								String message = "Negative available balance found for the accrual category '"+summaryRow.getAccrualCategory()+ "'.";
318 			        			allMessages.get("warningMessages").add(message);
319 			        		}
320 						}
321 					}
322 				}
323 			}
324         }
325         
326         List<String> infoMessages = timeDetailActionForm.getInfoMessages();
327         infoMessages.addAll(allMessages.get("infoMessages"));
328 
329         List<String> warningMessages = timeDetailActionForm.getWarningMessages();
330         warningMessages.addAll(allMessages.get("warningMessages"));
331 
332         List<String> actionMessages = timeDetailActionForm.getActionMessages();
333         actionMessages.addAll(allMessages.get("actionMessages"));
334 
335         timeDetailActionForm.setInfoMessages(infoMessages);
336         timeDetailActionForm.setWarningMessages(warningMessages);
337         timeDetailActionForm.setActionMessages(actionMessages);
338 	}
339 
340     /**
341      * This method involves creating an object-copy of every TimeBlock on the
342      * time sheet for overtime re-calculation.
343      *
344      * @throws Exception
345      */
346     public ActionForward deleteTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
347         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
348         
349         String principalId = HrContext.getPrincipalId();
350         String targetPrincipalId = HrContext.getTargetPrincipalId();
351         String documentId = tdaf.getDocumentId();
352 	 	 	
353         //Grab timeblock to be deleted from form
354         List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
355         TimeBlock deletedTimeBlock = null;
356         for (TimeBlock tb : timeBlocks) {
357             if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
358                 deletedTimeBlock = tb;
359                 break;
360             }
361         }
362         if (deletedTimeBlock == null) {
363             return mapping.findForward("basic");
364         }
365         //Remove from the list of timeblocks
366         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(tdaf.getTimesheetDocument().getTimeBlocks().size());
367         for (TimeBlock b : tdaf.getTimesheetDocument().getTimeBlocks()) {
368             referenceTimeBlocks.add(b.copy());
369         }
370 
371         // simple pointer, for clarity
372         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
373         newTimeBlocks.remove(deletedTimeBlock);
374 
375         //Delete timeblock
376         TkServiceLocator.getTimeBlockService().deleteTimeBlock(deletedTimeBlock);
377         // Add a row to the history table
378         TimeBlockHistory tbh = new TimeBlockHistory(deletedTimeBlock);
379         tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
380         TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
381 
382         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
383         List<String> assignmentKeys = new ArrayList<String>();
384         for (Assignment assignment : assignments) {
385             assignmentKeys.add(assignment.getAssignmentKey());
386         }
387         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
388 
389         //reset time block
390         TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
391         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
392         TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, HrContext.getPrincipalId());
393 
394         generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
395         
396         return mapping.findForward("basic");
397     }
398 
399     /**
400      * This method involves creating an object-copy of every TimeBlock on the
401      * time sheet for overtime re-calculation.
402      * Based on the form's timeBlockId or leaveBlockId, existing Time/Leave blocks will be deleted and new ones created
403      *
404      * @throws Exception
405      */
406     public ActionForward addTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
407         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
408         //if(this.isTokenValid(request,true)) {
409         if(StringUtils.isNotEmpty(tdaf.getLmLeaveBlockId())) {
410         	List<String> errors = TimeDetailValidationUtil.validateLeaveEntry(tdaf);
411         	if(errors.isEmpty()) {
412         		//KPME-2832: validate leave entry prior to save.
413         		//This duplicates validation done on submissions that went through TimeDetailWSAction, i.e. typical time calendar transactions.
414         		this.updateLeaveBlock(tdaf);
415         	}
416         	else {
417         		tdaf.setErrorMessages(errors);
418         	}
419         	return mapping.findForward("basic");
420         }
421         
422         if(StringUtils.isNotEmpty(tdaf.getSelectedEarnCode())) {
423         	EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(tdaf.getSelectedEarnCode(), TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate()).toLocalDate());
424         	if(ec != null && ec.getLeavePlan() != null) {
425         		//leave blocks changes
426             	List<String> errors = TimeDetailValidationUtil.validateLeaveEntry(tdaf);
427             	if(errors.isEmpty()) {
428             		//KPME-2832: validate leave entry prior to save.
429             		//This duplicates validation done on submissions that went through TimeDetailWSAction, i.e. typical time calendar transactions.
430             		this.changeLeaveBlocks(tdaf);
431             	}
432             	else {
433             		tdaf.setErrorMessages(errors);
434             	}
435             } else {
436             	// time blocks changes
437                 List<String> errors = TimeDetailValidationUtil.validateTimeEntryDetails(tdaf);
438                 if(errors.isEmpty()) {
439             		//KPME-2832: validate leave entry prior to save.
440             		//This duplicates validation done on submissions that went through TimeDetailWSAction, i.e. typical time calendar transactions.
441                 	this.changeTimeBlocks(tdaf);
442                 }
443                 else {
444                 	tdaf.setErrorMessages(errors);
445                 }
446         	}
447         }
448        // ActionFormUtils.validateHourLimit(tdaf);
449         // Removing the redirect and returning the basic action mapping forward results in
450         // duplicate time detail entry forms being submitted on browser refresh or back actions.
451         ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
452         ActionRedirect redirect = new ActionRedirect();
453         redirect.setPath("/TimeDetail.do");
454         redirect.addParameter("documentId", tdaf.getDocumentId());
455         return redirect;
456     }
457     
458     private void removeOldTimeBlock(TimeDetailActionForm tdaf) {
459 	  if (tdaf.getTkTimeBlockId() != null) {
460 	      TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
461 	      if (tb != null) {
462 	          TimeBlockHistory tbh = new TimeBlockHistory(tb);
463 	          TkServiceLocator.getTimeBlockService().deleteTimeBlock(tb);
464 	
465 	          // mark the original timeblock as deleted in the history table
466 			  tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
467 			  TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
468 	
469 			  // delete the timeblock from the memory
470 	          tdaf.getTimesheetDocument().getTimeBlocks().remove(tb);
471 	      }
472 	  }
473     }
474     
475     private void removeOldLeaveBlock(String lbId) {
476   	  if (lbId != null) {
477   	      LeaveBlock lb = LmServiceLocator.getLeaveBlockService().getLeaveBlock(lbId);
478   	      if (lb != null) {
479   	          LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(lbId, HrContext.getPrincipalId());
480   	      }
481   	  }
482     }
483     
484 	/**
485 	 * 
486 	 * Callers must first run Time Entry validations on tdaf.
487 	 * 
488 	 * @param tdaf
489 	 */
490     // add/update leave blocks 
491 	private void changeLeaveBlocks(TimeDetailActionForm tdaf) {
492 		DateTime beginDate = null;
493 		DateTime endDate = null;
494 		
495 		if(tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
496 			beginDate = TKUtils.convertDateStringToDateTime(tdaf.getStartDate(), tdaf.getStartTime());
497 			endDate = TKUtils.convertDateStringToDateTime(tdaf.getEndDate(), tdaf.getEndTime());
498 		} else {
499 			// should not apply time zone to dates when user's changing an hour entry
500 			beginDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getStartDate());
501 			endDate = TKUtils.formatDateTimeStringNoTimezone(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 = tdaf.getTimesheetDocument().getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));
510 
511         LmServiceLocator.getLeaveBlockService().addLeaveBlocks(beginDate, endDate, tdaf.getCalendarEntry(), selectedEarnCode, leaveAmount, desc, currentAssignment,
512                 spanningWeeks, LMConstants.LEAVE_BLOCK_TYPE.TIME_CALENDAR, HrContext.getTargetPrincipalId());
513 
514         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
515         List<String> assignmentKeys = new ArrayList<String>();
516         for (Assignment assignment : assignments) {
517           	assignmentKeys.add(assignment.getAssignmentKey());
518         }
519         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
520 
521         // A bad hack to apply rules to all timeblocks on timesheet
522         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
523         
524         // if we are changing an existing time block, we need to remove the time block
525         if(StringUtils.isNotBlank(tdaf.getTkTimeBlockId())) {
526         	TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
527         	if(tb != null) {
528         		TkServiceLocator.getTimeBlockService().deleteTimeBlock(tb);
529         		newTimeBlocks.remove(tb);	// removed the timeblock that should be deleted from list 
530         	}
531         }
532         // We need a  cloned reference set so we know whether or not to
533         // persist any potential changes without making hundreds of DB calls.
534         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks.size());
535         for (TimeBlock tb : newTimeBlocks) {
536             referenceTimeBlocks.add(tb.copy());
537         }
538 		TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
539 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, HrContext.getPrincipalId());
540         generateTimesheetChangedNotification(HrContext.getPrincipalId(), HrContext.getTargetPrincipalId(), tdaf.getDocumentId());
541 	}
542 	
543 	/**
544 	 * 
545 	 * Callers must first run Time Entry validations on tdaf.
546 	 * 
547 	 * @param tdaf
548 	 */
549 	// add/update time blocks
550 	private void changeTimeBlocks(TimeDetailActionForm tdaf) {
551 		boolean isClockLogCreated = false;
552         String clockLogBeginId = null;
553         String clockLogEndId = null;
554         tdaf.getDocumentId();
555 
556         
557         // This is for updating a timeblock or changing
558         // 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.
559         if (tdaf.getTkTimeBlockId() != null) {
560             TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
561             if (tb != null) {
562 	            isClockLogCreated = tb.getClockLogCreated();
563                 clockLogBeginId = tb.getClockLogBeginId();
564                 clockLogEndId = tb.getClockLogEndId();
565             }
566         }
567 
568         Assignment currentAssignment = tdaf.getTimesheetDocument().getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));
569 
570         // Surgery point - Need to construct a Date/Time with Appropriate Timezone.
571         DateTime startTime = null;
572         DateTime endTime = null;
573         if(tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
574             startTime = TKUtils.convertDateStringToDateTime(tdaf.getStartDate(), tdaf.getStartTime());
575             endTime = TKUtils.convertDateStringToDateTime(tdaf.getEndDate(), tdaf.getEndTime());
576             //KPME-2737
577             if (HrContext.isAnyAdmin() || HrContext.isAnyApprover() || HrContext.isAnyPayrollProcessor()) {
578                 startTime = TkServiceLocator.getGracePeriodService().processGracePeriodRule(startTime, LocalDate.fromDateFields(tdaf.getBeginCalendarEntryDate()));
579                 endTime = TkServiceLocator.getGracePeriodService().processGracePeriodRule(endTime, LocalDate.fromDateFields(tdaf.getBeginCalendarEntryDate()));
580             }
581         } else {
582             // should not apply time zone to dates when user's changing an hour entry
583             startTime = TKUtils.formatDateTimeStringNoTimezone(tdaf.getStartDate());
584             endTime = TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate());
585         }
586 
587         // This is just a reference, for code clarity, the below list is actually
588         // separate at the object level.
589         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
590 
591         // We need a  cloned reference set so we know whether or not to
592         // persist any potential changes without making hundreds of DB calls.
593         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks.size());
594         for (TimeBlock tb : newTimeBlocks) {
595             referenceTimeBlocks.add(tb.copy());
596         }
597 
598         List<TimeBlock> timeBlocksToAdd = null;
599         // KPME-1446 add spanningweeks to the calls below 
600         if (StringUtils.equals(tdaf.getAcrossDays(), "y")
601                 && !(endTime.getDayOfYear() - startTime.getDayOfYear() <= 1
602                 && endTime.getHourOfDay() == 0)) {
603 
604             timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocksSpanDates(currentAssignment,
605                     tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime,
606                     endTime, tdaf.getHours(), tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()),
607                     tdaf.getSpanningWeeks(), HrContext.getPrincipalId(), clockLogBeginId, clockLogEndId);
608         } else {
609             timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocks(currentAssignment,
610                     tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime,
611                     endTime, tdaf.getHours(), tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()),
612                     HrContext.getPrincipalId(), clockLogBeginId, clockLogEndId);
613         }
614         
615         TimeBlock existingTimeBlock = null;
616         TimeBlock timeBlockToUpdate = null;
617         
618         
619         if (tdaf.getTkTimeBlockId() != null) {
620         	timeBlockToUpdate = timeBlocksToAdd.get(0);
621         	TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(tdaf.getTkTimeBlockId());
622         	timeBlockToUpdate.setTkTimeBlockId(tdaf.getTkTimeBlockId());
623         }
624         
625         List<TimeBlock> finalNewTimeBlocks = new ArrayList<TimeBlock>();
626         
627         for (TimeBlock tb : newTimeBlocks) {
628         	if(!ObjectUtils.equals(tb.getTkTimeBlockId(), tdaf.getTkTimeBlockId())) {
629         		finalNewTimeBlocks.add(tb);
630         	} else {
631         		existingTimeBlock = tb;
632         		existingTimeBlock.copy(timeBlockToUpdate);
633         		finalNewTimeBlocks.add(existingTimeBlock);
634         	}
635         }
636         
637         for (TimeBlock tb : timeBlocksToAdd) {
638         	if(tdaf.getTkTimeBlockId() != null) {
639 	        	if(!ObjectUtils.equals(tb.getTkTimeBlockId(), tdaf.getTkTimeBlockId())) {
640 	        		finalNewTimeBlocks.add(tb);
641 	        	}
642         	} else {
643         		finalNewTimeBlocks.add(tb);
644         	}
645         }
646 
647         //reset time block
648         TkServiceLocator.getTimesheetService().resetTimeBlock(finalNewTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
649 
650         // apply overtime pref
651         // I changed start and end times comparison below. it used to be overtimeBeginTimestamp and overtimeEndTimestamp but
652         // for some reason, they're always null because, we have removed the time block before getting here. KPME-2162
653         if(StringUtils.isNotEmpty(tdaf.getOvertimePref())) {
654 	        for (TimeBlock tb : finalNewTimeBlocks) {
655 	        	if ((StringUtils.isNotEmpty(tdaf.getTkTimeBlockId()) && tdaf.getTkTimeBlockId().equals(tb.getTkTimeBlockId()))
656 	        		|| (tb.getBeginTimestamp().equals(startTime) && tb.getEndTimestamp().equals(endTime))) {
657 	                tb.setOvertimePref(tdaf.getOvertimePref());
658 	            }
659 	        }
660         }
661 
662 		List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
663         List<String> assignmentKeys = new ArrayList<String>();
664         for (Assignment assignment : assignments) {
665             	assignmentKeys.add(assignment.getAssignmentKey());
666         }
667 
668         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
669 
670         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, finalNewTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
671 
672         TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, finalNewTimeBlocks, HrContext.getPrincipalId());
673         
674         generateTimesheetChangedNotification(HrContext.getPrincipalId(), HrContext.getTargetPrincipalId(), tdaf.getDocumentId());
675         
676 	}
677 	
678 	/**
679 	 * 
680 	 * Callers must first run Time Entry validations on tdaf.
681 	 * 
682 	 * @param tdaf
683 	 */
684 	// KPME-2386
685 	private void updateLeaveBlock(TimeDetailActionForm tdaf) throws Exception {
686 
687 		String principalId = HrContext.getPrincipalId();
688 		String targetPrincipalId = HrContext.getTargetPrincipalId();
689 
690 		String selectedEarnCode = tdaf.getSelectedEarnCode();
691 		String leaveBlockId = tdaf.getLmLeaveBlockId();
692 		
693 		LeaveBlock updatedLeaveBlock = null;
694 		updatedLeaveBlock = LmServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
695 		
696 		//	KPME-3070: Code for creating new time block and deleting existing leave block starts here
697 		EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(tdaf.getSelectedEarnCode(), TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate()).toLocalDate());
698 		if (ec == null || ec.getLeavePlan() == null) {
699 			//	delete leave block code will come here
700 			LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlockId, HrContext.getPrincipalId());
701 			
702 			// time blocks changes
703 			List<String> errors = TimeDetailValidationUtil.validateTimeEntryDetails(tdaf);
704 			if (errors.isEmpty()) {
705 				// validate leave entry prior to save.
706 				this.changeTimeBlocks(tdaf);
707 			} else {
708 				tdaf.setErrorMessages(errors);
709 			}
710 			
711 			ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
712 			ActionRedirect redirect = new ActionRedirect();
713 			redirect.setPath("/TimeDetail.do");
714 			redirect.addParameter("documentId", tdaf.getDocumentId());
715 			return;
716 		}
717 
718 		//	Code for creating new time block ends here
719 		
720         if (updatedLeaveBlock.isEditable()) {
721             if (!updatedLeaveBlock.getLeaveAmount().equals(tdaf.getLeaveAmount())) {
722                 updatedLeaveBlock.setLeaveAmount(tdaf.getLeaveAmount());
723                 Assignment assignment = tdaf.getTimesheetDocument().getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));
724                 updatedLeaveBlock.setAssignmentKey(tdaf.getSelectedAssignment());
725                 updatedLeaveBlock.setJobNumber(assignment.getJobNumber());
726                 updatedLeaveBlock.setWorkArea(assignment.getWorkArea());
727                 updatedLeaveBlock.setTask(assignment.getTask());
728             }
729             
730             DateTime beginDate = null;
731     		DateTime endDate = null;
732             
733             EarnCode earnCode =  HrServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, updatedLeaveBlock.getLeaveLocalDate()); // selectedEarnCode = hrEarnCodeId
734             if(earnCode != null && earnCode.getRecordMethod().equalsIgnoreCase(HrConstants.EARN_CODE_TIME)) {
735             	if(tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
736         			beginDate = TKUtils.convertDateStringToDateTimeWithoutZone(tdaf.getStartDate(), tdaf.getStartTime());
737         			endDate   = TKUtils.convertDateStringToDateTimeWithoutZone(tdaf.getEndDate(), tdaf.getEndTime());
738         		}  else {
739         			beginDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getStartDate());
740         			endDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate());
741         		}
742             	updatedLeaveBlock.setBeginTimestamp(new Timestamp(beginDate.getMillis()));
743             	updatedLeaveBlock.setEndTimestamp(new Timestamp(endDate.getMillis()));
744             	updatedLeaveBlock.setLeaveAmount(TKUtils.getHoursBetween(beginDate.getMillis(), endDate.getMillis()));
745             }
746             
747             if (!updatedLeaveBlock.getEarnCode().equals(earnCode.getEarnCode())) {
748                 updatedLeaveBlock.setEarnCode(earnCode.getEarnCode());
749             }
750             
751             LmServiceLocator.getLeaveBlockService().updateLeaveBlock(updatedLeaveBlock, principalId);
752         }
753         
754         List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
755         List<String> assignmentKeys = new ArrayList<String>();
756         for (Assignment assignment : assignments) {
757             assignmentKeys.add(assignment.getAssignmentKey());
758         }
759         List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(targetPrincipalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
760 
761         // A bad hack to apply rules to all timeblocks on timesheet
762 		List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
763         // We need a  cloned reference set so we know whether or not to
764         // persist any potential changes without making hundreds of DB calls.
765         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks.size());
766         for (TimeBlock tb : newTimeBlocks) {
767             referenceTimeBlocks.add(tb.copy());
768         }
769 		TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), principalId);
770 		//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?
771 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, principalId);
772    	 	generateTimesheetChangedNotification(principalId, targetPrincipalId, tdaf.getDocumentId());
773 
774     }
775 
776 	//No time blocks should be saved directly in this action forward without first validating the entry.
777     public ActionForward updateTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
778 
779         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
780         Assignment assignment = tdaf.getTimesheetDocument().getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));
781 
782         //Grab timeblock to be updated from form
783         List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
784         TimeBlock updatedTimeBlock = null;
785         List<TimeHourDetail> oldDetailList = new ArrayList<TimeHourDetail>();
786         String oldAssignmenString = "";
787         for (TimeBlock tb : timeBlocks) {
788             if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
789             	oldDetailList = tb.getTimeHourDetails();
790             	oldAssignmenString = tb.getAssignmentKey();
791                 updatedTimeBlock = tb;
792                 tb.setJobNumber(assignment.getJobNumber());
793                 tb.setWorkArea(assignment.getWorkArea());
794                 tb.setTask(assignment.getTask());
795                 break;
796             }
797         }
798         
799         AssignmentDescriptionKey assignKey = AssignmentDescriptionKey.get(oldAssignmenString);
800         Assignment oldAssignment = HrServiceLocator.getAssignmentService().getAssignment(updatedTimeBlock.getPrincipalId(), assignKey, new LocalDate(updatedTimeBlock.getBeginDate()));
801         String oldRegEarnCode = oldAssignment.getJob().getPayTypeObj().getRegEarnCode();
802         
803         List<TimeHourDetail> tempList = new ArrayList<TimeHourDetail>();
804         tempList.addAll(oldDetailList);
805         for(TimeHourDetail thd : tempList) {
806         	// remove rule created details from old time block
807         	if(!thd.getEarnCode().equals(oldRegEarnCode)) {
808         	    oldDetailList.remove(thd);
809         	}
810         }
811         
812         Set<String> earnCodes = new HashSet<String>();
813         if (updatedTimeBlock != null) {
814             List<EarnCode> validEarnCodes = TkServiceLocator.getTimesheetService().getEarnCodesForTime(assignment, updatedTimeBlock.getBeginDateTime().toLocalDate(), true);
815             for (EarnCode e : validEarnCodes) {
816                 earnCodes.add(e.getEarnCode());
817             }
818         }
819 
820         if (updatedTimeBlock != null
821         		&& earnCodes.contains(updatedTimeBlock.getEarnCode())) {
822         	List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
823             List<String> assignmentKeys = new ArrayList<String>();
824             for (Assignment assign : assignments) {
825                 	assignmentKeys.add(assign.getAssignmentKey());
826             }        	
827             List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
828 
829         	TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, timeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
830         }
831         
832         //addTimeBlock handles validation and creation of object. Do not save time blocks directly in this method without validating the entry!
833         tdaf.setMethodToCall("addTimeBlock");
834 
835         return mapping.findForward("basic");
836     }
837 
838 
839     public ActionForward actualTimeInquiry(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
840         return mapping.findForward("ati");
841     }
842 
843     public ActionForward deleteLunchDeduction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
844 
845         TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
846         String timeHourDetailId = tdaf.getTkTimeHourDetailId();
847         
848         List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
849         TimeHourDetail thd = TkServiceLocator.getTimeHourDetailService().getTimeHourDetail(timeHourDetailId);
850         for(TimeBlock tb : newTimeBlocks) {
851         	if(tb.getTkTimeBlockId().equals(thd.getTkTimeBlockId())) {
852 	        	// mark the lunch deleted as Y
853 	            tb.setLunchDeleted(true);
854         	}
855         }
856        // remove the related time hour detail row with the lunch deduction
857        TkServiceLocator.getTimeHourDetailService().removeTimeHourDetail(thd.getTkTimeHourDetailId());
858         
859        List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
860        List<String> assignmentKeys = new ArrayList<String>();
861        for (Assignment assignment : assignments) {
862            	assignmentKeys.add(assignment.getAssignmentKey());
863        }
864        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
865 
866        TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
867         
868         // KPME-1340
869         TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
870         TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks);
871         tdaf.getTimesheetDocument().setTimeBlocks(newTimeBlocks);
872         
873         return mapping.findForward("basic");
874     }
875   
876   public ActionForward deleteLeaveBlock(ActionMapping mapping,ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
877 	  TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
878 	  
879 	  String principalId = HrContext.getPrincipalId();
880 	  String targetPrincipalId = HrContext.getTargetPrincipalId();
881 	  String documentId = tdaf.getDocumentId();
882 	  String leaveBlockId = tdaf.getLmLeaveBlockId();
883 
884       LeaveBlock blockToDelete = LmServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
885       if (blockToDelete != null && LmServiceLocator.getLMPermissionService().canDeleteLeaveBlock(HrContext.getPrincipalId(), blockToDelete)) {
886 		    LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlockId, HrContext.getPrincipalId());
887 
888           // A bad hack to apply rules to all timeblocks on timesheet
889           List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
890           // We need a  cloned reference set so we know whether or not to
891           // persist any potential changes without making hundreds of DB calls.
892           List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks.size());
893           for (TimeBlock tb : newTimeBlocks) {
894               referenceTimeBlocks.add(tb.copy());
895           }
896           List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
897           List<String> assignmentKeys = new ArrayList<String>();
898           for (Assignment assignment : assignments) {
899               assignmentKeys.add(assignment.getAssignmentKey());
900           }
901           List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
902 
903           //reset time block
904           TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
905           TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
906           TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, HrContext.getPrincipalId());
907           generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
908       }
909 
910       // if the leave block is NOT eligible for accrual, rerun accrual service for the leave calendar the leave block is on
911       EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(blockToDelete.getEarnCode(), blockToDelete.getLeaveLocalDate());
912       if(ec != null && ec.getEligibleForAccrual().equals("N")) {
913     	  CalendarEntry ce = HrServiceLocator.getCalendarEntryService()
914 					.getCurrentCalendarDatesForLeaveCalendar(blockToDelete.getPrincipalId(), blockToDelete.getLeaveLocalDate().toDateTimeAtStartOfDay());
915     	  if(ce != null) {
916     		  LmServiceLocator.getLeaveAccrualService().runAccrual(blockToDelete.getPrincipalId(), ce.getBeginPeriodFullDateTime().toDateTime(), ce.getEndPeriodFullDateTime().toDateTime(), false);
917     	  }
918       }
919 		
920       return mapping.findForward("basic");
921 	}
922   
923 	private void generateTimesheetChangedNotification(String principalId, String targetPrincipalId, String documentId) {
924 		if (!StringUtils.equals(principalId, targetPrincipalId)) {
925 			EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
926 			if (person != null && person.getDefaultName() != null) {
927 				String subject = "Timesheet Modification Notice";
928 				StringBuilder message = new StringBuilder();
929 				message.append("Your Timesheet was changed by ");
930 				message.append(person.getDefaultName().getCompositeNameUnmasked());
931 				message.append(" on your behalf.");
932 				message.append(SystemUtils.LINE_SEPARATOR);
933 				message.append(getTimesheetURL(documentId));
934 
935 				HrServiceLocator.getKPMENotificationService().sendNotification(subject, message.toString(), targetPrincipalId);
936 			}
937 		}
938 	}
939 
940 	@SuppressWarnings("deprecation")
941 	private String getTimesheetURL(String documentId) {
942 		Properties params = new Properties();
943 		params.put("documentId", documentId);
944 		return UrlFactory.parameterizeUrl(getApplicationBaseUrl() + "/TimeDetail.do", params);
945 	}
946 
947 	@Override
948 	protected String generateToken(HttpServletRequest request) {
949 		return super.generateToken(request);
950 	}
951 
952 	@Override
953 	protected boolean isTokenValid(HttpServletRequest request, boolean reset) {
954 		return super.isTokenValid(request, reset);
955 	}
956 
957 	@Override
958 	protected void resetToken(HttpServletRequest request) {
959 		super.resetToken(request);
960 	}
961 
962 	@Override
963 	protected void saveToken(HttpServletRequest request) {
964 		super.saveToken(request);
965 	}
966 
967 }