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