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