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