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 }