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 org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.time.DateUtils;
21  import org.apache.log4j.Logger;
22  import org.joda.time.DateTime;
23  import org.joda.time.DateTimeFieldType;
24  import org.joda.time.Interval;
25  import org.joda.time.LocalDateTime;
26  import org.kuali.hr.job.Job;
27  import org.kuali.hr.lm.LMConstants;
28  import org.kuali.hr.lm.accrual.AccrualCategory;
29  import org.kuali.hr.lm.accrual.AccrualCategoryRule;
30  import org.kuali.hr.lm.accrual.service.AccrualServiceImpl;
31  import org.kuali.hr.lm.leaveSummary.LeaveSummary;
32  import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
33  import org.kuali.hr.lm.leaveblock.LeaveBlock;
34  import org.kuali.hr.lm.util.LeaveBlockAggregate;
35  import org.kuali.hr.time.assignment.Assignment;
36  import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
37  import org.kuali.hr.time.calendar.Calendar;
38  import org.kuali.hr.time.calendar.CalendarEntries;
39  import org.kuali.hr.time.earncode.EarnCode;
40  import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
41  import org.kuali.hr.time.flsa.FlsaDay;
42  import org.kuali.hr.time.flsa.FlsaWeek;
43  import org.kuali.hr.time.principal.PrincipalHRAttributes;
44  import org.kuali.hr.time.service.base.TkServiceLocator;
45  import org.kuali.hr.time.timeblock.TimeBlock;
46  import org.kuali.hr.time.timeblock.TimeHourDetail;
47  import org.kuali.hr.time.timesheet.TimesheetDocument;
48  import org.kuali.hr.time.timesummary.AssignmentRow;
49  import org.kuali.hr.time.timesummary.EarnCodeSection;
50  import org.kuali.hr.time.timesummary.EarnGroupSection;
51  import org.kuali.hr.time.timesummary.TimeSummary;
52  import org.kuali.hr.time.util.TKUtils;
53  import org.kuali.hr.time.util.TkConstants;
54  import org.kuali.hr.time.util.TkTimeBlockAggregate;
55  import org.kuali.hr.time.workarea.WorkArea;
56  
57  import java.math.BigDecimal;
58  import java.sql.Timestamp;
59  import java.util.*;
60  
61  public class TimeSummaryServiceImpl implements TimeSummaryService {
62  	private static final String OTHER_EARN_GROUP = "Other";
63  	private static final Logger LOG = Logger.getLogger(TimeSummaryServiceImpl.class);
64  	
65      @Override
66  	public TimeSummary getTimeSummary(TimesheetDocument timesheetDocument) {
67  		TimeSummary timeSummary = new TimeSummary();
68  
69  		if(timesheetDocument.getTimeBlocks() == null) {
70  			return timeSummary;
71  		}
72  
73          List<Boolean> dayArrangements = new ArrayList<Boolean>();
74  
75  		timeSummary.setSummaryHeader(getHeaderForSummary(timesheetDocument.getCalendarEntry(), dayArrangements));
76  		TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timesheetDocument.getTimeBlocks(), timesheetDocument.getCalendarEntry(), TkServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true);
77  
78          List<Assignment> timeAssignments = timesheetDocument.getAssignments();
79          List<String> tAssignmentKeys = new ArrayList<String>();
80          for(Assignment assign : timeAssignments) {
81              tAssignmentKeys.add(assign.getAssignmentKey());
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 = combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
87  
88  		timeSummary.setWorkedHours(getWorkedHours(tkTimeBlockAggregate));
89  
90          List<EarnGroupSection> earnGroupSections = getEarnGroupSections(tkTimeBlockAggregate, timeSummary.getSummaryHeader().size()+1, 
91          			dayArrangements, timesheetDocument.getAsOfDate(), timesheetDocument.getDocEndDate());
92          timeSummary.setSections(earnGroupSections);
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<LeaveSummaryRow> getMaxedLeaveRows(
106 			CalendarEntries calendarEntry, String principalId) throws Exception {
107     	List<LeaveSummaryRow> maxedLeaveRows = new ArrayList<LeaveSummaryRow>();
108     	
109     	if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
110     		
111         	Map<String,Set<LeaveBlock>> eligibilities = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry,principalId);
112         	Set<LeaveBlock> onDemandTransfers = eligibilities.get(LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND);
113 
114         	Interval calendarEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
115         	
116         	//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
117         	//change, no warning given to the employee of balance limits being exceeded except on or after that day.
118 
119         	if(!onDemandTransfers.isEmpty()) {
120             	for(LeaveBlock lb : onDemandTransfers) {
121             		Date leaveDate = lb.getLeaveDate();
122                 	LeaveSummary summary = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(principalId, new java.sql.Date(DateUtils.addDays(leaveDate, 1).getTime()));
123                 	LeaveSummaryRow row = summary.getLeaveSummaryRowForAccrualCtgy(lb.getAccrualCategory());
124             		if(row != null) {
125             			//AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(row.getAccrualCategoryId());
126                     	//AccrualCategoryRule currentRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(accrualCategory, asOfDate, pha.getServiceDate());
127                     	if(calendarEntryInterval.contains(leaveDate.getTime())) {
128                     		//do not allow the on-demand max balance action if the rule the action occurs under is no longer in effect,
129                     		//or if the infraction did not occur within this interval. ( if it occurred during the previous interval, 
130                     		//the employee will have the option to take action in that interval up to & including the end date of that interval. )
131 	            			row.setInfractingLeaveBlockId(lb.getAccrualCategoryRuleId());
132 	            			AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(lb.getAccrualCategoryRuleId());
133 	            			
134 	            			if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.TRANSFER))
135 	            				row.setTransferable(true);
136 	            			else if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.PAYOUT))
137 	            				row.setPayoutable(true);
138 	            			
139 	            			boolean exists = false;
140 	            			for(LeaveSummaryRow maxedRow : maxedLeaveRows) {
141 	            				if(StringUtils.equals(maxedRow.getAccrualCategoryId(),row.getAccrualCategoryId()))
142 	            					exists = true;
143 	            			}
144 	            			if(!exists)
145             					maxedLeaveRows.add(row);
146                     	}
147             		}
148             	}
149         	}
150     	}
151 		return maxedLeaveRows;
152 	}
153 
154 	/**
155      * Aggregates timeblocks into the appropriate earngroup-> earncode -> assignment rows
156      * @param tkTimeBlockAggregate
157      * @param numEntries
158      * @param dayArrangements
159      * @param asOfDate
160      * @return
161      */
162 	public List<EarnGroupSection> getEarnGroupSections(TkTimeBlockAggregate tkTimeBlockAggregate, int numEntries, List<Boolean> dayArrangements, Date asOfDate , Date docEndDate){
163 		List<EarnGroupSection> earnGroupSections = new ArrayList<EarnGroupSection>();
164 		List<FlsaWeek> flsaWeeks = tkTimeBlockAggregate.getFlsaWeeks(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback());
165 		Map<String, EarnCodeSection> earnCodeToEarnCodeSection = new HashMap<String, EarnCodeSection>();
166 		Map<String, EarnGroupSection> earnGroupToEarnGroupSection = new HashMap<String, EarnGroupSection>();
167 		
168 		int dayCount = 0;
169 		
170 		//TODO remove this and correct the aggregate .. not sure what the down stream changes are
171 		//so leaving this for initial release
172 		List<FlsaWeek> trimmedFlsaWeeks = new ArrayList<FlsaWeek>();
173 		for(FlsaWeek flsaWeek : flsaWeeks){
174 			if(flsaWeek.getFlsaDays().size() > 0){
175 				trimmedFlsaWeeks.add(flsaWeek);
176 			}
177 		}
178 		
179 		//For every flsa week and day aggegate each time hour detail 
180 		// buckets it by earn code section first
181 		for(FlsaWeek flsaWeek : trimmedFlsaWeeks){
182 			int weekSize = 0;
183 			List<FlsaDay> flsaDays = flsaWeek.getFlsaDays();
184 			for(FlsaDay flsaDay : flsaDays){
185 				Map<String, List<TimeBlock>> earnCodeToTimeBlocks = flsaDay.getEarnCodeToTimeBlocks();
186 				
187 				for(List<TimeBlock> timeBlocks : earnCodeToTimeBlocks.values()){
188 					for(TimeBlock timeBlock : timeBlocks){
189 						for(TimeHourDetail thd : timeBlock.getTimeHourDetails()){
190 							if(StringUtils.equals(TkConstants.LUNCH_EARN_CODE, thd.getEarnCode())){
191 								continue;
192 							}
193 							EarnCodeSection earnCodeSection = earnCodeToEarnCodeSection.get(thd.getEarnCode());
194 							if(earnCodeSection == null){
195 								earnCodeSection = new EarnCodeSection();
196 								earnCodeSection.setEarnCode(thd.getEarnCode());
197 								EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(thd.getEarnCode(), TKUtils.getTimelessDate(asOfDate));
198 								earnCodeSection.setDescription(earnCodeObj.getDescription());
199 								earnCodeSection.setIsAmountEarnCode((earnCodeObj.getRecordMethod()!= null && earnCodeObj.getRecordMethod().equalsIgnoreCase(TkConstants.EARN_CODE_AMOUNT)) ? true : false);
200 								for(int i = 0;i<(numEntries-1);i++){
201 									earnCodeSection.getTotals().add(BigDecimal.ZERO);
202 								}
203 								
204 								earnCodeToEarnCodeSection.put(thd.getEarnCode(), earnCodeSection);
205 							}
206 							String assignKey = timeBlock.getAssignmentKey();
207 							AssignmentRow assignRow = earnCodeSection.getAssignKeyToAssignmentRowMap().get(assignKey);
208 							if(assignRow == null){
209 								assignRow = new AssignmentRow();
210 								assignRow.setAssignmentKey(assignKey);
211 								AssignmentDescriptionKey assignmentKey = TkServiceLocator.getAssignmentService().getAssignmentDescriptionKey(assignKey);
212 								Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(asOfDate));
213 								// some assignment may not be effective at the beginning of the pay period, use the end date of the period to find it
214 								if(assignment == null) {
215 									assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(docEndDate));
216 								}
217 								//TODO push this up to the assignment fetch/fully populated instead of like this
218 								if(assignment != null){
219 									if(assignment.getJob() == null){
220 										Job aJob = TkServiceLocator.getJobService().getJob(assignment.getPrincipalId(),assignment.getJobNumber(),TKUtils.getTimelessDate(assignment.getEffectiveDate()));
221 										assignment.setJob(aJob);
222 									}
223 									if(assignment.getWorkAreaObj() == null){
224 										WorkArea aWorkArea = TkServiceLocator.getWorkAreaService().getWorkArea(assignment.getWorkArea(), TKUtils.getTimelessDate(assignment.getEffectiveDate()));
225 										assignment.setWorkAreaObj(aWorkArea);
226 									}
227 									assignRow.setDescr(assignment.getAssignmentDescription());
228 								}
229 								for(int i = 0;i<(numEntries-1);i++){
230 									assignRow.getTotal().add(BigDecimal.ZERO);
231 									assignRow.getAmount().add(BigDecimal.ZERO);
232 								}
233 								assignRow.setEarnCodeSection(earnCodeSection);
234 								earnCodeSection.addAssignmentRow(assignRow);
235 							}
236 							assignRow.addToTotal(dayCount, thd.getHours());
237 							assignRow.addToAmount(dayCount, thd.getAmount());
238 						}
239 					}
240 				}
241 				dayCount++;
242 				weekSize++;
243 			}
244 			//end of flsa week accumulate weekly totals
245 			for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){
246 				earnCodeSection.addWeeklyTotal(dayCount, weekSize);
247 			}			
248 			weekSize = 0;
249 
250 			dayCount++;
251 		}
252 		
253 		dayCount = 0;
254 		//now create all teh earn group sections and aggregate accordingly
255 		for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){
256 			String earnCode = earnCodeSection.getEarnCode();
257 			EarnCodeGroup earnGroupObj = TkServiceLocator.getEarnCodeGroupService().getEarnCodeGroupSummaryForEarnCode(earnCode, TKUtils.getTimelessDate(asOfDate));
258 			String earnGroup = null;
259 			if(earnGroupObj == null){
260 				earnGroup = OTHER_EARN_GROUP;
261 			} else{
262 				earnGroup = earnGroupObj.getDescr();
263 			}
264 			
265 			EarnGroupSection earnGroupSection = earnGroupToEarnGroupSection.get(earnGroup);
266 			if(earnGroupSection == null){
267 				earnGroupSection = new EarnGroupSection();
268 				earnGroupSection.setEarnGroup(earnGroup);
269 				for(int i =0;i<(numEntries-1);i++){
270 					earnGroupSection.getTotals().add(BigDecimal.ZERO);
271 				}
272 				earnGroupToEarnGroupSection.put(earnGroup, earnGroupSection);
273 			}
274 			earnGroupSection.addEarnCodeSection(earnCodeSection, dayArrangements);
275 			
276 		}
277 		for(EarnGroupSection earnGroupSection : earnGroupToEarnGroupSection.values()){
278 			earnGroupSections.add(earnGroupSection);
279 		}
280 		return earnGroupSections;
281 	}
282 	
283 	/**
284 	 * Generate a list of string describing this pay calendar entry for the summary
285 	 * @param payCalEntry
286 	 * @return
287 	 */
288 	protected List<String> getSummaryHeader(CalendarEntries payCalEntry){
289 		List<String> summaryHeader = new ArrayList<String>();
290 		int dayCount = 0;
291 		Date beginDateTime = payCalEntry.getBeginPeriodDateTime();
292 		Date endDateTime = payCalEntry.getEndPeriodDateTime();
293 		boolean virtualDays = false;
294         LocalDateTime endDate = payCalEntry.getEndLocalDateTime();
295 
296         if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
297                 endDate.get(DateTimeFieldType.secondOfMinute()) != 0){
298             virtualDays = true;
299         }
300 		
301 		Date currDateTime = beginDateTime;
302 		java.util.Calendar cal = GregorianCalendar.getInstance();
303 		
304 		while(currDateTime.before(endDateTime)){
305 			LocalDateTime currDate = new LocalDateTime(currDateTime);
306 			summaryHeader.add(makeHeaderDiplayString(currDate, virtualDays));
307 			
308 			dayCount++;
309 			if((dayCount % 7) == 0){
310 				summaryHeader.add("Week "+ ((dayCount / 7)));
311 			}
312 			cal.setTime(currDateTime);
313 			cal.add(java.util.Calendar.HOUR, 24);
314 			currDateTime = cal.getTime();
315 		}
316 		
317 		summaryHeader.add("Period Total");
318 		return summaryHeader;
319 	}
320 
321     //kind of a hack
322     private TkTimeBlockAggregate combineTimeAndLeaveAggregates(TkTimeBlockAggregate tbAggregate, LeaveBlockAggregate lbAggregate) {
323         if (tbAggregate != null
324                 && lbAggregate != null
325                 && tbAggregate.getDayTimeBlockList().size() == lbAggregate.getDayLeaveBlockList().size()) {
326             for (int i = 0; i < tbAggregate.getDayTimeBlockList().size(); i++) {
327                 List<LeaveBlock> leaveBlocks = lbAggregate.getDayLeaveBlockList().get(i);
328                 if (CollectionUtils.isNotEmpty(leaveBlocks)) {
329                     for (LeaveBlock lb : leaveBlocks) {
330                         //convert leave block to generic time block and add to list
331                         //conveniently, we only really need the hours amount
332                         TimeBlock timeBlock = new TimeBlock();
333                         timeBlock.setHours(lb.getLeaveAmount().negate());
334                         timeBlock.setBeginTimestamp(new Timestamp(lb.getLeaveDate().getTime()));
335                         timeBlock.setEndTimestamp(new Timestamp(new DateTime(lb.getLeaveDate()).plusMinutes(timeBlock.getHours().intValue()).getMillis()));
336                         timeBlock.setAssignmentKey(lb.getAssignmentKey());
337                         timeBlock.setEarnCode(lb.getEarnCode());
338                         timeBlock.setPrincipalId(lb.getPrincipalId());
339                         timeBlock.setWorkArea(lb.getWorkArea());
340                         TimeHourDetail timeHourDetail = new TimeHourDetail();
341                         timeHourDetail.setEarnCode(timeBlock.getEarnCode());
342                         timeHourDetail.setHours(timeBlock.getHours());
343                         timeHourDetail.setAmount(BigDecimal.ZERO);
344                         timeBlock.addTimeHourDetail(timeHourDetail);
345                         tbAggregate.getDayTimeBlockList().get(i).add(timeBlock);
346                     }
347                 }
348 
349             }
350         }
351         return tbAggregate;
352     }
353 
354 
355 
356     /**
357      * Provides the number of hours worked for the pay period indicated in the
358      * aggregate.
359      *
360      * @param aggregate The aggregate we are summing
361      *
362      * @return A list of BigDecimals containing the number of hours worked.
363      * This list will line up with the header.
364      */
365     private List<BigDecimal> getWorkedHours(TkTimeBlockAggregate aggregate) {
366         List<BigDecimal> hours = new ArrayList<BigDecimal>();
367         BigDecimal periodTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO;
368         for (FlsaWeek week : aggregate.getFlsaWeeks(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback())) {
369             BigDecimal weeklyTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO;
370             for (FlsaDay day : week.getFlsaDays()) {
371                 BigDecimal totalForDay = TkConstants.BIG_DECIMAL_SCALED_ZERO;
372                 for (TimeBlock block : day.getAppliedTimeBlocks()) {
373                     totalForDay = totalForDay.add(block.getHours(), TkConstants.MATH_CONTEXT);
374                     weeklyTotal = weeklyTotal.add(block.getHours(), TkConstants.MATH_CONTEXT);
375                     periodTotal = periodTotal.add(block.getHours(), TkConstants.MATH_CONTEXT);
376                 }
377                 hours.add(totalForDay);
378             }
379             hours.add(weeklyTotal);
380         }
381         hours.add(periodTotal);
382 
383         return hours;
384     }
385 
386 
387     /**
388      * Handles the generation of the display header for the time summary.
389      *
390      * @param cal The PayCalendarEntries object we are using to derive information.
391      * @param dayArrangements Container passed in to store the position of week / period aggregate sums
392      *
393      * @return An in-order string of days for this period that properly accounts
394      * for FLSA week boundaries in the pay period.
395      */
396     @Override
397     public List<String> getHeaderForSummary(CalendarEntries cal, List<Boolean> dayArrangements) {
398         List<String> header = new ArrayList<String>();
399         if (cal == null) {
400             return Collections.emptyList();
401         }
402         // Maps directly to joda time day of week constants.
403         int flsaBeginDay = this.getPayCalendarForEntry(cal).getFlsaBeginDayConstant();
404         boolean virtualDays = false;
405         LocalDateTime startDate = cal.getBeginLocalDateTime();
406         LocalDateTime endDate = cal.getEndLocalDateTime();
407 
408         // Increment end date if we are on a virtual day calendar, so that the
409         // for loop can account for having the proper amount of days on the
410         // summary calendar.
411         if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
412                 endDate.get(DateTimeFieldType.secondOfMinute()) != 0)
413         {
414             endDate = endDate.plusDays(1);
415             virtualDays = true;
416         }
417 
418         boolean afterFirstDay = false;
419         int week = 1;
420         for (LocalDateTime currentDate = startDate; currentDate.compareTo(endDate) < 0; currentDate = currentDate.plusDays(1)) {
421 
422             if (currentDate.getDayOfWeek() == flsaBeginDay && afterFirstDay) {
423                 header.add("Week " + week);
424                 dayArrangements.add(false);
425                 week++;
426             }
427 
428             header.add(makeHeaderDiplayString(currentDate, virtualDays));
429             dayArrangements.add(true);
430 
431 
432             afterFirstDay = true;
433         }
434 
435         // We may have a very small final "week" on this pay period. For now
436         // we will mark it as a week, and if someone doesn't like it, it can
437         // be removed.
438         if (!header.get(header.size()-1).startsWith("Week")) {
439             dayArrangements.add(false);
440             header.add("Week " + week);
441         }
442 
443 
444         header.add("Period Total");
445         dayArrangements.add(false);
446         return header;
447     }
448 
449     /**
450      * Helper function to generate display text for the summary header.
451      * @param currentDate The date we are generating for.
452      * @param virtualDays Whether or not virtual days apply.
453      * @return A string appropriate for UI display.
454      */
455     private String makeHeaderDiplayString(LocalDateTime currentDate, boolean virtualDays) {
456         StringBuilder display = new StringBuilder();
457         
458         display.append(currentDate.toString("E"));
459         if (virtualDays) {
460         	LocalDateTime nextDate = currentDate.plusDays(1);
461         	display.append(" - ");
462             display.append(nextDate.toString("E"));
463         }
464         
465         display.append("<br />");
466         
467         display.append(currentDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
468         if (virtualDays) {
469             LocalDateTime nextDate = currentDate.plusDays(1);
470             display.append(" - ");
471             display.append(nextDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
472         }
473 
474         return display.toString();
475     }
476 
477     /**
478      * @param calEntry Calendar entry we are using for lookup.
479      * @return The PayCalendar that owns the provided entry.
480      */
481     private Calendar getPayCalendarForEntry(CalendarEntries calEntry) {
482         Calendar cal = null;
483 
484         if (calEntry != null) {
485             cal = TkServiceLocator.getCalendarService().getCalendar(calEntry.getHrCalendarId());
486         }
487 
488         return cal;
489     }
490 
491 }