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