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