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