View Javadoc

1   /**
2    * Copyright 2004-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.hr.time.approval.service;
17  
18  import com.google.common.collect.HashMultimap;
19  import com.google.common.collect.Multimap;
20  import org.apache.commons.collections.CollectionUtils;
21  import org.apache.commons.lang.StringUtils;
22  import org.apache.commons.lang3.time.DateUtils;
23  import org.apache.log4j.Logger;
24  import org.joda.time.*;
25  import org.joda.time.format.DateTimeFormat;
26  import org.joda.time.format.DateTimeFormatter;
27  import org.kuali.hr.lm.LMConstants;
28  import org.kuali.hr.lm.accrual.AccrualCategory;
29  import org.kuali.hr.lm.accrual.AccrualCategoryRule;
30  import org.kuali.hr.lm.leaveblock.LeaveBlock;
31  import org.kuali.hr.lm.leavecalendar.validation.LeaveCalendarValidationUtil;
32  import org.kuali.hr.lm.util.LeaveBlockAggregate;
33  import org.kuali.hr.time.approval.web.ApprovalTimeSummaryRow;
34  import org.kuali.hr.time.assignment.Assignment;
35  import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
36  import org.kuali.hr.time.calendar.Calendar;
37  import org.kuali.hr.time.calendar.CalendarEntries;
38  import org.kuali.hr.time.clocklog.ClockLog;
39  import org.kuali.hr.time.earncode.EarnCode;
40  import org.kuali.hr.time.flsa.FlsaDay;
41  import org.kuali.hr.time.flsa.FlsaWeek;
42  import org.kuali.hr.time.principal.PrincipalHRAttributes;
43  import org.kuali.hr.time.roles.TkUserRoles;
44  import org.kuali.hr.time.roles.UserRoles;
45  import org.kuali.hr.time.service.base.TkServiceLocator;
46  import org.kuali.hr.time.timeblock.TimeBlock;
47  import org.kuali.hr.time.timeblock.TimeHourDetail;
48  import org.kuali.hr.time.timesheet.TimesheetDocument;
49  import org.kuali.hr.time.util.*;
50  import org.kuali.hr.time.workarea.WorkArea;
51  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
52  import org.kuali.rice.kew.api.KewApiServiceLocator;
53  import org.kuali.rice.kew.api.note.Note;
54  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
55  import org.kuali.rice.kew.service.KEWServiceLocator;
56  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
57  import org.kuali.rice.krad.util.GlobalVariables;
58  import org.kuali.rice.kim.api.identity.principal.Principal;
59  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
60  import org.springframework.jdbc.support.rowset.SqlRowSet;
61  
62  import java.math.BigDecimal;
63  import java.sql.Types;
64  import java.text.SimpleDateFormat;
65  import java.util.*;
66  import java.util.Map.Entry;
67  
68  public class TimeApproveServiceImpl implements TimeApproveService {
69  
70  	private static final Logger LOG = Logger
71  			.getLogger(TimeApproveServiceImpl.class);
72  
73  	public static final int DAYS_WINDOW_DELTA = 31;
74  
75  	public Map<String, CalendarEntries> getPayCalendarEntriesForDept(
76  			String dept, Date currentDate) {
77  		DateTime minDt = new DateTime(currentDate,
78  				TKUtils.getSystemDateTimeZone());
79  		minDt = minDt.minusDays(DAYS_WINDOW_DELTA);
80  		java.sql.Date windowDate = TKUtils.getTimelessDate(minDt.toDate());
81  
82  		Map<String, CalendarEntries> pceMap = new HashMap<String, CalendarEntries>();
83  		Set<String> principals = new HashSet<String>();
84  		List<WorkArea> workAreasForDept = TkServiceLocator.getWorkAreaService()
85  				.getWorkAreas(dept, new java.sql.Date(currentDate.getTime()));
86  		// Get all of the principals within our window of time.
87  		for (WorkArea workArea : workAreasForDept) {
88  			Long waNum = workArea.getWorkArea();
89  			List<Assignment> assignments = TkServiceLocator
90  					.getAssignmentService().getActiveAssignmentsForWorkArea(
91  							waNum, TKUtils.getTimelessDate(currentDate));
92  
93  			if (assignments != null) {
94  				for (Assignment assignment : assignments) {
95  					principals.add(assignment.getPrincipalId());
96  				}
97  			} else {
98  				assignments = TkServiceLocator.getAssignmentService()
99  						.getActiveAssignmentsForWorkArea(waNum, windowDate);
100 				if (assignments != null) {
101 					for (Assignment assignment : assignments) {
102 						principals.add(assignment.getPrincipalId());
103 					}
104 				}
105 			}
106 		}
107 
108 		// Get the pay calendars
109 		Set<Calendar> payCals = new HashSet<Calendar>();
110 		for (String pid : principals) {
111 			PrincipalHRAttributes pc = TkServiceLocator
112 					.getPrincipalHRAttributeService().getPrincipalCalendar(pid,
113 							currentDate);
114 			if (pc == null)
115 				pc = TkServiceLocator.getPrincipalHRAttributeService()
116 						.getPrincipalCalendar(pid, windowDate);
117 
118 			if (pc != null) {
119 				payCals.add(pc.getCalendar());
120 			} else {
121 				LOG.warn("PrincipalCalendar null for principal: '" + pid + "'");
122 			}
123 		}
124 
125 		// Grab the pay calendar entries + groups
126 		for (Calendar pc : payCals) {
127 			CalendarEntries pce = TkServiceLocator
128 					.getCalendarEntriesService()
129 					.getCurrentCalendarEntriesByCalendarId(
130                             pc.getHrCalendarId(), currentDate);
131 			pceMap.put(pc.getCalendarName(), pce);
132 		}
133 
134 		return pceMap;
135 	}
136 
137 	@Override
138 	public Map<String, CalendarEntries> getPayCalendarEntriesForApprover(
139 			String principalId, Date currentDate, String dept) {
140 
141 		Map<String, CalendarEntries> pceMap = new HashMap<String, CalendarEntries>();
142 		Set<String> principals = new HashSet<String>();
143 		DateTime minDt = new DateTime(currentDate,
144 				TKUtils.getSystemDateTimeZone());
145 		minDt = minDt.minusDays(DAYS_WINDOW_DELTA);
146 		java.sql.Date windowDate = TKUtils.getTimelessDate(minDt.toDate());
147 		Set<Long> approverWorkAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas();
148 
149 		// Get all of the principals within our window of time.
150 		for (Long waNum : approverWorkAreas) {
151 			List<Assignment> assignments = TkServiceLocator
152 					.getAssignmentService().getActiveAssignmentsForWorkArea(
153 							waNum, TKUtils.getTimelessDate(currentDate));
154 
155 			if (assignments != null) {
156 				for (Assignment assignment : assignments) {
157 					principals.add(assignment.getPrincipalId());
158 				}
159 			}
160 		}
161 
162 		// Get the pay calendars
163 		Set<Calendar> payCals = new HashSet<Calendar>();
164 		for (String pid : principals) {
165 			PrincipalHRAttributes pc = TkServiceLocator
166 					.getPrincipalHRAttributeService().getPrincipalCalendar(pid,
167                             currentDate);
168 			if (pc == null)
169 				pc = TkServiceLocator.getPrincipalHRAttributeService()
170 						.getPrincipalCalendar(pid, windowDate);
171 
172 			if (pc != null) {
173 				payCals.add(pc.getCalendar());
174 			} else {
175 				LOG.warn("PrincipalCalendar null for principal: '" + pid + "'");
176 			}
177 		}
178 
179 		// Grab the pay calendar entries + groups
180 		for (Calendar pc : payCals) {
181 			CalendarEntries pce = TkServiceLocator
182 					.getCalendarEntriesService()
183 					.getCurrentCalendarEntriesByCalendarId(
184                             pc.getHrCalendarId(), currentDate);
185 			pceMap.put(pc.getCalendarName(), pce);
186 		}
187 
188 		return pceMap;
189 	}
190 
191 	public SortedSet<String> getApproverPayCalendarGroups(Date payBeginDate,
192 			Date payEndDate) {
193 		SortedSet<String> pcg = new TreeSet<String>();
194 
195 		Set<Long> approverWorkAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas();
196 		List<Assignment> assignments = new ArrayList<Assignment>();
197 
198 		for (Long workArea : approverWorkAreas) {
199 			if (workArea != null) {
200 				assignments.addAll(TkServiceLocator.getAssignmentService()
201 						.getActiveAssignmentsForWorkArea(workArea,
202 								new java.sql.Date(payBeginDate.getTime())));
203 			}
204 		}
205 		if (!assignments.isEmpty()) {
206 			for (Assignment assign : assignments) {
207 				String principalId = assign.getPrincipalId();
208 				TimesheetDocumentHeader tdh = TkServiceLocator
209 						.getTimesheetDocumentHeaderService().getDocumentHeader(
210 								principalId, payBeginDate, payEndDate);
211 				if (tdh != null) {
212 					String pyCalendarGroup = TkServiceLocator
213 							.getPrincipalHRAttributeService()
214 							.getPrincipalCalendar(principalId, tdh.getBeginDate())
215 							.getCalendar().getCalendarName();
216 					pcg.add(pyCalendarGroup);
217 				}
218 			}
219 		}
220 		return pcg;
221 	}
222 
223 	@SuppressWarnings("rawtypes")
224 	@Override
225 	public List<ApprovalTimeSummaryRow> getApprovalSummaryRows(
226 			Date payBeginDate, Date payEndDate, String calGroup,
227 			List<String> principalIds, List<String> payCalendarLabels,
228 			CalendarEntries payCalendarEntries) {
229 		List<ApprovalTimeSummaryRow> rows = new LinkedList<ApprovalTimeSummaryRow>();
230 		Map<String, TimesheetDocumentHeader> principalDocumentHeader = getPrincipalDocumentHeader(
231 				principalIds, payBeginDate, payEndDate);
232 
233 		Calendar payCalendar = TkServiceLocator.getCalendarService()
234 				.getCalendar(payCalendarEntries.getHrCalendarId());
235 		DateTimeZone dateTimeZone = TkServiceLocator.getTimezoneService()
236 				.getUserTimezoneWithFallback();
237 		List<Interval> dayIntervals = TKUtils
238 				.getDaySpanForCalendarEntry(payCalendarEntries);
239 
240 
241 		for (String principalId : principalIds) {
242 			TimesheetDocumentHeader tdh = new TimesheetDocumentHeader();
243 			String documentId = "";
244 			if (principalDocumentHeader.containsKey(principalId)) {
245 				tdh = principalDocumentHeader.get(principalId);
246 				documentId = principalDocumentHeader.get(principalId).getDocumentId();
247 			}
248 			List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
249             List<LeaveBlock> leaveBlocks = new ArrayList<LeaveBlock>();
250 			List<Note> notes = new ArrayList<Note>();
251 			List<String> warnings = new ArrayList<String>();
252 
253 			ApprovalTimeSummaryRow approvalSummaryRow = new ApprovalTimeSummaryRow();
254 
255 			if (principalDocumentHeader.containsKey(principalId)) {
256 				approvalSummaryRow
257 						.setApprovalStatus(TkConstants.DOC_ROUTE_STATUS.get(tdh
258 								.getDocumentStatus()));
259 			}
260 
261             Set<String> regularEarnCodes = new HashSet<String>();
262 			if (StringUtils.isNotBlank(documentId)) {
263                 TimesheetDocument td = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
264                 if (td != null) {
265                     timeBlocks = td.getTimeBlocks();
266                     //timeBlocks = TkServiceLocator.getTimeBlockService()
267                     //		.getTimeBlocks(documentId);
268                     List<String> assignKeys = new ArrayList<String>();
269                     for(Assignment a : td.getAssignments()) {
270                         assignKeys.add(a.getAssignmentKey());
271                         regularEarnCodes.add(a.getJob().getPayTypeObj().getRegEarnCode());
272                     }
273                     leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId,
274                             payBeginDate, payEndDate, assignKeys);
275                     notes = getNotesForDocument(documentId);
276 
277                     warnings = TkServiceLocator.getWarningService().getWarnings(td);
278                 }
279 
280 			}
281 			Map<String, Set<String>> transactionalWarnings = LeaveCalendarValidationUtil.validatePendingTransactions(principalId, payCalendarEntries.getBeginPeriodDate(), payCalendarEntries.getEndPeriodDate());
282 			
283 			warnings.addAll(transactionalWarnings.get("infoMessages"));
284 			warnings.addAll(transactionalWarnings.get("warningMessages"));
285 			warnings.addAll(transactionalWarnings.get("actionMessages"));
286 			
287 			Map<String, Set<String>> eligibleTransfers = findWarnings(principalId, payCalendarEntries);
288 			warnings.addAll(eligibleTransfers.get("warningMessages"));
289 			
290 			Map<String, BigDecimal> hoursToPayLabelMap = getHoursToPayDayMap(
291 					principalId, payEndDate, payCalendarLabels,
292 					timeBlocks, leaveBlocks, null, payCalendarEntries, payCalendar,
293 					dateTimeZone, dayIntervals);
294 			
295 			Map<String, BigDecimal> hoursToFlsaPayLabelMap = getHoursToFlsaWeekMap(
296 					principalId, payEndDate, payCalendarLabels,
297 					timeBlocks, leaveBlocks, null, payCalendarEntries, payCalendar,
298 					dateTimeZone, dayIntervals);
299 
300             EntityNamePrincipalName name = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
301 			approvalSummaryRow.setName(name != null
302                     && name.getDefaultName() != null
303                     && name.getDefaultName().getCompositeName() != null ? name.getDefaultName().getCompositeName() : principalId);
304 			approvalSummaryRow.setPrincipalId(principalId);
305 			approvalSummaryRow.setPayCalendarGroup(calGroup);
306 			approvalSummaryRow.setDocumentId(documentId);
307 			approvalSummaryRow.setHoursToPayLabelMap(hoursToPayLabelMap);
308 			approvalSummaryRow.setHoursToFlsaPayLabelMap(hoursToFlsaPayLabelMap);
309 			approvalSummaryRow.setPeriodTotal(hoursToPayLabelMap
310 					.get("Period Total"));
311 			approvalSummaryRow.setLstTimeBlocks(timeBlocks);
312 			approvalSummaryRow.setNotes(notes);
313 			approvalSummaryRow.setWarnings(warnings);
314 
315 			// Compare last clock log versus now and if > threshold
316 			// highlight entry
317 			ClockLog lastClockLog = TkServiceLocator.getClockLogService()
318 					.getLastClockLog(principalId);
319 			if (isSynchronousUser(principalId)) {
320                 approvalSummaryRow.setClockStatusMessage(createLabelForLastClockLog(lastClockLog));
321             }
322 			if (lastClockLog != null
323 					&& (StringUtils.equals(lastClockLog.getClockAction(),
324 							TkConstants.CLOCK_IN) || StringUtils
325 							.equals(lastClockLog.getClockAction(),
326 									TkConstants.LUNCH_IN))) {
327 				DateTime startTime = new DateTime(lastClockLog
328 						.getClockTimestamp().getTime());
329 				DateTime endTime = new DateTime(System.currentTimeMillis());
330 
331 				Hours hour = Hours.hoursBetween(startTime, endTime);
332 				if (hour != null) {
333 					int elapsedHours = hour.getHours();
334 					if (elapsedHours >= TkConstants.NUMBER_OF_HOURS_CLOCKED_IN_APPROVE_TAB_HIGHLIGHT) {
335 						approvalSummaryRow.setClockedInOverThreshold(true);
336 					}
337 				}
338 
339 			}
340 			rows.add(approvalSummaryRow);
341 		}
342 		return rows;
343 	}
344 
345     private boolean isSynchronousUser(String principalId) {
346         List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(principalId, TKUtils.getCurrentDate());
347         boolean isSynchronousUser = false;
348         if (CollectionUtils.isNotEmpty(assignments)) {
349             for (Assignment assignment : assignments) {
350                 isSynchronousUser |= assignment.isSynchronous();
351             }
352         }
353         return isSynchronousUser;
354     }
355 
356 	public List<TimesheetDocumentHeader> getDocumentHeadersByPrincipalIds(
357 			Date payBeginDate, Date payEndDate, List<String> principalIds) {
358 		List<TimesheetDocumentHeader> headers = new LinkedList<TimesheetDocumentHeader>();
359 		for (String principalId : principalIds) {
360 			TimesheetDocumentHeader tdh = TkServiceLocator
361 					.getTimesheetDocumentHeaderService().getDocumentHeader(
362 							principalId, payBeginDate, payEndDate);
363 			if (tdh != null) {
364 				headers.add(tdh);
365 			}
366 		}
367 
368 		return headers;
369 	}
370 
371 	/**
372 	 * Get pay calendar labels for approval tab
373 	 */
374 	public List<String> getPayCalendarLabelsForApprovalTab(Date payBeginDate,
375 			Date payEndDate) {
376 		// :)
377 		// http://stackoverflow.com/questions/111933/why-shouldnt-i-use-hungarian-notation
378 		List<String> lstPayCalendarLabels = new ArrayList<String>();
379 		DateTime payBegin = new DateTime(payBeginDate.getTime());
380 		DateTime payEnd = new DateTime(payEndDate.getTime());
381 		DateTime currTime = payBegin;
382 		int dayCounter = 1;
383 		int weekCounter = 1;
384 
385 		while (currTime.isBefore(payEnd)) {
386 			String labelForDay = createLabelForDay(currTime);
387 			lstPayCalendarLabels.add(labelForDay);
388 			currTime = currTime.plusDays(1);
389 			if ((dayCounter % 7) == 0) {
390 				lstPayCalendarLabels.add("Week " + weekCounter);
391 				weekCounter++;
392 			}
393 			dayCounter++;
394 		}
395 		lstPayCalendarLabels.add("Total Hours");
396 		return lstPayCalendarLabels;
397 	}
398 
399 	private Map<String, Set<String>> findWarnings(String principalId, CalendarEntries calendarEntry) {
400 //      List<String> warnings = LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(leaveBlocks);
401       Map<String, Set<String>> allMessages = new HashMap<String,Set<String>>();
402       allMessages.put("warningMessages", new HashSet<String>());
403 
404       Map<String, Set<LeaveBlock>> eligibilities;
405 
406   	 eligibilities = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry, principalId);
407 
408       if (eligibilities != null) {
409           for (Entry<String,Set<LeaveBlock>> entry : eligibilities.entrySet()) {
410         	  for(LeaveBlock lb : entry.getValue()) {
411         		  AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(lb.getAccrualCategoryRuleId());
412         		  if (rule != null) {
413         			  AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(rule.getLmAccrualCategoryId());
414         			  if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.TRANSFER)) {
415         				  //Todo: add link to balance transfer
416         				  allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance.");   //warningMessages
417         			  } else if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
418         				  //Todo: compute and display amount of time lost.
419         				  allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance.");      //warningMessages
420         			  } else if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.PAYOUT)) {
421         				  //Todo: display payout details.
422         				  allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance.");      //warningMessages            				  
423         			  }
424         		  }
425         	  }
426           }
427       }
428       
429       return allMessages;
430   }
431 	
432 	/**
433 	 * Create label for a given pay calendar day
434 	 * 
435 	 * @param fromDate
436 	 * @return
437 	 */
438 	private String createLabelForDay(DateTime fromDate) {
439 		DateMidnight dateMidnight = new DateMidnight(fromDate);
440 		if (dateMidnight.compareTo(fromDate) == 0) {
441 			DateTimeFormatter fmt = DateTimeFormat.forPattern("MMM/dd");
442 			return fmt.print(fromDate);
443 		}
444 		DateTime toDate = fromDate.plusDays(1);
445 		DateTimeFormatter fmt = DateTimeFormat.forPattern("MMM/dd k:m:s");
446 		return fmt.print(fromDate) + "-" + fmt.print(toDate);
447 	}
448 
449 	/**
450 	 * Create label for the last clock log
451 	 * 
452 	 * @param cl
453 	 * @return
454 	 */
455 	private String createLabelForLastClockLog(ClockLog cl) {
456 		// return sdf.format(dt);
457 		if (cl == null) {
458 			return "No previous clock information";
459 		}
460 		SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy hh:mm a");
461 		String dateTime = sdf.format(new java.sql.Date(cl.getClockTimestamp()
462 				.getTime()));
463 		if (StringUtils.equals(cl.getClockAction(), TkConstants.CLOCK_IN)) {
464 			return "Clocked in since: " + dateTime;
465 		} else if (StringUtils.equals(cl.getClockAction(),
466 				TkConstants.LUNCH_OUT)) {
467 			return "At Lunch since: " + dateTime;
468 		} else if (StringUtils
469 				.equals(cl.getClockAction(), TkConstants.LUNCH_IN)) {
470 			return "Returned from Lunch : " + dateTime;
471 		} else if (StringUtils.equals(cl.getClockAction(),
472 				TkConstants.CLOCK_OUT)) {
473 			return "Clocked out since: " + dateTime;
474 		} else {
475 			return "No previous clock information";
476 		}
477 
478 	}
479 
480 	public List<Map<String, Map<String, BigDecimal>>> getHoursByDayAssignmentBuckets(
481 			TkTimeBlockAggregate aggregate,
482 			List<Assignment> approverAssignments, List<String> payCalendarLabels) {
483 		Map<String, Assignment> mappedAssignments = mapAssignmentsByAssignmentKey(approverAssignments);
484 		List<List<TimeBlock>> blocksByDay = aggregate.getDayTimeBlockList();
485 
486 		// (assignment_key, <List of Hours Summed by Day>)
487 		Map<String, List<BigDecimal>> approverHours = new HashMap<String, List<BigDecimal>>();
488 		Map<String, List<BigDecimal>> otherHours = new HashMap<String, List<BigDecimal>>();
489 		for (int day = 0; day < blocksByDay.size(); day++) {
490 			List<TimeBlock> dayBlocks = blocksByDay.get(day);
491 			for (TimeBlock block : dayBlocks) {
492 				List<BigDecimal> hours;
493 				// Approver vs. Other :: Set our day-hour-list object
494 				if (mappedAssignments.containsKey(block.getAssignmentKey())) {
495 					hours = approverHours.get(block.getAssignmentKey());
496 					if (hours == null) {
497 						hours = new ArrayList<BigDecimal>();
498 						approverHours.put(block.getAssignmentKey(), hours);
499 					}
500 				} else {
501 					hours = otherHours.get(block.getAssignmentKey());
502 					if (hours == null) {
503 						hours = new ArrayList<BigDecimal>();
504 						otherHours.put(block.getAssignmentKey(), hours);
505 					}
506 				}
507 
508 				// Fill in zeroes for days with 0 hours / no time blocks
509 				for (int fill = hours.size(); fill <= day; fill++) {
510 					hours.add(TkConstants.BIG_DECIMAL_SCALED_ZERO);
511 				}
512 
513 				// Add time from time block to existing time.
514 				BigDecimal timeToAdd = hours.get(day);
515 				timeToAdd = timeToAdd.add(block.getHours(),
516 						TkConstants.MATH_CONTEXT);
517 				hours.set(day, timeToAdd);
518 			}
519 		}
520 
521 		// Compute Weekly / Period Summary Totals for each Assignment.
522 		// assignment row, each row has a map of pay calendar label -> big
523 		// decimal totals.
524 		Map<String, Map<String, BigDecimal>> approverAssignToPayHourTotals = new HashMap<String, Map<String, BigDecimal>>();
525 		Map<String, Map<String, BigDecimal>> otherAssignToPayHourTotals = new HashMap<String, Map<String, BigDecimal>>();
526 
527 		// Pass by Reference
528 		generateSummaries(approverAssignToPayHourTotals, approverHours,
529 				payCalendarLabels);
530 		generateSummaries(otherAssignToPayHourTotals, otherHours,
531 				payCalendarLabels);
532 
533 		// Add to our return list, "virtual" tuple.
534 		List<Map<String, Map<String, BigDecimal>>> returnTuple = new ArrayList<Map<String, Map<String, BigDecimal>>>(
535 				2);
536 		returnTuple.add(approverAssignToPayHourTotals);
537 		returnTuple.add(otherAssignToPayHourTotals);
538 
539 		return returnTuple;
540 	}
541 
542 	// Helper method for above method.
543 	private void generateSummaries(
544 			Map<String, Map<String, BigDecimal>> payHourTotals,
545 			Map<String, List<BigDecimal>> assignmentToHours,
546 			List<String> payCalendarLabels) {
547 		for (Entry<String, List<BigDecimal>> entry : assignmentToHours.entrySet()) {
548 			// for every Assignment
549 			Map<String, BigDecimal> hoursToPayLabelMap = new LinkedHashMap<String, BigDecimal>();
550 			List<BigDecimal> dayTotals = entry.getValue();
551 			int dayCount = 0;
552 			BigDecimal weekTotal = new BigDecimal(0.00);
553 			BigDecimal periodTotal = new BigDecimal(0.00);
554 			for (String payCalendarLabel : payCalendarLabels) {
555 				if (StringUtils.contains(payCalendarLabel, "Week")) {
556 					hoursToPayLabelMap.put(payCalendarLabel, weekTotal);
557 					weekTotal = new BigDecimal(0.00);
558 				} else if (StringUtils
559 						.contains(payCalendarLabel, "Total Hours")) {
560 					hoursToPayLabelMap.put(payCalendarLabel, periodTotal);
561 				} else {
562 					BigDecimal dayTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO;
563 					if (dayCount < dayTotals.size())
564 						dayTotal = dayTotals.get(dayCount);
565 
566 					hoursToPayLabelMap.put(payCalendarLabel, dayTotal);
567 					weekTotal = weekTotal.add(dayTotal,
568 							TkConstants.MATH_CONTEXT);
569 					periodTotal = periodTotal.add(dayTotal);
570 					dayCount++;
571 				}
572 			}
573 			payHourTotals.put(entry.getKey(), hoursToPayLabelMap);
574 		}
575 	}
576 
577 	private Map<String, Assignment> mapAssignmentsByAssignmentKey(
578 			List<Assignment> assignments) {
579 		Map<String, Assignment> assignmentMap = new HashMap<String, Assignment>();
580 		for (Assignment assignment : assignments) {
581 			assignmentMap
582 					.put(AssignmentDescriptionKey
583 							.getAssignmentKeyString(assignment), assignment);
584 		}
585 		return assignmentMap;
586 	}
587 
588 	/**
589 	 * Aggregate TimeBlocks to hours per day and sum for week
590 	 */
591 	@Override
592 	public Map<String, BigDecimal> getHoursToPayDayMap(String principalId,
593 			Date payEndDate, List<String> payCalendarLabels,
594 			List<TimeBlock> lstTimeBlocks, List<LeaveBlock> leaveBlocks, Long workArea,
595 			CalendarEntries payCalendarEntries, Calendar payCalendar,
596 			DateTimeZone dateTimeZone, List<Interval> dayIntervals) {
597 
598 		Map<String, BigDecimal> hoursToPayLabelMap = new LinkedHashMap<String, BigDecimal>();
599 		List<BigDecimal> dayTotals = new ArrayList<BigDecimal>();
600 
601         TkTimeBlockAggregate tkTimeBlockAggregate = buildAndMergeAggregates(lstTimeBlocks, leaveBlocks, payCalendarEntries, payCalendar, dayIntervals);
602 
603 		List<FlsaWeek> flsaWeeks = tkTimeBlockAggregate
604 				.getFlsaWeeks(dateTimeZone);
605 		for (FlsaWeek week : flsaWeeks) {
606 			for (FlsaDay day : week.getFlsaDays()) {
607 				BigDecimal total = new BigDecimal(0.00);
608 				for (TimeBlock tb : day.getAppliedTimeBlocks()) {
609                     for (TimeHourDetail thd : tb.getTimeHourDetails()) {
610                         if (workArea != null) {
611                             if (tb.getWorkArea().compareTo(workArea) == 0) {
612                                 total = total.add(thd.getHours(),
613                                         TkConstants.MATH_CONTEXT);
614                             } else {
615                                 total = total.add(new BigDecimal("0"),
616                                         TkConstants.MATH_CONTEXT);
617                             }
618                         } else {
619                             total = total.add(thd.getHours(),
620                                     TkConstants.MATH_CONTEXT);
621                         }
622                     }
623 				}
624 				dayTotals.add(total);
625 			}
626 		}
627 
628 		int dayCount = 0;
629 		BigDecimal weekTotal = new BigDecimal(0.00);
630 		BigDecimal periodTotal = new BigDecimal(0.00);
631 		for (String payCalendarLabel : payCalendarLabels) {
632 			if (StringUtils.contains(payCalendarLabel, "Week")) {
633 				hoursToPayLabelMap.put(payCalendarLabel, weekTotal);
634 				weekTotal = new BigDecimal(0.00);
635 			} else if (StringUtils.contains(payCalendarLabel, "Period Total")) {
636 				hoursToPayLabelMap.put(payCalendarLabel, periodTotal);
637 			} else {
638 				if(dayCount < dayTotals.size()) {
639 					hoursToPayLabelMap.put(payCalendarLabel,
640 							dayTotals.get(dayCount));
641 					weekTotal = weekTotal.add(dayTotals.get(dayCount),
642 							TkConstants.MATH_CONTEXT);
643 					periodTotal = periodTotal.add(dayTotals.get(dayCount));
644 					dayCount++;
645 				}
646 
647 			}
648 
649 		}
650 		return hoursToPayLabelMap;
651 	}
652 
653     private TkTimeBlockAggregate buildAndMergeAggregates(List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks,
654                                                          CalendarEntries calendarEntries, Calendar calendar, List<Interval> dayIntervals) {
655         TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timeBlocks, calendarEntries, calendar, true, dayIntervals);
656         LeaveBlockAggregate leaveBlockAggregate = new LeaveBlockAggregate(leaveBlocks, calendarEntries);
657         return TkTimeBlockAggregate.combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
658     }
659 	
660 	/**
661 	 * Aggregate TimeBlocks to hours per day and sum for flsa week (including previous/next weeks)
662 	 */
663 	@Override
664 	public Map<String, BigDecimal> getHoursToFlsaWeekMap(String principalId, 
665 			Date payEndDate, List<String> payCalendarLabels, 
666 			List<TimeBlock> lstTimeBlocks, List<LeaveBlock> leaveBlocks, Long workArea,
667 			CalendarEntries payCalendarEntries, Calendar payCalendar, 
668 			DateTimeZone dateTimeZone, List<Interval> dayIntervals) {
669 		
670 		Map<String, BigDecimal> hoursToFlsaWeekMap = new LinkedHashMap<String, BigDecimal>();
671 
672         TkTimeBlockAggregate tkTimeBlockAggregate = buildAndMergeAggregates(lstTimeBlocks, leaveBlocks, payCalendarEntries, payCalendar, dayIntervals);
673         List<List<FlsaWeek>> flsaWeeks = tkTimeBlockAggregate.getFlsaWeeks(dateTimeZone, principalId);
674 		
675 		int weekCount = 1;
676 		for (List<FlsaWeek> flsaWeekParts : flsaWeeks) {
677 			BigDecimal weekTotal = new BigDecimal(0.00);
678 			for (FlsaWeek flsaWeekPart : flsaWeekParts) {
679 				for (FlsaDay flsaDay : flsaWeekPart.getFlsaDays()) {
680 					for (TimeBlock timeBlock : flsaDay.getAppliedTimeBlocks()) {
681                         for (TimeHourDetail thd : timeBlock.getTimeHourDetails()) {
682                             if (workArea != null) {
683                                 if (timeBlock.getWorkArea().compareTo(workArea) == 0) {
684                                     weekTotal = weekTotal.add(thd.getHours(), TkConstants.MATH_CONTEXT);
685                                 } else {
686                                     weekTotal = weekTotal.add(new BigDecimal("0"), TkConstants.MATH_CONTEXT);
687                                 }
688                             } else {
689                                 weekTotal = weekTotal.add(thd.getHours(),TkConstants.MATH_CONTEXT);
690                             }
691                         }
692 					}
693 				}
694 			}
695 			hoursToFlsaWeekMap.put("Week " + weekCount++, weekTotal);
696 		}
697 		
698 		return hoursToFlsaWeekMap;
699 	}
700 
701 	public boolean doesApproverHavePrincipalsForCalendarGroup(Date asOfDate,
702 			String calGroup) {
703 		Set<Long> approverWorkAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas();
704 		for (Long workArea : approverWorkAreas) {
705 			List<Assignment> assignments = TkServiceLocator
706 					.getAssignmentService().getActiveAssignmentsForWorkArea(
707 							workArea, new java.sql.Date(asOfDate.getTime()));
708 			List<String> principalIds = new ArrayList<String>();
709 			for (Assignment assign : assignments) {
710 				if (principalIds.contains(assign.getPrincipalId())) {
711 					continue;
712 				}
713 				principalIds.add(assign.getPrincipalId());
714 			}
715 
716 			for (String principalId : principalIds) {
717 				PrincipalHRAttributes principalCal = TkServiceLocator
718 						.getPrincipalHRAttributeService().getPrincipalCalendar(
719 								principalId, asOfDate);
720 				if (StringUtils.equals(principalCal.getPayCalendar(),
721 						calGroup)) {
722 					return true;
723 				}
724 			}
725 		}
726 		return false;
727 	}
728 
729     @Override
730 	public List<Note> getNotesForDocument(String documentNumber) {
731         return KewApiServiceLocator.getNoteService().getNotes(documentNumber);
732 	}
733 
734     @Override
735     public List<String> getTimePrincipalIdsWithSearchCriteria(List<String> workAreaList, String calendarGroup, java.sql.Date effdt, java.sql.Date beginDate, java.sql.Date endDate) {
736     	if (CollectionUtils.isEmpty(workAreaList)) {
737     		return new ArrayList<String>();
738   	    }
739   		List<Assignment> assignmentList = TkServiceLocator.getAssignmentService().getAssignments(workAreaList, effdt, beginDate, endDate);
740   		List<Assignment> tempList = this.removeNoTimeAssignment(assignmentList);
741   		Set<String> pids = new HashSet<String>();
742         for(Assignment anAssignment : tempList) {
743         	if(anAssignment != null) {
744         		pids.add(anAssignment.getPrincipalId());
745          	}
746         }
747         List<String> ids = new ArrayList<String>();
748         ids.addAll(pids);
749   		
750   		if(CollectionUtils.isEmpty(ids)) {
751   			return new ArrayList<String>();
752   		}
753   		// use unique principalIds and selected calendarGroup to get unique ids from principalHRAttributes table
754   		List<String> idList = TkServiceLocator.getPrincipalHRAttributeService()
755   				.getActiveEmployeesIdForTimeCalendarAndIdList(calendarGroup, ids, endDate); 
756   		if(CollectionUtils.isEmpty(idList)) {
757   			return new ArrayList<String>();
758   		}
759   		return idList;
760     }
761     
762     private List<Assignment> removeNoTimeAssignment(List<Assignment> assignmentList) {
763     	List<Assignment> results = new ArrayList<Assignment>();
764 		if(CollectionUtils.isNotEmpty(assignmentList)) {
765 	     	for(Assignment anAssignment: assignmentList) {
766      			if(anAssignment != null 
767 		    			&& anAssignment.getJob() != null 
768 		    			&& anAssignment.getJob().getFlsaStatus() != null 
769 		    			&& anAssignment.getJob().getFlsaStatus().equalsIgnoreCase(TkConstants.FLSA_STATUS_NON_EXEMPT)) {
770      				results.add(anAssignment);	
771      			}
772 	        }
773 	    }
774 		return results;
775 	}
776     
777 	@Override
778 	public Map<String, TimesheetDocumentHeader> getPrincipalDocumentHeader(
779 			List<String> principalIds, Date payBeginDate, Date payEndDate) {
780 		Map<String, TimesheetDocumentHeader> principalDocumentHeader = new LinkedHashMap<String, TimesheetDocumentHeader>();
781 		for (String principalId : principalIds) {
782 			
783 			TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, DateUtils.addMilliseconds(payEndDate, 1));
784 			if(tdh != null) {
785 				principalDocumentHeader.put(principalId, tdh);	
786 			}
787 		}
788 		return principalDocumentHeader;
789 	}
790 
791 	@Override
792 	public Multimap<String, Long> getDeptWorkAreasByWorkAreas(
793 			Set<Long> approverWorkAreas) {
794 		Multimap<String, Long> deptWorkAreas = HashMultimap.create();
795 
796 		if (approverWorkAreas.size() > 0) {
797 			// prepare the OR statement for query
798 			StringBuilder workAreas = new StringBuilder();
799 			for (Long workarea : approverWorkAreas) {
800 				if(workarea != null) {
801 					workAreas.append("work_area = " + workarea + " or ");
802 				}
803 			}
804 			String workAreasForQuery = workAreas.substring(0,
805 					workAreas.length() - 3);
806 			String sql = "SELECT DISTINCT work_area, dept FROM tk_work_area_t "
807 					+ "WHERE " + workAreasForQuery + " AND effdt <= ?";
808 
809 			/**
810 			 * Multimap is an interface from Google's java common library -
811 			 * Guava. HashMultimap allows us to create a map with duplicate keys
812 			 * which will then generate a data structure, i.e. [key] => [value1,
813 			 * value2, value3...]
814 			 * 
815 			 * It save a good lines of code to do the same thing through the
816 			 * java map, e.g. Map<String, List<String>> map = new
817 			 * Hashmap<String, List<String>>();
818 			 * 
819 			 * See the java doc for more information:
820 			 * http://google-collections.googlecode
821 			 * .com/svn/trunk/javadoc/com/google/common/collect/Multimap.html
822 			 */
823 			SqlRowSet rs = TkServiceLocator.getTkJdbcTemplate().queryForRowSet(
824 					sql, new Object[] { TKUtils.getCurrentDate() },
825 					new int[] { Types.DATE });
826 			while (rs.next()) {
827 				deptWorkAreas
828 						.put(rs.getString("dept"), rs.getLong("work_area"));
829 			}
830 		}
831 		return deptWorkAreas;
832 	}
833 
834 	@Override
835 	public Multimap<String, Long> getDeptWorkAreasByDepts(Set<String> userDepts) {
836 		Multimap<String, Long> deptWorkAreas = HashMultimap.create();
837 
838 		if (userDepts.size() > 0) {
839 			// prepare the OR statement for query
840 			StringBuilder depts = new StringBuilder();
841 			for (String dept : userDepts) {
842 				depts.append("dept = '" + dept + "' or ");
843 			}
844 			String deptsForQuery = depts.substring(0, depts.length() - 4);
845 			String sql = "SELECT DISTINCT work_area, dept FROM tk_work_area_t "
846 					+ "WHERE " + deptsForQuery + " AND effdt <= ?";
847 
848 			SqlRowSet rs = TkServiceLocator.getTkJdbcTemplate().queryForRowSet(
849 					sql, new Object[] { TKUtils.getCurrentDate() },
850 					new int[] { Types.DATE });
851 			while (rs.next()) {
852 				deptWorkAreas
853 						.put(rs.getString("dept"), rs.getLong("work_area"));
854 			}
855 		}
856 		return deptWorkAreas;
857 	}
858 
859 	public DocumentRouteHeaderValue getRouteHeader(String documentId) {
860 		return KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
861 	}
862 	
863 	@Override
864 	public List<CalendarEntries> getAllPayCalendarEntriesForApprover(String principalId, Date currentDate) {
865 		Set<String> principals = new HashSet<String>();
866         UserRoles userRoles = TkUserRoles.getUserRoles(principalId);
867 		Set<Long> approverWorkAreas = userRoles.getApproverWorkAreas();
868         approverWorkAreas.addAll(userRoles.getReviewerWorkAreas());
869 
870 		// Get all of the principals within our window of time.
871 		for (Long waNum : approverWorkAreas) {
872 			List<Assignment> assignments = TkServiceLocator
873 					.getAssignmentService().getActiveAssignmentsForWorkArea(waNum, TKUtils.getTimelessDate(currentDate));
874 
875 			if (assignments != null) {
876 				for (Assignment assignment : assignments) {
877 					principals.add(assignment.getPrincipalId());
878 				}
879 			}
880 		}
881 		List<TimesheetDocumentHeader> documentHeaders = new ArrayList<TimesheetDocumentHeader>();
882 		for(String pid : principals) {
883 			documentHeaders.addAll(TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeadersForPrincipalId(pid));
884 		}
885 		Set<CalendarEntries> payPeriodSet = new HashSet<CalendarEntries>();
886 		for(TimesheetDocumentHeader tdh : documentHeaders) {
887             CalendarEntries pe = TkServiceLocator.getTimesheetService().getTimesheetDocument(tdh.getDocumentId()).getCalendarEntry();
888     		if(pe != null) {
889     			payPeriodSet.add(pe);
890     		}
891         }
892 		List<CalendarEntries> ppList = new ArrayList<CalendarEntries>(payPeriodSet);
893         
894 		return ppList;
895 	}
896 	
897 }