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