001    /**
002     * Copyright 2004-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.hr.time.detail.web;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.sql.Timestamp;
021    import java.text.SimpleDateFormat;
022    import java.util.ArrayList;
023    import java.util.Collection;
024    import java.util.Collections;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Set;
030    import java.util.Map.Entry;
031    
032    import javax.servlet.http.HttpServletRequest;
033    import javax.servlet.http.HttpServletResponse;
034    
035    import org.apache.commons.lang.StringUtils;
036    import org.apache.commons.lang.time.DateUtils;
037    import org.apache.struts.action.ActionForm;
038    import org.apache.struts.action.ActionForward;
039    import org.apache.struts.action.ActionMapping;
040    import org.joda.time.DateTime;
041    import org.joda.time.Interval;
042    import org.kuali.hr.lm.LMConstants;
043    import org.kuali.hr.lm.accrual.AccrualCategory;
044    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
045    import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
046    import org.kuali.hr.lm.balancetransfer.validation.BalanceTransferValidationUtils;
047    import org.kuali.hr.lm.leaveSummary.LeaveSummary;
048    import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
049    import org.kuali.hr.lm.leaveblock.LeaveBlock;
050    import org.kuali.hr.lm.leavecalendar.validation.LeaveCalendarValidationUtil;
051    import org.kuali.hr.lm.util.LeaveBlockAggregate;
052    import org.kuali.hr.time.assignment.Assignment;
053    import org.kuali.hr.time.calendar.Calendar;
054    import org.kuali.hr.time.calendar.CalendarEntries;
055    import org.kuali.hr.time.calendar.TkCalendar;
056    import org.kuali.hr.time.earncode.EarnCode;
057    import org.kuali.hr.time.principal.PrincipalHRAttributes;
058    import org.kuali.hr.time.roles.TkUserRoles;
059    import org.kuali.hr.time.roles.UserRoles;
060    import org.kuali.hr.time.service.base.TkServiceLocator;
061    import org.kuali.hr.time.timeblock.TimeBlock;
062    import org.kuali.hr.time.timeblock.TimeBlockHistory;
063    import org.kuali.hr.time.timesheet.TimesheetDocument;
064    import org.kuali.hr.time.timesheet.web.TimesheetAction;
065    import org.kuali.hr.time.timesheet.web.TimesheetActionForm;
066    import org.kuali.hr.time.timesummary.AssignmentRow;
067    import org.kuali.hr.time.timesummary.EarnCodeSection;
068    import org.kuali.hr.time.timesummary.EarnGroupSection;
069    import org.kuali.hr.time.timesummary.TimeSummary;
070    import org.kuali.hr.time.util.TKContext;
071    import org.kuali.hr.time.util.TKUser;
072    import org.kuali.hr.time.util.TKUtils;
073    import org.kuali.hr.time.util.TkConstants;
074    import org.kuali.hr.time.util.TkTimeBlockAggregate;
075    import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
076    import org.kuali.rice.kew.service.KEWServiceLocator;
077    import org.kuali.rice.krad.exception.AuthorizationException;
078    import org.kuali.rice.krad.util.GlobalVariables;
079    import org.kuali.rice.krad.util.ObjectUtils;
080    
081    public class TimeDetailAction extends TimesheetAction {
082    
083        @Override
084        protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
085            super.checkTKAuthorization(form, methodToCall); // Checks for read access first.
086            UserRoles roles = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId());
087            TimesheetDocument doc = TKContext.getCurrentTimesheetDocument();
088    
089            // Check for write access to Timeblock.
090            if (StringUtils.equals(methodToCall, "addTimeBlock") || StringUtils.equals(methodToCall, "deleteTimeBlock") || StringUtils.equals(methodToCall, "updateTimeBlock")) {
091                if (!roles.isDocumentWritable(doc)) {
092                    throw new AuthorizationException(roles.getPrincipalId(), "TimeDetailAction", "");
093                }
094            }
095        }
096    
097        @Override
098        public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
099            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    }