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.timesummary.service; 017 018 import java.math.BigDecimal; 019 import java.sql.Timestamp; 020 import java.util.*; 021 022 import org.apache.commons.collections.CollectionUtils; 023 import org.apache.commons.lang.StringUtils; 024 import org.apache.commons.lang.time.DateUtils; 025 import org.apache.log4j.Logger; 026 import org.joda.time.DateTime; 027 import org.joda.time.DateTimeFieldType; 028 import org.joda.time.Interval; 029 import org.joda.time.LocalDateTime; 030 import org.kuali.hr.job.Job; 031 import org.kuali.hr.lm.LMConstants; 032 import org.kuali.hr.lm.accrual.AccrualCategoryRule; 033 import org.kuali.hr.lm.leaveSummary.LeaveSummary; 034 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow; 035 import org.kuali.hr.lm.leaveblock.LeaveBlock; 036 import org.kuali.hr.lm.util.LeaveBlockAggregate; 037 import org.kuali.hr.time.assignment.Assignment; 038 import org.kuali.hr.time.assignment.AssignmentDescriptionKey; 039 import org.kuali.hr.time.calendar.Calendar; 040 import org.kuali.hr.time.calendar.CalendarEntries; 041 import org.kuali.hr.time.earncode.EarnCode; 042 import org.kuali.hr.time.earncodegroup.EarnCodeGroup; 043 import org.kuali.hr.time.flsa.FlsaDay; 044 import org.kuali.hr.time.flsa.FlsaWeek; 045 import org.kuali.hr.time.service.base.TkServiceLocator; 046 import org.kuali.hr.time.timeblock.TimeBlock; 047 import org.kuali.hr.time.timeblock.TimeHourDetail; 048 import org.kuali.hr.time.timesheet.TimesheetDocument; 049 import org.kuali.hr.time.timesummary.AssignmentColumn; 050 import org.kuali.hr.time.timesummary.AssignmentRow; 051 import org.kuali.hr.time.timesummary.EarnCodeSection; 052 import org.kuali.hr.time.timesummary.EarnGroupSection; 053 import org.kuali.hr.time.timesummary.TimeSummary; 054 import org.kuali.hr.time.util.TKUtils; 055 import org.kuali.hr.time.util.TkConstants; 056 import org.kuali.hr.time.util.TkTimeBlockAggregate; 057 import org.kuali.hr.time.workarea.WorkArea; 058 059 public class TimeSummaryServiceImpl implements TimeSummaryService { 060 private static final String OTHER_EARN_GROUP = "Other"; 061 private static final Logger LOG = Logger.getLogger(TimeSummaryServiceImpl.class); 062 063 @Override 064 public TimeSummary getTimeSummary(TimesheetDocument timesheetDocument) { 065 TimeSummary timeSummary = new TimeSummary(); 066 067 if(timesheetDocument == null || timesheetDocument.getTimeBlocks() == null) { 068 return timeSummary; 069 } 070 071 List<Boolean> dayArrangements = new ArrayList<Boolean>(); 072 073 timeSummary.setSummaryHeader(getHeaderForSummary(timesheetDocument.getCalendarEntry(), dayArrangements)); 074 TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timesheetDocument.getTimeBlocks(), timesheetDocument.getCalendarEntry(), TkServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true); 075 076 List<Assignment> timeAssignments = timesheetDocument.getAssignments(); 077 List<String> tAssignmentKeys = new ArrayList<String>(); 078 Set<String> regularEarnCodes = new HashSet<String>(); 079 for(Assignment assign : timeAssignments) { 080 tAssignmentKeys.add(assign.getAssignmentKey()); 081 regularEarnCodes.add(assign.getJob().getPayTypeObj().getRegEarnCode()); 082 } 083 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(), 084 timesheetDocument.getCalendarEntry().getBeginPeriodDate(), timesheetDocument.getCalendarEntry().getEndPeriodDate(), tAssignmentKeys); 085 LeaveBlockAggregate leaveBlockAggregate = new LeaveBlockAggregate(leaveBlocks, timesheetDocument.getCalendarEntry()); 086 tkTimeBlockAggregate = TkTimeBlockAggregate.combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate); 087 088 timeSummary.setWorkedHours(getWorkedHours(tkTimeBlockAggregate, regularEarnCodes)); 089 090 List<EarnGroupSection> earnGroupSections = getEarnGroupSections(tkTimeBlockAggregate, timeSummary.getSummaryHeader().size()+1, 091 dayArrangements, timesheetDocument.getAsOfDate(), timesheetDocument.getDocEndDate()); 092 timeSummary.setSections(sortEarnGroupSections(earnGroupSections, regularEarnCodes)); 093 094 try { 095 List<LeaveSummaryRow> maxedLeaveRows = getMaxedLeaveRows(timesheetDocument.getCalendarEntry(),timesheetDocument.getPrincipalId()); 096 timeSummary.setMaxedLeaveRows(maxedLeaveRows); 097 } catch (Exception e) { 098 // TODO Auto-generated catch block 099 LOG.error("error retreiving maxed leave rows", e); 100 } 101 102 return timeSummary; 103 } 104 105 private List<EarnGroupSection> sortEarnGroupSections(List<EarnGroupSection> sections, Set<String> regularEarnCodes) { 106 List<EarnGroupSection> sortedList = new ArrayList<EarnGroupSection>(); 107 //first sort by alpha 108 Collections.sort(sections, new Comparator<EarnGroupSection>() { 109 @Override 110 public int compare(EarnGroupSection egs1, EarnGroupSection egs2) { 111 if (egs1 == null ^ egs2 == null) { 112 return egs1 == null ? -1 : 1; 113 } 114 if (egs1 == null && egs2 == null) { 115 return 0; 116 } 117 //'other' earn group needs to be last. 118 boolean isOther1 = StringUtils.equals(egs1.getEarnGroup(), OTHER_EARN_GROUP); 119 boolean isOther2 = StringUtils.equals(egs2.getEarnGroup(), OTHER_EARN_GROUP); 120 if (isOther1 ^ isOther2) { 121 return isOther1 ? 1 : -1; 122 } 123 if (isOther1 && isOther2) { 124 return 0; 125 } 126 return egs1.getEarnGroup().compareTo(egs2.getEarnGroup()); 127 } 128 }); 129 130 List<EarnGroupSection> copy = new ArrayList<EarnGroupSection>(sections); 131 //loop through in reverse 132 for (EarnGroupSection egs : copy) { 133 if (!CollectionUtils.intersection(regularEarnCodes, egs.getEarnCodeToEarnCodeSectionMap().keySet()).isEmpty()) { 134 sortedList.add(egs); 135 sections.remove(egs); 136 } 137 } 138 sortedList.addAll(sections); 139 return sortedList; 140 } 141 142 private List<LeaveSummaryRow> getMaxedLeaveRows( 143 CalendarEntries calendarEntry, String principalId) throws Exception { 144 List<LeaveSummaryRow> maxedLeaveRows = new ArrayList<LeaveSummaryRow>(); 145 146 if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) { 147 148 Map<String,Set<LeaveBlock>> eligibilities = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry,principalId); 149 Set<LeaveBlock> onDemandTransfers = eligibilities.get(LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND); 150 151 Interval calendarEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime()); 152 153 //use the current date if on the current calendar? yes -> no warning given until accrual is reached. If accrual occurs on last day of period or last day of service interval 154 //change, no warning given to the employee of balance limits being exceeded except on or after that day. 155 156 if(!onDemandTransfers.isEmpty()) { 157 for(LeaveBlock lb : onDemandTransfers) { 158 Date leaveDate = lb.getLeaveDate(); 159 LeaveSummary summary = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(principalId, new java.sql.Date(DateUtils.addDays(leaveDate, 1).getTime())); 160 LeaveSummaryRow row = summary.getLeaveSummaryRowForAccrualCtgy(lb.getAccrualCategory()); 161 if(row != null) { 162 //AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(row.getAccrualCategoryId()); 163 //AccrualCategoryRule currentRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(accrualCategory, asOfDate, pha.getServiceDate()); 164 if(calendarEntryInterval.contains(leaveDate.getTime())) { 165 //do not allow the on-demand max balance action if the rule the action occurs under is no longer in effect, 166 //or if the infraction did not occur within this interval. ( if it occurred during the previous interval, 167 //the employee will have the option to take action in that interval up to & including the end date of that interval. ) 168 row.setInfractingLeaveBlockId(lb.getAccrualCategoryRuleId()); 169 AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(lb.getAccrualCategoryRuleId()); 170 171 if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.TRANSFER)) 172 row.setTransferable(true); 173 else if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.PAYOUT)) 174 row.setPayoutable(true); 175 176 boolean exists = false; 177 for(LeaveSummaryRow maxedRow : maxedLeaveRows) { 178 if(StringUtils.equals(maxedRow.getAccrualCategoryId(),row.getAccrualCategoryId())) 179 exists = true; 180 } 181 if(!exists) 182 maxedLeaveRows.add(row); 183 } 184 } 185 } 186 } 187 } 188 return maxedLeaveRows; 189 } 190 191 /** 192 * Aggregates timeblocks into the appropriate earngroup-> earncode -> assignment rows 193 * @param tkTimeBlockAggregate 194 * @param numEntries 195 * @param dayArrangements 196 * @param asOfDate 197 * @return 198 */ 199 public List<EarnGroupSection> getEarnGroupSections(TkTimeBlockAggregate tkTimeBlockAggregate, int numEntries, List<Boolean> dayArrangements, Date asOfDate , Date docEndDate){ 200 List<EarnGroupSection> earnGroupSections = new ArrayList<EarnGroupSection>(); 201 List<FlsaWeek> flsaWeeks = tkTimeBlockAggregate.getFlsaWeeks(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback()); 202 Map<String, EarnCodeSection> earnCodeToEarnCodeSection = new TreeMap<String, EarnCodeSection>(); 203 Map<String, EarnGroupSection> earnGroupToEarnGroupSection = new HashMap<String, EarnGroupSection>(); 204 205 int dayCount = 0; 206 207 //TODO remove this and correct the aggregate .. not sure what the down stream changes are 208 //so leaving this for initial release 209 List<FlsaWeek> trimmedFlsaWeeks = new ArrayList<FlsaWeek>(); 210 for(FlsaWeek flsaWeek : flsaWeeks){ 211 if(flsaWeek.getFlsaDays().size() > 0){ 212 trimmedFlsaWeeks.add(flsaWeek); 213 } 214 } 215 216 //For every flsa week and day aggegate each time hour detail 217 // buckets it by earn code section first 218 for(FlsaWeek flsaWeek : trimmedFlsaWeeks){ 219 int weekSize = 0; 220 List<FlsaDay> flsaDays = flsaWeek.getFlsaDays(); 221 for(FlsaDay flsaDay : flsaDays){ 222 Map<String, List<TimeBlock>> earnCodeToTimeBlocks = flsaDay.getEarnCodeToTimeBlocks(); 223 224 for(List<TimeBlock> timeBlocks : earnCodeToTimeBlocks.values()){ 225 for(TimeBlock timeBlock : timeBlocks){ 226 for(TimeHourDetail thd : timeBlock.getTimeHourDetails()){ 227 if(StringUtils.equals(TkConstants.LUNCH_EARN_CODE, thd.getEarnCode())){ 228 continue; 229 } 230 EarnCodeSection earnCodeSection = earnCodeToEarnCodeSection.get(thd.getEarnCode()); 231 if(earnCodeSection == null){ 232 earnCodeSection = new EarnCodeSection(); 233 earnCodeSection.setEarnCode(thd.getEarnCode()); 234 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(thd.getEarnCode(), TKUtils.getTimelessDate(asOfDate)); 235 earnCodeSection.setDescription(earnCodeObj != null ? earnCodeObj.getDescription() : null); 236 earnCodeSection.setIsAmountEarnCode(earnCodeObj != null ? TkConstants.EARN_CODE_AMOUNT.equalsIgnoreCase(earnCodeObj.getRecordMethod()) : false); 237 for(int i = 0;i<(numEntries-1);i++){ 238 earnCodeSection.getTotals().add(BigDecimal.ZERO); 239 } 240 241 earnCodeToEarnCodeSection.put(thd.getEarnCode(), earnCodeSection); 242 } 243 String assignKey = timeBlock.getAssignmentKey(); 244 AssignmentRow assignRow = earnCodeSection.getAssignKeyToAssignmentRowMap().get(assignKey); 245 if(assignRow == null){ 246 assignRow = new AssignmentRow(); 247 assignRow.setAssignmentKey(assignKey); 248 AssignmentDescriptionKey assignmentKey = TkServiceLocator.getAssignmentService().getAssignmentDescriptionKey(assignKey); 249 Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(asOfDate)); 250 // some assignment may not be effective at the beginning of the pay period, use the end date of the period to find it 251 if(assignment == null) { 252 assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(docEndDate)); 253 } 254 //TODO push this up to the assignment fetch/fully populated instead of like this 255 if(assignment != null){ 256 if(assignment.getJob() == null){ 257 Job aJob = TkServiceLocator.getJobService().getJob(assignment.getPrincipalId(),assignment.getJobNumber(),TKUtils.getTimelessDate(assignment.getEffectiveDate())); 258 assignment.setJob(aJob); 259 } 260 if(assignment.getWorkAreaObj() == null){ 261 WorkArea aWorkArea = TkServiceLocator.getWorkAreaService().getWorkArea(assignment.getWorkArea(), TKUtils.getTimelessDate(assignment.getEffectiveDate())); 262 assignment.setWorkAreaObj(aWorkArea); 263 } 264 assignRow.setDescr(assignment.getAssignmentDescription()); 265 } 266 assignRow.setEarnCodeSection(earnCodeSection); 267 for (int i = 0; i < numEntries - 1; i++) { 268 assignRow.addAssignmentColumn(new AssignmentColumn()); 269 } 270 271 earnCodeSection.addAssignmentRow(assignRow); 272 } 273 assignRow.addToTotal(dayCount, thd.getHours()); 274 assignRow.addToAmount(dayCount, thd.getAmount()); 275 } 276 } 277 } 278 dayCount++; 279 weekSize++; 280 } 281 //end of flsa week accumulate weekly totals 282 for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){ 283 earnCodeSection.addWeeklyTotal(dayCount, weekSize); 284 } 285 weekSize = 0; 286 287 dayCount++; 288 } 289 290 dayCount = 0; 291 //now create all teh earn group sections and aggregate accordingly 292 for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){ 293 String earnCode = earnCodeSection.getEarnCode(); 294 EarnCodeGroup earnGroupObj = TkServiceLocator.getEarnCodeGroupService().getEarnCodeGroupSummaryForEarnCode(earnCode, TKUtils.getTimelessDate(asOfDate)); 295 String earnGroup = null; 296 if(earnGroupObj == null){ 297 earnGroup = OTHER_EARN_GROUP; 298 } else{ 299 earnGroup = earnGroupObj.getDescr(); 300 } 301 302 EarnGroupSection earnGroupSection = earnGroupToEarnGroupSection.get(earnGroup); 303 if(earnGroupSection == null){ 304 earnGroupSection = new EarnGroupSection(); 305 earnGroupSection.setEarnGroup(earnGroup); 306 for(int i =0;i<(numEntries-1);i++){ 307 earnGroupSection.getTotals().add(BigDecimal.ZERO); 308 } 309 earnGroupToEarnGroupSection.put(earnGroup, earnGroupSection); 310 } 311 earnGroupSection.addEarnCodeSection(earnCodeSection, dayArrangements); 312 313 } 314 for(EarnGroupSection earnGroupSection : earnGroupToEarnGroupSection.values()){ 315 earnGroupSections.add(earnGroupSection); 316 } 317 return earnGroupSections; 318 } 319 320 /** 321 * Generate a list of string describing this pay calendar entry for the summary 322 * @param payCalEntry 323 * @return 324 */ 325 protected List<String> getSummaryHeader(CalendarEntries payCalEntry){ 326 List<String> summaryHeader = new ArrayList<String>(); 327 int dayCount = 0; 328 Date beginDateTime = payCalEntry.getBeginPeriodDateTime(); 329 Date endDateTime = payCalEntry.getEndPeriodDateTime(); 330 boolean virtualDays = false; 331 LocalDateTime endDate = payCalEntry.getEndLocalDateTime(); 332 333 if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 || 334 endDate.get(DateTimeFieldType.secondOfMinute()) != 0){ 335 virtualDays = true; 336 } 337 338 Date currDateTime = beginDateTime; 339 java.util.Calendar cal = GregorianCalendar.getInstance(); 340 341 while(currDateTime.before(endDateTime)){ 342 LocalDateTime currDate = new LocalDateTime(currDateTime); 343 summaryHeader.add(makeHeaderDiplayString(currDate, virtualDays)); 344 345 dayCount++; 346 if((dayCount % 7) == 0){ 347 summaryHeader.add("Week "+ ((dayCount / 7))); 348 } 349 cal.setTime(currDateTime); 350 cal.add(java.util.Calendar.HOUR, 24); 351 currDateTime = cal.getTime(); 352 } 353 354 summaryHeader.add("Period Total"); 355 return summaryHeader; 356 } 357 358 /** 359 * Provides the number of hours worked for the pay period indicated in the 360 * aggregate. 361 * 362 * @param aggregate The aggregate we are summing 363 * 364 * @return A list of BigDecimals containing the number of hours worked. 365 * This list will line up with the header. 366 */ 367 private List<BigDecimal> getWorkedHours(TkTimeBlockAggregate aggregate, Set<String> regularEarnCodes) { 368 List<BigDecimal> hours = new ArrayList<BigDecimal>(); 369 BigDecimal periodTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO; 370 for (FlsaWeek week : aggregate.getFlsaWeeks(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback())) { 371 BigDecimal weeklyTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO; 372 for (FlsaDay day : week.getFlsaDays()) { 373 BigDecimal totalForDay = TkConstants.BIG_DECIMAL_SCALED_ZERO; 374 for (TimeBlock block : day.getAppliedTimeBlocks()) { 375 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(block.getEarnCode(), block.getEndDate()); 376 if (ec != null 377 && (ec.getOvtEarnCode() 378 || regularEarnCodes.contains(ec.getEarnCode()))) { 379 totalForDay = totalForDay.add(block.getHours(), TkConstants.MATH_CONTEXT); 380 weeklyTotal = weeklyTotal.add(block.getHours(), TkConstants.MATH_CONTEXT); 381 periodTotal = periodTotal.add(block.getHours(), TkConstants.MATH_CONTEXT); 382 } 383 } 384 hours.add(totalForDay); 385 } 386 hours.add(weeklyTotal); 387 } 388 hours.add(periodTotal); 389 390 return hours; 391 } 392 393 394 /** 395 * Handles the generation of the display header for the time summary. 396 * 397 * @param cal The PayCalendarEntries object we are using to derive information. 398 * @param dayArrangements Container passed in to store the position of week / period aggregate sums 399 * 400 * @return An in-order string of days for this period that properly accounts 401 * for FLSA week boundaries in the pay period. 402 */ 403 @Override 404 public List<String> getHeaderForSummary(CalendarEntries cal, List<Boolean> dayArrangements) { 405 List<String> header = new ArrayList<String>(); 406 if (cal == null) { 407 return Collections.emptyList(); 408 } 409 // Maps directly to joda time day of week constants. 410 int flsaBeginDay = this.getPayCalendarForEntry(cal).getFlsaBeginDayConstant(); 411 boolean virtualDays = false; 412 LocalDateTime startDate = cal.getBeginLocalDateTime(); 413 LocalDateTime endDate = cal.getEndLocalDateTime(); 414 415 // Increment end date if we are on a virtual day calendar, so that the 416 // for loop can account for having the proper amount of days on the 417 // summary calendar. 418 if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 || 419 endDate.get(DateTimeFieldType.secondOfMinute()) != 0) 420 { 421 endDate = endDate.plusDays(1); 422 virtualDays = true; 423 } 424 425 boolean afterFirstDay = false; 426 int week = 1; 427 for (LocalDateTime currentDate = startDate; currentDate.compareTo(endDate) < 0; currentDate = currentDate.plusDays(1)) { 428 429 if (currentDate.getDayOfWeek() == flsaBeginDay && afterFirstDay) { 430 header.add("Week " + week); 431 dayArrangements.add(false); 432 week++; 433 } 434 435 header.add(makeHeaderDiplayString(currentDate, virtualDays)); 436 dayArrangements.add(true); 437 438 439 afterFirstDay = true; 440 } 441 442 // We may have a very small final "week" on this pay period. For now 443 // we will mark it as a week, and if someone doesn't like it, it can 444 // be removed. 445 if (!header.get(header.size()-1).startsWith("Week")) { 446 dayArrangements.add(false); 447 header.add("Week " + week); 448 } 449 450 451 header.add("Period Total"); 452 dayArrangements.add(false); 453 return header; 454 } 455 456 /** 457 * Helper function to generate display text for the summary header. 458 * @param currentDate The date we are generating for. 459 * @param virtualDays Whether or not virtual days apply. 460 * @return A string appropriate for UI display. 461 */ 462 private String makeHeaderDiplayString(LocalDateTime currentDate, boolean virtualDays) { 463 StringBuilder display = new StringBuilder(); 464 465 display.append(currentDate.toString("E")); 466 if (virtualDays) { 467 LocalDateTime nextDate = currentDate.plusDays(1); 468 display.append(" - "); 469 display.append(nextDate.toString("E")); 470 } 471 472 display.append("<br />"); 473 474 display.append(currentDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT)); 475 if (virtualDays) { 476 LocalDateTime nextDate = currentDate.plusDays(1); 477 display.append(" - "); 478 display.append(nextDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT)); 479 } 480 481 return display.toString(); 482 } 483 484 /** 485 * @param calEntry Calendar entry we are using for lookup. 486 * @return The PayCalendar that owns the provided entry. 487 */ 488 private Calendar getPayCalendarForEntry(CalendarEntries calEntry) { 489 Calendar cal = null; 490 491 if (calEntry != null) { 492 cal = TkServiceLocator.getCalendarService().getCalendar(calEntry.getHrCalendarId()); 493 } 494 495 return cal; 496 } 497 498 }