View Javadoc

1   /**
2    * Copyright 2004-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.hr.time.timesummary.service;
17  
18  import java.math.BigDecimal;
19  import java.sql.Timestamp;
20  import java.util.*;
21  
22  import org.apache.commons.collections.CollectionUtils;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.commons.lang.time.DateUtils;
25  import org.apache.log4j.Logger;
26  import org.joda.time.DateTime;
27  import org.joda.time.DateTimeFieldType;
28  import org.joda.time.Interval;
29  import org.joda.time.LocalDateTime;
30  import org.kuali.hr.job.Job;
31  import org.kuali.hr.lm.LMConstants;
32  import org.kuali.hr.lm.accrual.AccrualCategoryRule;
33  import org.kuali.hr.lm.leaveSummary.LeaveSummary;
34  import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
35  import org.kuali.hr.lm.leaveblock.LeaveBlock;
36  import org.kuali.hr.lm.util.LeaveBlockAggregate;
37  import org.kuali.hr.time.assignment.Assignment;
38  import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
39  import org.kuali.hr.time.calendar.Calendar;
40  import org.kuali.hr.time.calendar.CalendarEntries;
41  import org.kuali.hr.time.earncode.EarnCode;
42  import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
43  import org.kuali.hr.time.flsa.FlsaDay;
44  import org.kuali.hr.time.flsa.FlsaWeek;
45  import org.kuali.hr.time.service.base.TkServiceLocator;
46  import org.kuali.hr.time.timeblock.TimeBlock;
47  import org.kuali.hr.time.timeblock.TimeHourDetail;
48  import org.kuali.hr.time.timesheet.TimesheetDocument;
49  import org.kuali.hr.time.timesummary.AssignmentColumn;
50  import org.kuali.hr.time.timesummary.AssignmentRow;
51  import org.kuali.hr.time.timesummary.EarnCodeSection;
52  import org.kuali.hr.time.timesummary.EarnGroupSection;
53  import org.kuali.hr.time.timesummary.TimeSummary;
54  import org.kuali.hr.time.util.TKUtils;
55  import org.kuali.hr.time.util.TkConstants;
56  import org.kuali.hr.time.util.TkTimeBlockAggregate;
57  import org.kuali.hr.time.workarea.WorkArea;
58  
59  public class TimeSummaryServiceImpl implements TimeSummaryService {
60  	private static final String OTHER_EARN_GROUP = "Other";
61  	private static final Logger LOG = Logger.getLogger(TimeSummaryServiceImpl.class);
62  	
63      @Override
64  	public TimeSummary getTimeSummary(TimesheetDocument timesheetDocument) {
65  		TimeSummary timeSummary = new TimeSummary();
66  
67  		if(timesheetDocument == null || timesheetDocument.getTimeBlocks() == null) {
68  			return timeSummary;
69  		}
70  
71          List<Boolean> dayArrangements = new ArrayList<Boolean>();
72  
73  		timeSummary.setSummaryHeader(getHeaderForSummary(timesheetDocument.getCalendarEntry(), dayArrangements));
74  		TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timesheetDocument.getTimeBlocks(), timesheetDocument.getCalendarEntry(), TkServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true);
75  
76          List<Assignment> timeAssignments = timesheetDocument.getAssignments();
77          List<String> tAssignmentKeys = new ArrayList<String>();
78          Set<String> regularEarnCodes = new HashSet<String>();
79          for(Assignment assign : timeAssignments) {
80              tAssignmentKeys.add(assign.getAssignmentKey());
81              regularEarnCodes.add(assign.getJob().getPayTypeObj().getRegEarnCode());
82          }
83          List<LeaveBlock> leaveBlocks =  TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(),
84                  timesheetDocument.getCalendarEntry().getBeginPeriodDate(), timesheetDocument.getCalendarEntry().getEndPeriodDate(), tAssignmentKeys);
85          LeaveBlockAggregate leaveBlockAggregate = new LeaveBlockAggregate(leaveBlocks, timesheetDocument.getCalendarEntry());
86          tkTimeBlockAggregate = TkTimeBlockAggregate.combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
87  
88  		timeSummary.setWorkedHours(getWorkedHours(tkTimeBlockAggregate, regularEarnCodes));
89  
90          List<EarnGroupSection> earnGroupSections = getEarnGroupSections(tkTimeBlockAggregate, timeSummary.getSummaryHeader().size()+1, 
91          			dayArrangements, timesheetDocument.getAsOfDate(), timesheetDocument.getDocEndDate());
92          timeSummary.setSections(sortEarnGroupSections(earnGroupSections, regularEarnCodes));
93          
94          try {
95  			List<LeaveSummaryRow> maxedLeaveRows = getMaxedLeaveRows(timesheetDocument.getCalendarEntry(),timesheetDocument.getPrincipalId());
96  			timeSummary.setMaxedLeaveRows(maxedLeaveRows);
97  		} catch (Exception e) {
98  			// TODO Auto-generated catch block
99  			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 }