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    }