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