View Javadoc

1   /**
2    * Copyright 2004-2014 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.kpme.tklm.time.timesummary.service;
17  
18  import java.math.BigDecimal;
19  import java.util.*;
20  
21  import org.apache.commons.collections.CollectionUtils;
22  import org.apache.commons.collections.MapUtils;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.log4j.Logger;
25  import org.joda.time.DateTime;
26  import org.joda.time.DateTimeConstants;
27  import org.joda.time.DateTimeFieldType;
28  import org.joda.time.DateTimeZone;
29  import org.joda.time.Interval;
30  import org.joda.time.LocalDate;
31  import org.joda.time.LocalDateTime;
32  import org.kuali.kpme.core.accrualcategory.rule.AccrualCategoryRule;
33  import org.kuali.kpme.core.assignment.Assignment;
34  import org.kuali.kpme.core.assignment.AssignmentDescriptionKey;
35  import org.kuali.kpme.core.calendar.Calendar;
36  import org.kuali.kpme.core.calendar.entry.CalendarEntry;
37  import org.kuali.kpme.core.earncode.EarnCode;
38  import org.kuali.kpme.core.earncode.group.EarnCodeGroup;
39  import org.kuali.kpme.core.job.Job;
40  import org.kuali.kpme.core.service.HrServiceLocator;
41  import org.kuali.kpme.core.util.HrConstants;
42  import org.kuali.kpme.core.workarea.WorkArea;
43  import org.kuali.kpme.tklm.common.TkConstants;
44  import org.kuali.kpme.tklm.leave.block.LeaveBlock;
45  import org.kuali.kpme.tklm.leave.block.LeaveBlockAggregate;
46  import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
47  import org.kuali.kpme.tklm.leave.summary.LeaveSummary;
48  import org.kuali.kpme.tklm.leave.summary.LeaveSummaryRow;
49  import org.kuali.kpme.tklm.time.detail.web.ActionFormUtils;
50  import org.kuali.kpme.tklm.time.flsa.FlsaDay;
51  import org.kuali.kpme.tklm.time.flsa.FlsaWeek;
52  import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
53  import org.kuali.kpme.tklm.time.timehourdetail.TimeHourDetail;
54  import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
55  import org.kuali.kpme.tklm.time.timesummary.AssignmentColumn;
56  import org.kuali.kpme.tklm.time.timesummary.AssignmentRow;
57  import org.kuali.kpme.tklm.time.timesummary.EarnCodeSection;
58  import org.kuali.kpme.tklm.time.timesummary.EarnGroupSection;
59  import org.kuali.kpme.tklm.time.timesummary.TimeSummary;
60  import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
61  
62  public class TimeSummaryServiceImpl implements TimeSummaryService {
63  	private static final String OTHER_EARN_GROUP = "Other";
64  	private static final Logger LOG = Logger.getLogger(TimeSummaryServiceImpl.class);
65  	
66      @Override
67  	public TimeSummary getTimeSummary(TimesheetDocument timesheetDocument) {
68  		TimeSummary timeSummary = new TimeSummary();
69  
70  		if(timesheetDocument == null || timesheetDocument.getTimeBlocks() == null) {
71  			return timeSummary;
72  		}
73  
74          List<Boolean> dayArrangements = new ArrayList<Boolean>();
75  
76  		timeSummary.setTimeSummaryHeader(getHeaderForSummary(timesheetDocument.getCalendarEntry(), timeSummary));
77  		
78  		TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timesheetDocument.getTimeBlocks(), timesheetDocument.getCalendarEntry(), HrServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true);
79  
80          List<Assignment> timeAssignments = timesheetDocument.getAssignments();
81          List<String> tAssignmentKeys = new ArrayList<String>();
82          Set<String> regularEarnCodes = new HashSet<String>();
83          for(Assignment assign : timeAssignments) {
84              tAssignmentKeys.add(assign.getAssignmentKey());
85              regularEarnCodes.add(assign.getJob().getPayTypeObj().getRegEarnCode());
86          }
87          List<LeaveBlock> leaveBlocks =  LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(),
88                  timesheetDocument.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate(), timesheetDocument.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate(), tAssignmentKeys);
89          LeaveBlockAggregate leaveBlockAggregate = new LeaveBlockAggregate(leaveBlocks, timesheetDocument.getCalendarEntry());
90          tkTimeBlockAggregate = TkTimeBlockAggregate.combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
91  
92  		timeSummary.setWorkedHours(getWorkedHours(tkTimeBlockAggregate, regularEarnCodes, timeSummary));
93  		
94  		// Set Flsa week total map
95  		Map<String, BigDecimal> flsaWeekTotal = getHoursToFlsaWeekMap(tkTimeBlockAggregate, timesheetDocument.getPrincipalId(), null);
96  		timeSummary.setFlsaWeekTotalMap(flsaWeekTotal);
97  
98          Map<String,List<EarnGroupSection>> earnGroupSections = getEarnGroupSections(tkTimeBlockAggregate, timeSummary.getTimeSummaryHeader().size()+1, 
99          			dayArrangements, timesheetDocument.getAsOfDate(), timesheetDocument.getDocEndDate());
100         timeSummary.setWeeklySections(earnGroupSections);
101         
102         for (String key : timeSummary.getWeeklySections().keySet()) {
103         	timeSummary.getWeeklySections().put(key,sortEarnGroupSections(timeSummary.getWeeklySections().get(key), regularEarnCodes));
104         }
105         
106 //        timeSummary.setSections(sortEarnGroupSections(earnGroupSections, regularEarnCodes));
107         
108         
109         Map<String, String> aMap = ActionFormUtils.buildAssignmentStyleClassMap(timesheetDocument.getTimeBlocks(), leaveBlocks);
110 		// set css classes for each assignment row
111         for(String week : timeSummary.getWeeklySections().keySet()) {
112 			for (EarnGroupSection earnGroupSection : timeSummary.getWeeklySections().get(week)) {
113 				for (EarnCodeSection section : earnGroupSection.getEarnCodeSections()) {
114 					for (AssignmentRow assignRow : section.getAssignmentsRows()) {
115 						String assignmentCssStyle = MapUtils.getString(aMap, assignRow.getAssignmentKey());
116 						assignRow.setCssClass(assignmentCssStyle);
117 						for (AssignmentColumn assignmentColumn : assignRow.getAssignmentColumns().values()) {
118 							assignmentColumn.setCssClass(assignmentCssStyle);
119 						}
120 					}
121 				}
122 			}
123         }
124         try {
125 			List<LeaveSummaryRow> maxedLeaveRows = getMaxedLeaveRows(timesheetDocument.getCalendarEntry(),timesheetDocument.getPrincipalId());
126 			timeSummary.setMaxedLeaveRows(maxedLeaveRows);
127 		} catch (Exception e) {
128 			// TODO Auto-generated catch block
129 			LOG.error("error retreiving maxed leave rows", e);
130 		}
131 
132 		return timeSummary;
133 	}
134     
135     private List<EarnGroupSection> sortEarnGroupSections(List<EarnGroupSection> sections, Set<String> regularEarnCodes) {
136     	List<EarnGroupSection> sortedList = new ArrayList<EarnGroupSection>();
137     	//first sort by alpha
138     	Collections.sort(sections, new Comparator<EarnGroupSection>() {
139     		@Override
140     		public int compare(EarnGroupSection egs1, EarnGroupSection egs2) {
141     			if (egs1 == null ^ egs2 == null) {
142     				return egs1 == null ? -1 : 1;
143     			}
144     			if (egs1 == null && egs2 == null) {
145     				return 0;
146     			}
147     			//'other' earn group needs to be last.
148     			boolean isOther1 = StringUtils.equals(egs1.getEarnGroup(), OTHER_EARN_GROUP);
149     			boolean isOther2 = StringUtils.equals(egs2.getEarnGroup(), OTHER_EARN_GROUP);
150     			if (isOther1 ^ isOther2) {
151     				return isOther1 ? 1 : -1;
152     			}
153     			if (isOther1 && isOther2) {
154     				return 0;
155     			}
156     			return egs1.getEarnGroup().compareTo(egs2.getEarnGroup());
157     		}
158     	});
159 
160     	List<EarnGroupSection> copy = new ArrayList<EarnGroupSection>(sections);
161     	//loop through in reverse
162     	for (EarnGroupSection egs : copy) {
163     		if (!CollectionUtils.intersection(regularEarnCodes, egs.getEarnCodeToEarnCodeSectionMap().keySet()).isEmpty()) {
164     			sortedList.add(egs);
165     			sections.remove(egs);
166     		}
167     	}
168     	sortedList.addAll(sections);
169     	return sortedList;
170 	}
171 	
172     private List<LeaveSummaryRow> getMaxedLeaveRows(
173 			CalendarEntry calendarEntry, String principalId) throws Exception {
174     	List<LeaveSummaryRow> maxedLeaveRows = new ArrayList<LeaveSummaryRow>();
175     	
176     	if (LmServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, HrConstants.FLSA_STATUS_NON_EXEMPT, true)) {
177     		
178         	Map<String,Set<LeaveBlock>> eligibilities = LmServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry,principalId);
179         	Set<LeaveBlock> onDemandTransfers = eligibilities.get(HrConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND);
180 
181         	Interval calendarEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
182         	
183         	//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
184         	//change, no warning given to the employee of balance limits being exceeded except on or after that day.
185 
186         	if(!onDemandTransfers.isEmpty()) {
187             	for(LeaveBlock lb : onDemandTransfers) {
188             		LocalDate leaveDate = lb.getLeaveLocalDate();
189                 	LeaveSummary summary = LmServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(principalId, leaveDate.plusDays(1));
190                 	LeaveSummaryRow row = summary.getLeaveSummaryRowForAccrualCtgy(lb.getAccrualCategory());
191             		if(row != null) {
192             			//AccrualCategory accrualCategory = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(row.getAccrualCategoryId());
193                     	//AccrualCategoryRule currentRule = HrServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(accrualCategory, asOfDate, pha.getServiceDate());
194                     	if(calendarEntryInterval.contains(leaveDate.toDate().getTime())) {
195                     		//do not allow the on-demand max balance action if the rule the action occurs under is no longer in effect,
196                     		//or if the infraction did not occur within this interval. ( if it occurred during the previous interval, 
197                     		//the employee will have the option to take action in that interval up to & including the end date of that interval. )
198 	            			row.setInfractingLeaveBlockId(lb.getAccrualCategoryRuleId());
199 	            			AccrualCategoryRule aRule = lb.getAccrualCategoryRule();
200 	            			
201 	            			if(StringUtils.equals(aRule.getActionAtMaxBalance(),HrConstants.ACTION_AT_MAX_BALANCE.TRANSFER))
202 	            				row.setTransferable(true);
203 	            			else if(StringUtils.equals(aRule.getActionAtMaxBalance(),HrConstants.ACTION_AT_MAX_BALANCE.PAYOUT))
204 	            				row.setPayoutable(true);
205 	            			
206 	            			boolean exists = false;
207 	            			for(LeaveSummaryRow maxedRow : maxedLeaveRows) {
208 	            				if(StringUtils.equals(maxedRow.getAccrualCategoryId(),row.getAccrualCategoryId()))
209 	            					exists = true;
210 	            			}
211 	            			if(!exists)
212             					maxedLeaveRows.add(row);
213                     	}
214             		}
215             	}
216         	}
217     	}
218 		return maxedLeaveRows;
219 	}
220 
221 	/**
222      * Aggregates timeblocks into the appropriate earngroup-> earncode -> assignment rows
223      * @param tkTimeBlockAggregate
224      * @param numEntries
225      * @param dayArrangements
226      * @param asOfDate
227      * @return
228      */
229 	public Map<String, List<EarnGroupSection>> getEarnGroupSections(TkTimeBlockAggregate tkTimeBlockAggregate, int numEntries, List<Boolean> dayArrangements, LocalDate asOfDate, LocalDate docEndDate){
230 		List<EarnGroupSection> earnGroupSections = new ArrayList<EarnGroupSection>();
231 		Map<String, List<EarnGroupSection>> weeklyEarnGroupSections = new LinkedHashMap<String, List<EarnGroupSection>>();
232 		Map<String, List<EarnCodeSection>> earnCodeSections = new LinkedHashMap<String, List<EarnCodeSection>>();
233 		List<FlsaWeek> flsaWeeks = tkTimeBlockAggregate.getFlsaWeeks(HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback(), DateTimeConstants.SUNDAY, true);
234 		Map<String, EarnCodeSection> earnCodeToEarnCodeSection = new TreeMap<String, EarnCodeSection>();
235 		Map<String, EarnGroupSection> earnGroupToEarnGroupSection = new HashMap<String, EarnGroupSection>();
236 		
237 		int dayCount = 0;
238 		
239 		//TODO remove this and correct the aggregate .. not sure what the down stream changes are
240 		//so leaving this for initial release
241 		List<FlsaWeek> trimmedFlsaWeeks = new ArrayList<FlsaWeek>();
242 		for(FlsaWeek flsaWeek : flsaWeeks){
243 			if(flsaWeek.getFlsaDays().size() > 0) {
244 				trimmedFlsaWeeks.add(flsaWeek);
245 			}
246 		}
247 		
248 		//For every flsa week and day aggegate each time hour detail 
249 		// buckets it by earn code section first
250 		int weekCount = 1; 
251 		for(FlsaWeek flsaWeek : trimmedFlsaWeeks){
252 			earnCodeToEarnCodeSection = new TreeMap<String, EarnCodeSection>();
253 			int weekSize = 0;
254 			List<FlsaDay> flsaDays = flsaWeek.getFlsaDays();
255 			for(FlsaDay flsaDay : flsaDays){
256                 LocalDateTime ld = flsaDay.getFlsaDate();
257                 int ldDay = ld.getDayOfWeek();
258 
259 				Map<String, List<TimeBlock>> earnCodeToTimeBlocks = flsaDay.getEarnCodeToTimeBlocks();
260 				
261 				for(List<TimeBlock> timeBlocks : earnCodeToTimeBlocks.values()){
262 					for(TimeBlock timeBlock : timeBlocks){
263 						for(TimeHourDetail thd : timeBlock.getTimeHourDetails()){
264 							if(StringUtils.equals(HrConstants.LUNCH_EARN_CODE, thd.getEarnCode())){
265 								continue;
266 							}
267 							EarnCodeSection earnCodeSection = earnCodeToEarnCodeSection.get(thd.getEarnCode());
268 							if(earnCodeSection == null){
269 								earnCodeSection = new EarnCodeSection();
270 								earnCodeSection.setEarnCode(thd.getEarnCode());
271 								EarnCode earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(thd.getEarnCode(), asOfDate);
272 								earnCodeSection.setDescription(earnCodeObj != null ? earnCodeObj.getDescription() : null);
273 				 	 	 	 	earnCodeSection.setIsAmountEarnCode(earnCodeObj != null ? HrConstants.EARN_CODE_AMOUNT.equalsIgnoreCase(earnCodeObj.getRecordMethod()) : false);
274 								for(int i = 0;i<(numEntries-1);i++){
275 									earnCodeSection.getTotals().add(BigDecimal.ZERO);
276 								}
277 								earnCodeToEarnCodeSection.put(thd.getEarnCode(), earnCodeSection);
278 							}
279 							String assignKey = timeBlock.getAssignmentKey();
280 							AssignmentRow assignRow = earnCodeSection.getAssignKeyToAssignmentRowMap().get(assignKey);
281 							if(assignRow == null){
282 								assignRow = new AssignmentRow();
283 								assignRow.setAssignmentKey(assignKey);
284 								AssignmentDescriptionKey assignmentKey = HrServiceLocator.getAssignmentService().getAssignmentDescriptionKey(assignKey);
285 								Assignment assignment = HrServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, asOfDate);
286 								// some assignment may not be effective at the beginning of the pay period, use the end date of the period to find it
287 								if(assignment == null) {
288 									assignment = HrServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, asOfDate);
289 								}
290 								//TODO push this up to the assignment fetch/fully populated instead of like this
291 								if(assignment != null){
292 									if(assignment.getJob() == null){
293 										Job aJob = HrServiceLocator.getJobService().getJob(assignment.getPrincipalId(),assignment.getJobNumber(), assignment.getEffectiveLocalDate());
294 										assignment.setJob(aJob);
295 									}
296 									if(assignment.getWorkAreaObj() == null){
297 										WorkArea aWorkArea = HrServiceLocator.getWorkAreaService().getWorkAreaWithoutRoles(assignment.getWorkArea(), assignment.getEffectiveLocalDate());
298 										assignment.setWorkAreaObj(aWorkArea);
299 									}
300 									assignRow.setDescr(assignment.getAssignmentDescription());
301 								}
302 								assignRow.setEarnCodeSection(earnCodeSection);
303 								for (int i = 1; i < numEntries; i++) {
304 									AssignmentColumn assignmentColumn = new AssignmentColumn();
305 //									assignmentColumn.setTotal(HrConstants.BIG_DECIMAL_SCALED_ZERO);
306 //									assignmentColumn.setAmount(HrConstants.BIG_DECIMAL_SCALED_ZERO);
307 									assignRow.addAssignmentColumn(i,assignmentColumn);
308 								}
309 								earnCodeSection.addAssignmentRow(assignRow);
310 							}
311 							
312 							assignRow.addToTotal(timeBlock.getBeginDateTime().getDayOfWeek(), thd.getHours());
313 							assignRow.addToAmount(timeBlock.getBeginDateTime().getDayOfWeek(), thd.getAmount());
314 
315 						}
316 					}
317 				}
318 				dayCount++;
319 				weekSize++;
320 			}
321 			
322 			weekSize = 0;
323 			dayCount = 0;
324 			earnCodeSections.put("Week "+weekCount++, new LinkedList<EarnCodeSection>(earnCodeToEarnCodeSection.values()));
325 		}
326 		
327 		dayCount = 0;
328 		//now create all teh earn group sections and aggregate accordingly
329 		for (String key : earnCodeSections.keySet()){
330 			earnGroupToEarnGroupSection = new HashMap<String, EarnGroupSection>();
331 			earnGroupSections = new ArrayList<EarnGroupSection>();
332 			List<EarnCodeSection> earnCodeToEarnCodeSections = earnCodeSections.get(key);
333 			for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSections){
334 				String earnCode = earnCodeSection.getEarnCode();
335 				EarnCodeGroup earnGroupObj = HrServiceLocator.getEarnCodeGroupService().getEarnCodeGroupSummaryForEarnCode(earnCode, asOfDate);
336 				String earnGroup = null;
337 				if(earnGroupObj == null){
338 					earnGroup = OTHER_EARN_GROUP;
339 				} else{
340 					earnGroup = earnGroupObj.getDescr();
341 				}
342 				
343 				EarnGroupSection earnGroupSection = earnGroupToEarnGroupSection.get(earnGroup);
344 				if(earnGroupSection == null){
345 					earnGroupSection = new EarnGroupSection();
346 					earnGroupSection.setEarnGroup(earnGroup);
347 					for(int i =1;i<(numEntries);i++){
348 						earnGroupSection.getTotals().put(i,BigDecimal.ZERO);
349 						earnGroupSection.getTotals().get(i).setScale(HrConstants.BIG_DECIMAL_SCALE, HrConstants.BIG_DECIMAL_SCALE_ROUNDING);
350 					}
351 					earnGroupToEarnGroupSection.put(earnGroup, earnGroupSection);
352 				}
353 				earnGroupSection.addEarnCodeSection(earnCodeSection, dayArrangements);
354 				
355 			}
356 			for(EarnGroupSection earnGroupSection : earnGroupToEarnGroupSection.values()){
357 				earnGroupSections.add(earnGroupSection);
358 			}
359 		    weeklyEarnGroupSections.put(key, earnGroupSections);
360 		}
361 		
362 		return weeklyEarnGroupSections;
363 	}
364 	
365 	/**
366 	 * Generate a list of string describing this pay calendar entry for the summary
367 	 * @param payCalEntry
368 	 * @return
369 	 */
370 	protected List<String> getSummaryHeader(CalendarEntry payCalEntry){
371 		List<String> summaryHeader = new ArrayList<String>();
372 		int dayCount = 0;
373 		DateTime beginDateTime = payCalEntry.getBeginPeriodFullDateTime();
374 		DateTime endDateTime = payCalEntry.getEndPeriodFullDateTime();
375 		boolean virtualDays = false;
376         LocalDateTime endDate = payCalEntry.getEndPeriodLocalDateTime();
377 
378         if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
379                 endDate.get(DateTimeFieldType.secondOfMinute()) != 0){
380             virtualDays = true;
381         }
382 		
383         DateTime currentDateTime = beginDateTime;
384 		
385 		while(currentDateTime.isBefore(endDateTime)){
386 			LocalDateTime currDate = currentDateTime.toLocalDateTime();
387 			summaryHeader.add(makeHeaderDiplayString(currDate, virtualDays));
388 			
389 			dayCount++;
390 			if((dayCount % 7) == 0){
391 				summaryHeader.add("Week "+ ((dayCount / 7)));
392 			}
393 			currentDateTime = currentDateTime.plusDays(1);
394 		}
395 		
396 		summaryHeader.add("Period Total");
397 		return summaryHeader;
398 	}
399 
400     /**
401      * Provides the number of hours worked for the pay period indicated in the
402      * aggregate.
403      *
404      * @param aggregate The aggregate we are summing
405      *
406      * @return A list of BigDecimals containing the number of hours worked.
407      * This list will line up with the header.
408      */
409     private List<BigDecimal> getWorkedHours(TkTimeBlockAggregate aggregate, Set<String> regularEarnCodes, TimeSummary timeSummary) {
410         List<BigDecimal> hours = new ArrayList<BigDecimal>();
411         Map<Integer, BigDecimal> weekHours = new TreeMap<Integer, BigDecimal>();
412         Map<String, BigDecimal> weekTotalMap = new LinkedHashMap<String, BigDecimal>();
413         
414         BigDecimal periodTotal = HrConstants.BIG_DECIMAL_SCALED_ZERO;
415         int i=0;
416         for (FlsaWeek week : aggregate.getFlsaWeeks(HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback(), DateTimeConstants.SUNDAY, true)) {
417         	weekHours = new TreeMap<Integer, BigDecimal>();
418         	
419             BigDecimal weeklyTotal = HrConstants.BIG_DECIMAL_SCALED_ZERO;
420             for (FlsaDay day : week.getFlsaDays()) {
421                 BigDecimal totalForDay = HrConstants.BIG_DECIMAL_SCALED_ZERO;
422                 LocalDateTime ld = day.getFlsaDate();
423                 int ldDay = ld.getDayOfWeek();
424                 for (TimeBlock block : day.getAppliedTimeBlocks()) {
425                     EarnCode ec = HrServiceLocator.getEarnCodeService().getEarnCode(block.getEarnCode(), block.getEndDateTime().toLocalDate());
426                     if (ec != null
427                             && (ec.getOvtEarnCode()
428                             || regularEarnCodes.contains(ec.getEarnCode()) || ec.getCountsAsRegularPay().equals("Y"))) {
429                         totalForDay = totalForDay.add(block.getHours(), HrConstants.MATH_CONTEXT);
430                         weeklyTotal = weeklyTotal.add(block.getHours(), HrConstants.MATH_CONTEXT);
431                         periodTotal = periodTotal.add(block.getHours(), HrConstants.MATH_CONTEXT);
432                     }
433                 }
434                 weekHours.put(ldDay, totalForDay);
435                 hours.add(totalForDay);
436             }
437             i++;
438             weekTotalMap.put("Week "+i, weeklyTotal);
439             timeSummary.getWeeklyWorkedHours().put("Week "+i, weekHours);
440             hours.add(weeklyTotal);
441         }
442         hours.add(periodTotal);
443         timeSummary.setGrandTotal(periodTotal);
444         timeSummary.setWeekTotalMap(weekTotalMap);
445         return hours;
446     }
447 
448 
449     /**
450      * Handles the generation of the display header for the time summary.
451      *
452      * @param cal The PayCalendarEntries object we are using to derive information.
453      * @param dayArrangements Container passed in to store the position of week / period aggregate sums
454      *
455      * @return An in-order string of days for this period that properly accounts
456      * for FLSA week boundaries in the pay period.
457      */
458     @Override
459     public List<String> getHeaderForSummary(CalendarEntry cal, List<Boolean> dayArrangements) {
460  		List<String> header = new ArrayList<String>();
461         if (cal == null) {
462             return Collections.emptyList();
463         }
464         // Maps directly to joda time day of week constants.
465         int flsaBeginDay = this.getPayCalendarForEntry(cal).getFlsaBeginDayConstant();
466         boolean virtualDays = false;
467         LocalDateTime startDate = cal.getBeginPeriodLocalDateTime();
468         LocalDateTime endDate = cal.getEndPeriodLocalDateTime();
469 
470         // Increment end date if we are on a virtual day calendar, so that the
471         // for loop can account for having the proper amount of days on the
472         // summary calendar.
473         if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
474                 endDate.get(DateTimeFieldType.secondOfMinute()) != 0)
475         {
476             endDate = endDate.plusDays(1);
477             virtualDays = true;
478         }
479 
480         boolean afterFirstDay = false;
481         int week = 1;
482         for (LocalDateTime currentDate = startDate; currentDate.compareTo(endDate) < 0; currentDate = currentDate.plusDays(1)) {
483 
484             if (currentDate.getDayOfWeek() == flsaBeginDay && afterFirstDay) {
485                 header.add("Week " + week);
486                 dayArrangements.add(false);
487                 week++;
488             }
489 
490             header.add(makeHeaderDiplayString(currentDate, virtualDays));
491             dayArrangements.add(true);
492 
493 
494             afterFirstDay = true;
495         }
496 
497         // We may have a very small final "week" on this pay period. For now
498         // we will mark it as a week, and if someone doesn't like it, it can
499         // be removed.
500         if (!header.isEmpty() && !header.get(header.size() - 1).startsWith("Week")) {
501             dayArrangements.add(false);
502             header.add("Week " + week);
503         }
504 
505 
506         header.add("Period Total");
507         dayArrangements.add(false);
508         return header;
509     }
510 
511     public Map<Integer, String> getHeaderForSummary(CalendarEntry cal, TimeSummary timeSummary) {
512         Map<Integer, String> header = new LinkedHashMap<Integer,String>();
513         Map<String, String> weekDates = new LinkedHashMap<String,String>();
514         
515         header.put(DateTimeConstants.SUNDAY, "Sun");
516         header.put(DateTimeConstants.MONDAY, "Mon");
517         header.put(DateTimeConstants.TUESDAY, "Tue");
518         header.put(DateTimeConstants.WEDNESDAY, "Wed");
519         header.put(DateTimeConstants.THURSDAY, "Thu");
520         header.put(DateTimeConstants.FRIDAY, "Fri");
521         header.put(DateTimeConstants.SATURDAY, "Sat");
522         
523         LocalDateTime startDate = cal.getBeginPeriodLocalDateTime();
524         LocalDateTime endDate = cal.getEndPeriodLocalDateTime();
525 
526         LocalDateTime actualStartDate = cal.getBeginPeriodLocalDateTime();
527         LocalDateTime actualEndDate = cal.getEndPeriodLocalDateTime();
528         
529         int daysToMinus = 0;
530         if(DateTimeConstants.SUNDAY != startDate.getDayOfWeek()) {
531         	daysToMinus = startDate.getDayOfWeek();
532         }
533         
534         actualStartDate = startDate.minusDays(daysToMinus);
535         int daysToAdd = 0;
536         if(endDate.getDayOfWeek() != DateTimeConstants.SUNDAY) {
537         	daysToAdd = DateTimeConstants.SATURDAY - endDate.getDayOfWeek();
538         } else {
539         	daysToAdd = DateTimeConstants.SATURDAY;
540         }
541         
542         actualEndDate = endDate.plusDays(daysToAdd);
543         
544         // Increment end date if we are on a virtual day calendar, so that the
545         // for loop can account for having the proper amount of days on the
546         // summary calendar.
547         if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
548                 endDate.get(DateTimeFieldType.secondOfMinute()) != 0)
549         {
550             endDate = endDate.plusDays(1);
551         }
552 
553         boolean afterFirstDay = false;
554         int week = 1;
555         
556         LocalDateTime weekStart = actualStartDate;
557         LocalDateTime weekEnd = actualStartDate;
558         for (LocalDateTime currentDate = actualStartDate; currentDate.compareTo(actualEndDate) < 0; currentDate = currentDate.plusDays(1)) {
559         	
560             if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY && afterFirstDay) {
561                 StringBuilder display = new StringBuilder();
562                 display.append(weekStart.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
563                 display.append(" - ");
564                 display.append(weekEnd.minusDays(1).toString(TkConstants.DT_ABBREV_DATE_FORMAT));
565                 weekDates.put("Week "+week, display.toString());
566                 weekStart = currentDate;
567                 week++;
568             }
569 
570             weekEnd = weekEnd.plusDays(1);
571             afterFirstDay = true;
572         }
573 
574         // We may have a very small final "week" on this pay period. For now
575         // we will mark it as a week, and if someone doesn't like it, it can
576         // be removed.
577         if (!header.isEmpty() && !header.get(header.size() - 1).startsWith("Week")) {
578         	StringBuilder display = new StringBuilder();
579             display.append(weekStart.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
580             display.append(" - ");
581             display.append(actualEndDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
582             weekDates.put("Week "+week, display.toString());
583         }
584         
585         timeSummary.setWeekDates(weekDates);
586         return header;
587     }
588     /**
589      * Helper function to generate display text for the summary header.
590      * @param currentDate The date we are generating for.
591      * @param virtualDays Whether or not virtual days apply.
592      * @return A string appropriate for UI display.
593      */
594     private String makeHeaderDiplayString(LocalDateTime currentDate, boolean virtualDays) {
595         StringBuilder display = new StringBuilder();
596         
597         display.append(currentDate.toString("E"));
598         if (virtualDays) {
599         	LocalDateTime nextDate = currentDate.plusDays(1);
600         	display.append(" - ");
601             display.append(nextDate.toString("E"));
602         }
603         
604 //        display.append("<br />");
605 //        
606 //        display.append(currentDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
607 //        if (virtualDays) {
608 //            LocalDateTime nextDate = currentDate.plusDays(1);
609 //            display.append(" - ");
610 //            display.append(nextDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
611 //        }
612 
613         return display.toString();
614     }
615 
616     /**
617      * @param calEntry Calendar entry we are using for lookup.
618      * @return The PayCalendar that owns the provided entry.
619      */
620     private Calendar getPayCalendarForEntry(CalendarEntry calEntry) {
621         Calendar cal = null;
622 
623         if (calEntry != null) {
624             cal = HrServiceLocator.getCalendarService().getCalendar(calEntry.getHrCalendarId());
625         }
626 
627         return cal;
628     }
629 
630     
631 	private Map<String, BigDecimal> getHoursToFlsaWeekMap(TkTimeBlockAggregate tkTimeBlockAggregate, String principalId, Long workArea) {
632 		
633 		Map<String, BigDecimal> hoursToFlsaWeekMap = new LinkedHashMap<String, BigDecimal>();
634 		DateTimeZone dateTimeZone = HrServiceLocator.getTimezoneService()
635 				.getUserTimezoneWithFallback();
636 		List<List<FlsaWeek>> flsaWeeks = tkTimeBlockAggregate.getFlsaWeeks(dateTimeZone, principalId);
637 		
638 		int weekCount = 1;
639 		for (List<FlsaWeek> flsaWeekParts : flsaWeeks) {
640 			BigDecimal weekTotal = new BigDecimal(0.00);
641 			for (FlsaWeek flsaWeekPart : flsaWeekParts) {
642 				for (FlsaDay flsaDay : flsaWeekPart.getFlsaDays()) {
643 					for (TimeBlock timeBlock : flsaDay.getAppliedTimeBlocks()) {
644 						if (workArea != null) {
645 							if (timeBlock.getWorkArea().compareTo(workArea) == 0) {
646 								weekTotal = weekTotal.add(timeBlock.getHours(), HrConstants.MATH_CONTEXT);
647 							} else {
648 								weekTotal = weekTotal.add(new BigDecimal("0"), HrConstants.MATH_CONTEXT);
649 							}
650 						} else {
651 							weekTotal = weekTotal.add(timeBlock.getHours(),HrConstants.MATH_CONTEXT);
652 						}
653 					}
654 				}
655 			}
656 			hoursToFlsaWeekMap.put("Week " + weekCount++, weekTotal);
657 		}
658 		
659 		return hoursToFlsaWeekMap;
660 	}
661 }