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 }