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