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