org.kuali.kpme.tklm.time.detail.web.TimeDetailAction.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.kpme.tklm.time.detail.web.TimeDetailAction.java

Source

/**
 * Copyright 2004-2013 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.kpme.tklm.time.detail.web;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.kuali.kpme.core.accrualcategory.AccrualCategory;
import org.kuali.kpme.core.accrualcategory.rule.AccrualCategoryRule;
import org.kuali.kpme.core.assignment.Assignment;
import org.kuali.kpme.core.assignment.AssignmentDescriptionKey;
import org.kuali.kpme.core.block.CalendarBlockContract;
import org.kuali.kpme.core.calendar.Calendar;
import org.kuali.kpme.core.calendar.entry.CalendarEntry;
import org.kuali.kpme.core.earncode.EarnCode;
import org.kuali.kpme.core.principal.PrincipalHRAttributes;
import org.kuali.kpme.core.service.HrServiceLocator;
import org.kuali.kpme.core.util.HrConstants;
import org.kuali.kpme.core.util.HrContext;
import org.kuali.kpme.core.util.TKUtils;
import org.kuali.kpme.tklm.common.LMConstants;
import org.kuali.kpme.tklm.common.TkConstants;
import org.kuali.kpme.tklm.leave.block.LeaveBlock;
import org.kuali.kpme.tklm.leave.block.LeaveBlockAggregate;
import org.kuali.kpme.tklm.leave.calendar.validation.LeaveCalendarValidationUtil;
import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
import org.kuali.kpme.tklm.leave.transfer.BalanceTransfer;
import org.kuali.kpme.tklm.leave.transfer.validation.BalanceTransferValidationUtils;
import org.kuali.kpme.tklm.time.calendar.TkCalendar;
import org.kuali.kpme.tklm.time.service.TkServiceLocator;
import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
import org.kuali.kpme.tklm.time.timeblock.TimeBlockHistory;
import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
import org.kuali.kpme.tklm.time.timesheet.web.TimesheetAction;
import org.kuali.kpme.tklm.time.timesummary.AssignmentColumn;
import org.kuali.kpme.tklm.time.timesummary.AssignmentRow;
import org.kuali.kpme.tklm.time.timesummary.EarnCodeSection;
import org.kuali.kpme.tklm.time.timesummary.EarnGroupSection;
import org.kuali.kpme.tklm.time.timesummary.TimeSummary;
import org.kuali.kpme.tklm.time.util.TkContext;
import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
import org.kuali.rice.kew.api.KewApiServiceLocator;
import org.kuali.rice.kew.api.document.DocumentStatus;
import org.kuali.rice.kew.service.KEWServiceLocator;
import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.krad.exception.AuthorizationException;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.UrlFactory;

public class TimeDetailAction extends TimesheetAction {

    @Override
    protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
        super.checkTKAuthorization(form, methodToCall);

        TimeDetailActionForm timeDetailActionForm = (TimeDetailActionForm) form;

        String principalId = GlobalVariables.getUserSession().getPrincipalId();
        TimesheetDocument timesheetDocument = TkServiceLocator.getTimesheetService()
                .getTimesheetDocument(timeDetailActionForm.getDocumentId());
        if (StringUtils.equals(methodToCall, "addTimeBlock") || StringUtils.equals(methodToCall, "deleteTimeBlock")
                || StringUtils.equals(methodToCall, "updateTimeBlock")) {
            if (!HrServiceLocator.getHRPermissionService().canEditCalendarDocument(principalId,
                    timesheetDocument)) {
                throw new AuthorizationException(principalId, "TimeDetailAction", "");
            }
        }
    }

    @Override
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        ActionForward forward = super.execute(mapping, form, request, response);

        TimeDetailActionForm timeDetailActionForm = (TimeDetailActionForm) form;

        CalendarEntry calendarEntry = timeDetailActionForm.getCalendarEntry();
        TimesheetDocument timesheetDocument = timeDetailActionForm.getTimesheetDocument();

        if (calendarEntry != null && timesheetDocument != null) {
            List<String> assignmentKeys = new ArrayList<String>();
            for (Assignment assignment : timesheetDocument.getAssignments()) {
                assignmentKeys.add(assignment.getAssignmentKey());
            }

            if (timesheetDocument != null) {
                timeDetailActionForm.setAssignmentDescriptions(
                        timeDetailActionForm.getTimesheetDocument().getAssignmentDescriptions(false));
            }

            timeDetailActionForm.setDocEditable("false");
            if (HrContext.isSystemAdmin()) {
                timeDetailActionForm.setDocEditable("true");
            } else {
                DocumentStatus documentStatus = KewApiServiceLocator.getWorkflowDocumentService()
                        .getDocumentStatus(timeDetailActionForm.getDocumentId());
                if (!DocumentStatus.FINAL.equals(documentStatus)
                        && !DocumentStatus.CANCELED.getCode().equals(documentStatus)
                        && !DocumentStatus.DISAPPROVED.getCode().equals(documentStatus)) {
                    if (StringUtils.equals(timesheetDocument.getPrincipalId(),
                            GlobalVariables.getUserSession().getPrincipalId()) || HrContext.isSystemAdmin()
                            || TkContext.isLocationAdmin() || TkContext.isDepartmentAdmin()
                            || HrContext.isReviewer() || HrContext.isAnyApprover()) {
                        timeDetailActionForm.setDocEditable("true");
                    }

                    //if the timesheet has been approved by at least one of the approvers, the employee should not be able to edit it
                    if (StringUtils.equals(timesheetDocument.getPrincipalId(),
                            GlobalVariables.getUserSession().getPrincipalId())
                            && timesheetDocument.getDocumentHeader().getDocumentStatus()
                                    .equals(HrConstants.ROUTE_STATUS.ENROUTE)) {
                        Collection actions = KEWServiceLocator.getActionTakenService().findByDocIdAndAction(
                                timesheetDocument.getDocumentHeader().getDocumentId(),
                                HrConstants.DOCUMENT_ACTIONS.APPROVE);
                        if (!actions.isEmpty()) {
                            timeDetailActionForm.setDocEditable("false");
                        }
                    }
                }
            }

            List<TimeBlock> timeBlocks = TkServiceLocator.getTimesheetService()
                    .getTimesheetDocument(timeDetailActionForm.getDocumentId()).getTimeBlocks();
            List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(
                    timesheetDocument.getPrincipalId(), calendarEntry.getBeginPeriodFullDateTime().toLocalDate(),
                    calendarEntry.getEndPeriodFullDateTime().toLocalDate(), assignmentKeys);

            timeDetailActionForm.getTimesheetDocument().setTimeBlocks(timeBlocks);
            assignStypeClassMapForTimeSummary(timeDetailActionForm, timeBlocks, leaveBlocks);

            Calendar payCalendar = HrServiceLocator.getCalendarService()
                    .getCalendar(calendarEntry != null ? calendarEntry.getHrCalendarId() : null);
            List<Interval> intervals = TKUtils.getFullWeekDaySpanForCalendarEntry(calendarEntry);
            LeaveBlockAggregate lbAggregate = new LeaveBlockAggregate(leaveBlocks, calendarEntry, intervals);
            TkTimeBlockAggregate tbAggregate = new TkTimeBlockAggregate(timeBlocks, calendarEntry, payCalendar,
                    true, intervals);
            // use both time aggregate and leave aggregate to populate the calendar
            TkCalendar cal = TkCalendar.getCalendar(tbAggregate, lbAggregate);
            cal.assignAssignmentStyle(timeDetailActionForm.getAssignStyleClassMap());
            timeDetailActionForm.setTkCalendar(cal);

            timeDetailActionForm
                    .setTimeBlockString(ActionFormUtils.getTimeBlocksJson(tbAggregate.getFlattenedTimeBlockList()));
            timeDetailActionForm.setLeaveBlockString(
                    ActionFormUtils.getLeaveBlocksJson(lbAggregate.getFlattenedLeaveBlockList()));

            timeDetailActionForm.setOvertimeEarnCodes(HrServiceLocator.getEarnCodeService()
                    .getOvertimeEarnCodesStrs(timesheetDocument.getAsOfDate()));

            if (StringUtils.equals(timesheetDocument.getPrincipalId(),
                    GlobalVariables.getUserSession().getPrincipalId())) {
                timeDetailActionForm.setWorkingOnItsOwn("true");
            }

            setMessages(timeDetailActionForm);
        }

        return forward;
    }

    // use lists of time blocks and leave blocks to build the style class map and assign css class to associated summary rows
    private void assignStypeClassMapForTimeSummary(TimeDetailActionForm tdaf, List<TimeBlock> timeBlocks,
            List<LeaveBlock> leaveBlocks) throws Exception {
        TimeSummary ts = TkServiceLocator.getTimeSummaryService().getTimeSummary(tdaf.getTimesheetDocument());
        tdaf.setAssignStyleClassMap(ActionFormUtils.buildAssignmentStyleClassMap(timeBlocks, leaveBlocks));
        Map<String, String> aMap = tdaf.getAssignStyleClassMap();
        // set css classes for each assignment row
        for (EarnGroupSection earnGroupSection : ts.getSections()) {
            for (EarnCodeSection section : earnGroupSection.getEarnCodeSections()) {
                for (AssignmentRow assignRow : section.getAssignmentsRows()) {
                    String assignmentCssStyle = MapUtils.getString(aMap, assignRow.getAssignmentKey());
                    assignRow.setCssClass(assignmentCssStyle);
                    for (AssignmentColumn assignmentColumn : assignRow.getAssignmentColumns()) {
                        assignmentColumn.setCssClass(assignmentCssStyle);
                    }
                }
            }

        }
        tdaf.setTimeSummary(ts);
        //  ActionFormUtils.validateHourLimit(tdaf);
        ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
        ActionFormUtils.addUnapprovedIPWarningFromClockLog(tdaf);
    }

    protected void setMessages(TimeDetailActionForm timeDetailActionForm) {
        String principalId = HrContext.getTargetPrincipalId();
        TimesheetDocument timesheetDocument = timeDetailActionForm.getTimesheetDocument();
        CalendarEntry calendarEntry = timeDetailActionForm.getCalendarEntry();

        List<LeaveBlock> balanceTransferLeaveBlocks = LmServiceLocator.getLeaveBlockService()
                .getLeaveBlocksWithType(timesheetDocument.getPrincipalId(),
                        calendarEntry.getBeginPeriodFullDateTime().toLocalDate(),
                        calendarEntry.getEndPeriodFullDateTime().toLocalDate(),
                        LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);

        Map<String, Set<String>> allMessages = LeaveCalendarValidationUtil
                .getWarningMessagesForLeaveBlocks(balanceTransferLeaveBlocks);

        // add warning messages based on max carry over balances for each accrual category for non-exempt leave users
        List<BalanceTransfer> losses = new ArrayList<BalanceTransfer>();
        if (LmServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId,
                HrConstants.FLSA_STATUS_NON_EXEMPT, true)) {
            PrincipalHRAttributes principalCalendar = HrServiceLocator.getPrincipalHRAttributeService()
                    .getPrincipalCalendar(principalId, calendarEntry.getEndPeriodFullDateTime().toLocalDate());

            Interval calendarInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),
                    calendarEntry.getEndPeriodDate().getTime());
            Map<String, Set<LeaveBlock>> maxBalInfractions = new HashMap<String, Set<LeaveBlock>>();

            if (principalCalendar != null) {
                maxBalInfractions = LmServiceLocator.getAccrualCategoryMaxBalanceService()
                        .getMaxBalanceViolations(calendarEntry, principalId);

                for (Entry<String, Set<LeaveBlock>> entry : maxBalInfractions.entrySet()) {
                    for (LeaveBlock lb : entry.getValue()) {
                        if (calendarInterval.contains(lb.getLeaveDate().getTime())) {
                            AccrualCategory accrualCat = HrServiceLocator.getAccrualCategoryService()
                                    .getAccrualCategory(lb.getAccrualCategory(), lb.getLeaveLocalDate());
                            AccrualCategoryRule aRule = HrServiceLocator.getAccrualCategoryRuleService()
                                    .getAccrualCategoryRule(lb.getAccrualCategoryRuleId());
                            if (StringUtils.equals(aRule.getActionAtMaxBalance(),
                                    HrConstants.ACTION_AT_MAX_BALANCE.LOSE)) {
                                DateTime aDate = null;
                                if (StringUtils.equals(aRule.getMaxBalanceActionFrequency(),
                                        HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
                                    aDate = HrServiceLocator.getLeavePlanService().getRolloverDayOfLeavePlan(
                                            principalCalendar.getLeavePlan(), lb.getLeaveLocalDate());
                                } else {
                                    Calendar cal = HrServiceLocator.getCalendarService()
                                            .getCalendarByPrincipalIdAndDate(principalId,
                                                    new LocalDate(lb.getLeaveDate()), true);
                                    CalendarEntry leaveEntry = HrServiceLocator.getCalendarEntryService()
                                            .getCurrentCalendarEntryByCalendarId(cal.getHrCalendarId(),
                                                    new DateTime(lb.getLeaveDate()));
                                    aDate = new DateTime(leaveEntry.getEndPeriodDate());
                                }
                                aDate = aDate.minusDays(1);
                                if (calendarInterval.contains(aDate.getMillis())
                                        && aDate.toDate().compareTo(calendarEntry.getEndPeriodDate()) <= 0) {
                                    //may want to calculate summary for all rows, displayable or not, and determine displayability via tags.
                                    AccrualCategory accrualCategory = HrServiceLocator.getAccrualCategoryService()
                                            .getAccrualCategory(aRule.getLmAccrualCategoryId());
                                    BigDecimal accruedBalance = LmServiceLocator.getAccrualService()
                                            .getAccruedBalanceForPrincipal(principalId, accrualCategory,
                                                    lb.getLeaveLocalDate());

                                    BalanceTransfer loseTransfer = LmServiceLocator.getBalanceTransferService()
                                            .initializeTransfer(principalId, lb.getAccrualCategoryRuleId(),
                                                    accruedBalance, lb.getLeaveLocalDate());
                                    boolean valid = BalanceTransferValidationUtils.validateTransfer(loseTransfer);
                                    if (valid) {
                                        losses.add(loseTransfer);
                                    }
                                }
                            }

                            // accrual categories within the leave plan that are hidden from the leave summary WILL appear.
                            String message = "You have exceeded the maximum balance limit for '"
                                    + accrualCat.getAccrualCategory() + "' as of " + lb.getLeaveDate() + ". "
                                    + "Depending upon the accrual category rules, leave over this limit may be forfeited.";
                            //  leave blocks are sorted in getMaxBalanceViolations() method, so we just take the one with the earliest leave date for an accrual category.
                            if (!StringUtils.contains(allMessages.get("warningMessages").toString(),
                                    "You have exceeded the maximum balance limit for '"
                                            + accrualCat.getAccrualCategory())) {
                                allMessages.get("warningMessages").add(message);
                            }
                        }
                    }
                }
            }
            timeDetailActionForm.setForfeitures(losses);

            Map<String, Set<String>> transactionalMessages = LeaveCalendarValidationUtil
                    .validatePendingTransactions(HrContext.getTargetPrincipalId(),
                            calendarEntry.getBeginPeriodFullDateTime().toLocalDate(),
                            calendarEntry.getEndPeriodFullDateTime().toLocalDate());
            allMessages.get("infoMessages").addAll(transactionalMessages.get("infoMessages"));
            allMessages.get("warningMessages").addAll(transactionalMessages.get("warningMessages"));
            allMessages.get("actionMessages").addAll(transactionalMessages.get("actionMessages"));

            if (principalCalendar != null) {
                Calendar calendar = HrServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(
                        principalId, calendarEntry.getEndPeriodFullDateTime().toLocalDate(), true);

                if (calendar != null) {
                    List<CalendarEntry> leaveCalendarEntries = HrServiceLocator.getCalendarEntryService()
                            .getCalendarEntriesEndingBetweenBeginAndEndDate(calendar.getHrCalendarId(),
                                    calendarEntry.getBeginPeriodFullDateTime(),
                                    calendarEntry.getEndPeriodFullDateTime());

                    List<AccrualCategory> accrualCategories = HrServiceLocator.getAccrualCategoryService()
                            .getActiveLeaveAccrualCategoriesForLeavePlan(principalCalendar.getLeavePlan(),
                                    calendarEntry.getEndPeriodFullDateTime().toLocalDate());
                    for (AccrualCategory accrualCategory : accrualCategories) {
                        if (LmServiceLocator.getAccrualCategoryMaxCarryOverService()
                                .exceedsAccrualCategoryMaxCarryOver(accrualCategory.getAccrualCategory(),
                                        principalId, leaveCalendarEntries,
                                        calendarEntry.getEndPeriodFullDateTime().toLocalDate())) {
                            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.";
                            if (!allMessages.get("warningMessages").contains(message)) {
                                allMessages.get("warningMessages").add(message);
                            }
                        }
                    }
                }
            }
        }

        List<String> infoMessages = timeDetailActionForm.getInfoMessages();
        infoMessages.addAll(allMessages.get("infoMessages"));

        List<String> warningMessages = timeDetailActionForm.getWarningMessages();
        warningMessages.addAll(allMessages.get("warningMessages"));

        List<String> actionMessages = timeDetailActionForm.getActionMessages();
        actionMessages.addAll(allMessages.get("actionMessages"));

        timeDetailActionForm.setInfoMessages(infoMessages);
        timeDetailActionForm.setWarningMessages(warningMessages);
        timeDetailActionForm.setActionMessages(actionMessages);
    }

    /**
     * This method involves creating an object-copy of every TimeBlock on the
     * time sheet for overtime re-calculation.
     *
     * @throws Exception
     */
    public ActionForward deleteTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        TimeDetailActionForm tdaf = (TimeDetailActionForm) form;

        String principalId = HrContext.getPrincipalId();
        String targetPrincipalId = HrContext.getTargetPrincipalId();
        String documentId = tdaf.getDocumentId();

        //Grab timeblock to be deleted from form
        List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
        TimeBlock deletedTimeBlock = null;
        for (TimeBlock tb : timeBlocks) {
            if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
                deletedTimeBlock = tb;
                break;
            }
        }
        if (deletedTimeBlock == null) {
            return mapping.findForward("basic");
        }
        //Remove from the list of timeblocks
        List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(
                tdaf.getTimesheetDocument().getTimeBlocks().size());
        for (TimeBlock b : tdaf.getTimesheetDocument().getTimeBlocks()) {
            referenceTimeBlocks.add(b.copy());
        }

        // simple pointer, for clarity
        List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
        newTimeBlocks.remove(deletedTimeBlock);

        //Delete timeblock
        TkServiceLocator.getTimeBlockService().deleteTimeBlock(deletedTimeBlock);
        // Add a row to the history table
        TimeBlockHistory tbh = new TimeBlockHistory(deletedTimeBlock);
        tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
        TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);

        List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
        List<String> assignmentKeys = new ArrayList<String>();
        for (Assignment assignment : assignments) {
            assignmentKeys.add(assignment.getAssignmentKey());
        }
        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(
                principalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(),
                assignmentKeys);

        //reset time block
        TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks,
                tdaf.getTimesheetDocument().getAsOfDate());
        TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks,
                leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
        TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks,
                HrContext.getPrincipalId());

        generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);

        return mapping.findForward("basic");
    }

    /**
     * This method involves creating an object-copy of every TimeBlock on the
     * time sheet for overtime re-calculation.
     * Based on the form's timeBlockId or leaveBlockId, existing Time/Leave blocks will be deleted and new ones created
     *
     * @throws Exception
     */
    public ActionForward addTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        TimeDetailActionForm tdaf = (TimeDetailActionForm) form;

        String principalId = HrContext.getPrincipalId();
        String targetPrincipalId = HrContext.getTargetPrincipalId();
        String documentId = tdaf.getDocumentId();

        if (StringUtils.isNotEmpty(tdaf.getTkTimeBlockId())) {
            // the user is changing an existing time block, so need to delete this time block
            //           this.removeOldTimeBlock(tdaf);
        } else if (StringUtils.isNotEmpty(tdaf.getLmLeaveBlockId())) {
            // the user is changing an existing leave block, so need to delete this leave block
            //           this.removeOldLeaveBlock(tdaf.getLmLeaveBlockId());

            this.updateLeaveBlock(tdaf);
            generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
            return mapping.findForward("basic");

        }
        if (StringUtils.isNotEmpty(tdaf.getSelectedEarnCode())) {
            EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(tdaf.getSelectedEarnCode(),
                    tdaf.getTimesheetDocument().getAsOfDate());
            if (ec != null && (ec.getLeavePlan() != null || ec.getEligibleForAccrual().equals("N"))) { // leave blocks changes
                this.changeLeaveBlocks(tdaf);
            } else { // time blocks changes
                this.changeTimeBlocks(tdaf);
            }
        }

        // ActionFormUtils.validateHourLimit(tdaf);
        ActionFormUtils.addWarningTextFromEarnGroup(tdaf);

        generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);

        return mapping.findForward("basic");
    }

    private void removeOldTimeBlock(TimeDetailActionForm tdaf) {
        if (tdaf.getTkTimeBlockId() != null) {
            TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
            if (tb != null) {
                TimeBlockHistory tbh = new TimeBlockHistory(tb);
                TkServiceLocator.getTimeBlockService().deleteTimeBlock(tb);

                // mark the original timeblock as deleted in the history table
                tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
                TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);

                // delete the timeblock from the memory
                tdaf.getTimesheetDocument().getTimeBlocks().remove(tb);
            }
        }
    }

    private void removeOldLeaveBlock(String lbId) {
        if (lbId != null) {
            CalendarBlockContract lb = LmServiceLocator.getLeaveBlockService().getLeaveBlock(lbId);
            if (lb != null) {
                LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(lbId, HrContext.getPrincipalId());
            }
        }
    }

    // add/update leave blocks 
    private void changeLeaveBlocks(TimeDetailActionForm tdaf) {
        DateTime beginDate = null;
        DateTime endDate = null;

        if (tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
            beginDate = TKUtils.convertDateStringToDateTime(tdaf.getStartDate(), tdaf.getStartTime());
            endDate = TKUtils.convertDateStringToDateTime(tdaf.getEndDate(), tdaf.getEndTime());
        } else {
            beginDate = TKUtils.formatDateTimeString(tdaf.getStartDate());
            endDate = TKUtils.formatDateTimeString(tdaf.getEndDate());
        }

        String selectedEarnCode = tdaf.getSelectedEarnCode();
        BigDecimal leaveAmount = tdaf.getLeaveAmount();

        String desc = ""; // there's no description field in time calendar pop window
        String spanningWeeks = tdaf.getSpanningWeeks();
        Assignment currentAssignment = tdaf.getTimesheetDocument()
                .getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));
        LmServiceLocator.getLeaveBlockService().addLeaveBlocks(beginDate, endDate, tdaf.getCalendarEntry(),
                selectedEarnCode, leaveAmount, desc, currentAssignment, spanningWeeks,
                LMConstants.LEAVE_BLOCK_TYPE.TIME_CALENDAR, HrContext.getTargetPrincipalId());

        List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
        List<String> assignmentKeys = new ArrayList<String>();
        for (Assignment assignment : assignments) {
            assignmentKeys.add(assignment.getAssignmentKey());
        }
        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(
                HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(),
                tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);

        // A bad hack to apply rules to all timeblocks on timesheet
        List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
        TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks,
                leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
        TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks, newTimeBlocks,
                HrContext.getPrincipalId());
    }

    // add/update time blocks
    private void changeTimeBlocks(TimeDetailActionForm tdaf) {
        DateTime overtimeBeginDateTime = null;
        DateTime overtimeEndDateTime = null;
        boolean isClockLogCreated = false;

        // This is for updating a timeblock or changing
        // 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.
        if (tdaf.getTkTimeBlockId() != null) {
            TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
            if (tb != null) {
                isClockLogCreated = tb.getClockLogCreated();
                if (StringUtils.isNotEmpty(tdaf.getOvertimePref())) {
                    //TODO:  This doesn't do anything!!! these variables are never used.  Should they be?
                    overtimeBeginDateTime = tb.getBeginDateTime();
                    overtimeEndDateTime = tb.getEndDateTime();
                }
            }
            // old time block is deleted from addTimeBlock method
            // this.removeOldTimeBlock(tdaf);
        }

        Assignment currentAssignment = tdaf.getTimesheetDocument()
                .getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));

        // Surgery point - Need to construct a Date/Time with Appropriate Timezone.
        DateTime startTime = TKUtils.convertDateStringToDateTime(tdaf.getStartDate(), tdaf.getStartTime());
        DateTime endTime = TKUtils.convertDateStringToDateTime(tdaf.getEndDate(), tdaf.getEndTime());

        // We need a  cloned reference set so we know whether or not to
        // persist any potential changes without making hundreds of DB calls.
        List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(
                tdaf.getTimesheetDocument().getTimeBlocks().size());
        for (TimeBlock tb : tdaf.getTimesheetDocument().getTimeBlocks()) {
            referenceTimeBlocks.add(tb.copy());
        }

        // This is just a reference, for code clarity, the above list is actually
        // separate at the object level.
        List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
        List<TimeBlock> timeBlocksToAdd = null;
        // KPME-1446 add spanningweeks to the calls below 
        if (StringUtils.equals(tdaf.getAcrossDays(), "y")
                && !(endTime.getDayOfYear() - startTime.getDayOfYear() <= 1 && endTime.getHourOfDay() == 0)) {

            timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocksSpanDates(currentAssignment,
                    tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime, endTime, tdaf.getHours(),
                    tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()),
                    tdaf.getSpanningWeeks(), HrContext.getPrincipalId());

        } else {
            timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocks(currentAssignment,
                    tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime, endTime, tdaf.getHours(),
                    tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()),
                    HrContext.getPrincipalId());
        }

        TimeBlock existingTimeBlock = null;
        TimeBlock timeBlockToUpdate = null;

        if (tdaf.getTkTimeBlockId() != null) {
            timeBlockToUpdate = timeBlocksToAdd.get(0);
            TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(tdaf.getTkTimeBlockId());
            timeBlockToUpdate.setTkTimeBlockId(tdaf.getTkTimeBlockId());
        }

        List<TimeBlock> finalNewTimeBlocks = new ArrayList<TimeBlock>();

        for (TimeBlock tb : newTimeBlocks) {
            if (!ObjectUtils.equals(tb.getTkTimeBlockId(), tdaf.getTkTimeBlockId())) {
                finalNewTimeBlocks.add(tb);
            } else {
                existingTimeBlock = tb;
                existingTimeBlock.copy(timeBlockToUpdate);
                finalNewTimeBlocks.add(existingTimeBlock);
            }
        }

        for (TimeBlock tb : timeBlocksToAdd) {
            if (tdaf.getTkTimeBlockId() != null) {
                if (!ObjectUtils.equals(tb.getTkTimeBlockId(), tdaf.getTkTimeBlockId())) {
                    finalNewTimeBlocks.add(tb);
                }
            } else {
                finalNewTimeBlocks.add(tb);
            }
        }

        //reset time block
        TkServiceLocator.getTimesheetService().resetTimeBlock(finalNewTimeBlocks,
                tdaf.getTimesheetDocument().getAsOfDate());

        // apply overtime pref
        // I changed start and end times comparison below. it used to be overtimeBeginTimestamp and overtimeEndTimestamp but
        // for some reason, they're always null because, we have removed the time block before getting here. KPME-2162
        if (StringUtils.isNotEmpty(tdaf.getOvertimePref())) {
            for (TimeBlock tb : finalNewTimeBlocks) {
                if ((StringUtils.isNotEmpty(tdaf.getTkTimeBlockId())
                        && tdaf.getTkTimeBlockId().equals(tb.getTkTimeBlockId()))
                        || (tb.getBeginTimestamp().equals(startTime) && tb.getEndTimestamp().equals(endTime))) {
                    tb.setOvertimePref(tdaf.getOvertimePref());
                }
            }
        }

        List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
        List<String> assignmentKeys = new ArrayList<String>();
        for (Assignment assignment : assignments) {
            assignmentKeys.add(assignment.getAssignmentKey());
        }

        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(
                HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(),
                tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);

        TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK,
                finalNewTimeBlocks, leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(),
                HrContext.getPrincipalId());

        TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, finalNewTimeBlocks,
                HrContext.getPrincipalId());

    }

    // KPME-2386
    private void updateLeaveBlock(TimeDetailActionForm tdaf) throws Exception {

        String principalId = HrContext.getPrincipalId();
        String targetPrincipalId = HrContext.getTargetPrincipalId();
        CalendarEntry calendarEntry = tdaf.getCalendarEntry();
        String selectedEarnCode = tdaf.getSelectedEarnCode();
        String leaveBlockId = tdaf.getLmLeaveBlockId();

        LeaveBlock updatedLeaveBlock = null;
        updatedLeaveBlock = LmServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
        if (updatedLeaveBlock.isEditable()) {
            if (!updatedLeaveBlock.getLeaveAmount().equals(tdaf.getLeaveAmount())) {
                updatedLeaveBlock.setLeaveAmount(tdaf.getLeaveAmount());
            }

            DateTime beginDate = null;
            DateTime endDate = null;

            EarnCode earnCode = HrServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode,
                    updatedLeaveBlock.getLeaveLocalDate()); // selectedEarnCode = hrEarnCodeId
            if (earnCode != null && earnCode.getRecordMethod().equalsIgnoreCase(HrConstants.EARN_CODE_TIME)) {
                if (tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
                    beginDate = TKUtils.convertDateStringToDateTimeWithoutZone(tdaf.getStartDate(),
                            tdaf.getStartTime());
                    endDate = TKUtils.convertDateStringToDateTimeWithoutZone(tdaf.getEndDate(), tdaf.getEndTime());
                } else {
                    beginDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getStartDate());
                    endDate = TKUtils.formatDateTimeStringNoTimezone(tdaf.getEndDate());
                }
                updatedLeaveBlock.setBeginTimestamp(new Timestamp(beginDate.getMillis()));
                updatedLeaveBlock.setEndTimestamp(new Timestamp(endDate.getMillis()));
                updatedLeaveBlock
                        .setLeaveAmount(TKUtils.getHoursBetween(beginDate.getMillis(), endDate.getMillis()));
            }

            if (!updatedLeaveBlock.getEarnCode().equals(earnCode.getEarnCode())) {
                updatedLeaveBlock.setEarnCode(earnCode.getEarnCode());
            }

            LmServiceLocator.getLeaveBlockService().updateLeaveBlock(updatedLeaveBlock, principalId);
        }

        List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
        List<String> assignmentKeys = new ArrayList<String>();
        for (Assignment assignment : assignments) {
            assignmentKeys.add(assignment.getAssignmentKey());
        }
        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(
                HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(),
                tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);

        // A bad hack to apply rules to all timeblocks on timesheet
        List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
        TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks,
                leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
        TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks, newTimeBlocks,
                HrContext.getPrincipalId());

    }

    public ActionForward updateTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
        Assignment assignment = tdaf.getTimesheetDocument()
                .getAssignment(AssignmentDescriptionKey.get(tdaf.getSelectedAssignment()));

        //Grab timeblock to be updated from form
        List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
        TimeBlock updatedTimeBlock = null;
        for (TimeBlock tb : timeBlocks) {
            if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
                updatedTimeBlock = tb;
                tb.setJobNumber(assignment.getJobNumber());
                tb.setWorkArea(assignment.getWorkArea());
                tb.setTask(assignment.getTask());
                break;
            }
        }

        Set<String> earnCodes = new HashSet<String>();
        if (updatedTimeBlock != null) {
            List<EarnCode> validEarnCodes = TkServiceLocator.getTimesheetService().getEarnCodesForTime(assignment,
                    updatedTimeBlock.getBeginDateTime().toLocalDate(), true);
            for (EarnCode e : validEarnCodes) {
                earnCodes.add(e.getEarnCode());
            }
        }

        if (updatedTimeBlock != null && earnCodes.contains(updatedTimeBlock.getEarnCode())) {
            TkServiceLocator.getTimeBlockService().updateTimeBlock(updatedTimeBlock);

            TimeBlockHistory tbh = new TimeBlockHistory(updatedTimeBlock);
            tbh.setActionHistory(TkConstants.ACTIONS.UPDATE_TIME_BLOCK);
            TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
        }
        tdaf.setMethodToCall("addTimeBlock");
        return mapping.findForward("basic");
    }

    public ActionForward actualTimeInquiry(ActionMapping mapping, ActionForm form, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        return mapping.findForward("ati");
    }

    public ActionForward deleteLunchDeduction(ActionMapping mapping, ActionForm form, HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
        String timeHourDetailId = tdaf.getTkTimeHourDetailId();
        TkServiceLocator.getTimeBlockService().deleteLunchDeduction(timeHourDetailId);

        List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();

        List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
        List<String> assignmentKeys = new ArrayList<String>();
        for (Assignment assignment : assignments) {
            assignmentKeys.add(assignment.getAssignmentKey());
        }
        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(
                HrContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(),
                tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);

        TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks,
                tdaf.getTimesheetDocument().getAsOfDate());

        // KPME-1340
        TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks,
                leaveBlocks, tdaf.getCalendarEntry(), tdaf.getTimesheetDocument(), HrContext.getPrincipalId());
        TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks);
        tdaf.getTimesheetDocument().setTimeBlocks(newTimeBlocks);

        return mapping.findForward("basic");
    }

    public ActionForward deleteLeaveBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        TimeDetailActionForm tdaf = (TimeDetailActionForm) form;

        String principalId = HrContext.getPrincipalId();
        String targetPrincipalId = HrContext.getTargetPrincipalId();
        String documentId = tdaf.getDocumentId();
        String leaveBlockId = tdaf.getLmLeaveBlockId();

        LeaveBlock blockToDelete = LmServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
        if (blockToDelete != null && LmServiceLocator.getLMPermissionService()
                .canDeleteLeaveBlock(HrContext.getPrincipalId(), blockToDelete)) {
            LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlockId, HrContext.getPrincipalId());

            generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
        }

        // if the leave block is NOT eligible for accrual, rerun accrual service for the leave calendar the leave block is on
        EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(blockToDelete.getEarnCode(),
                blockToDelete.getLeaveLocalDate());
        if (ec != null && ec.getEligibleForAccrual().equals("N")) {
            CalendarEntry ce = HrServiceLocator.getCalendarEntryService().getCurrentCalendarDatesForLeaveCalendar(
                    blockToDelete.getPrincipalId(), blockToDelete.getLeaveLocalDate().toDateTimeAtStartOfDay());
            if (ce != null) {
                LmServiceLocator.getLeaveAccrualService().runAccrual(blockToDelete.getPrincipalId(),
                        ce.getBeginPeriodFullDateTime().toDateTime(), ce.getEndPeriodFullDateTime().toDateTime(),
                        false);
            }
        }

        return mapping.findForward("basic");
    }

    private void generateTimesheetChangedNotification(String principalId, String targetPrincipalId,
            String documentId) {
        if (!StringUtils.equals(principalId, targetPrincipalId)) {
            EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService()
                    .getDefaultNamesForPrincipalId(principalId);
            if (person != null && person.getDefaultName() != null) {
                String subject = "Timesheet Modification Notice";
                StringBuilder message = new StringBuilder();
                message.append("Your Timesheet was changed by ");
                message.append(person.getDefaultName().getCompositeNameUnmasked());
                message.append(" on your behalf.");
                message.append(SystemUtils.LINE_SEPARATOR);
                message.append(getTimesheetURL(documentId));

                HrServiceLocator.getKPMENotificationService().sendNotification(subject, message.toString(),
                        targetPrincipalId);
            }
        }
    }

    @SuppressWarnings("deprecation")
    private String getTimesheetURL(String documentId) {
        Properties params = new Properties();
        params.put("documentId", documentId);
        return UrlFactory.parameterizeUrl(getApplicationBaseUrl() + "/TimeDetail.do", params);
    }

}