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 }