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 org.apache.commons.collections.CollectionUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.apache.commons.lang.time.DateUtils;
021    import org.apache.log4j.Logger;
022    import org.joda.time.DateTime;
023    import org.joda.time.DateTimeFieldType;
024    import org.joda.time.Interval;
025    import org.joda.time.LocalDateTime;
026    import org.kuali.hr.job.Job;
027    import org.kuali.hr.lm.LMConstants;
028    import org.kuali.hr.lm.accrual.AccrualCategory;
029    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
030    import org.kuali.hr.lm.accrual.service.AccrualServiceImpl;
031    import org.kuali.hr.lm.leaveSummary.LeaveSummary;
032    import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
033    import org.kuali.hr.lm.leaveblock.LeaveBlock;
034    import org.kuali.hr.lm.util.LeaveBlockAggregate;
035    import org.kuali.hr.time.assignment.Assignment;
036    import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
037    import org.kuali.hr.time.calendar.Calendar;
038    import org.kuali.hr.time.calendar.CalendarEntries;
039    import org.kuali.hr.time.earncode.EarnCode;
040    import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
041    import org.kuali.hr.time.flsa.FlsaDay;
042    import org.kuali.hr.time.flsa.FlsaWeek;
043    import org.kuali.hr.time.principal.PrincipalHRAttributes;
044    import org.kuali.hr.time.service.base.TkServiceLocator;
045    import org.kuali.hr.time.timeblock.TimeBlock;
046    import org.kuali.hr.time.timeblock.TimeHourDetail;
047    import org.kuali.hr.time.timesheet.TimesheetDocument;
048    import org.kuali.hr.time.timesummary.AssignmentRow;
049    import org.kuali.hr.time.timesummary.EarnCodeSection;
050    import org.kuali.hr.time.timesummary.EarnGroupSection;
051    import org.kuali.hr.time.timesummary.TimeSummary;
052    import org.kuali.hr.time.util.TKUtils;
053    import org.kuali.hr.time.util.TkConstants;
054    import org.kuali.hr.time.util.TkTimeBlockAggregate;
055    import org.kuali.hr.time.workarea.WorkArea;
056    
057    import java.math.BigDecimal;
058    import java.sql.Timestamp;
059    import java.util.*;
060    
061    public class TimeSummaryServiceImpl implements TimeSummaryService {
062            private static final String OTHER_EARN_GROUP = "Other";
063            private static final Logger LOG = Logger.getLogger(TimeSummaryServiceImpl.class);
064            
065        @Override
066            public TimeSummary getTimeSummary(TimesheetDocument timesheetDocument) {
067                    TimeSummary timeSummary = new TimeSummary();
068    
069                    if(timesheetDocument.getTimeBlocks() == null) {
070                            return timeSummary;
071                    }
072    
073            List<Boolean> dayArrangements = new ArrayList<Boolean>();
074    
075                    timeSummary.setSummaryHeader(getHeaderForSummary(timesheetDocument.getCalendarEntry(), dayArrangements));
076                    TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timesheetDocument.getTimeBlocks(), timesheetDocument.getCalendarEntry(), TkServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true);
077    
078            List<Assignment> timeAssignments = timesheetDocument.getAssignments();
079            List<String> tAssignmentKeys = new ArrayList<String>();
080            for(Assignment assign : timeAssignments) {
081                tAssignmentKeys.add(assign.getAssignmentKey());
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 = combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
087    
088                    timeSummary.setWorkedHours(getWorkedHours(tkTimeBlockAggregate));
089    
090            List<EarnGroupSection> earnGroupSections = getEarnGroupSections(tkTimeBlockAggregate, timeSummary.getSummaryHeader().size()+1, 
091                                    dayArrangements, timesheetDocument.getAsOfDate(), timesheetDocument.getDocEndDate());
092            timeSummary.setSections(earnGroupSections);
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<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    }