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