View Javadoc

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