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