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.lm.leave.approval.service;
017
018 import java.math.BigDecimal;
019 import java.text.SimpleDateFormat;
020 import java.util.ArrayList;
021 import java.util.Date;
022 import java.util.HashMap;
023 import java.util.HashSet;
024 import java.util.LinkedHashMap;
025 import java.util.LinkedList;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.Map.Entry;
029 import java.util.Set;
030
031 import org.apache.commons.collections.CollectionUtils;
032 import org.apache.commons.lang3.StringUtils;
033 import org.apache.commons.lang3.time.DateUtils;
034 import org.joda.time.DateTime;
035 import org.kuali.hr.lm.LMConstants;
036 import org.kuali.hr.lm.accrual.AccrualCategory;
037 import org.kuali.hr.lm.accrual.AccrualCategoryRule;
038 import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
039 import org.kuali.hr.lm.leaveSummary.LeaveSummary;
040 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
041 import org.kuali.hr.lm.leaveblock.LeaveBlock;
042 import org.kuali.hr.lm.leavecalendar.validation.LeaveCalendarValidationUtil;
043 import org.kuali.hr.lm.leavepayout.LeavePayout;
044 import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
045 import org.kuali.hr.time.approval.web.ApprovalLeaveSummaryRow;
046 import org.kuali.hr.time.assignment.Assignment;
047 import org.kuali.hr.time.calendar.CalendarEntries;
048 import org.kuali.hr.time.person.TKPerson;
049 import org.kuali.hr.time.principal.PrincipalHRAttributes;
050 import org.kuali.hr.time.principal.dao.PrincipalHRAttributesDao;
051 import org.kuali.hr.time.roles.TkUserRoles;
052 import org.kuali.hr.time.service.base.TkServiceLocator;
053 import org.kuali.hr.time.util.TKContext;
054 import org.kuali.hr.time.util.TKUser;
055 import org.kuali.hr.time.util.TKUtils;
056 import org.kuali.hr.time.util.TkConstants;
057 import org.kuali.rice.kew.api.KewApiServiceLocator;
058 import org.kuali.rice.kew.api.note.Note;
059 import org.kuali.rice.krad.util.GlobalVariables;
060 import org.springframework.jdbc.support.rowset.SqlRowSet;
061
062 public class LeaveApprovalServiceImpl implements LeaveApprovalService{
063 public static final int DAYS_WINDOW_DELTA = 31;
064 private PrincipalHRAttributesDao principalHRAttributesDao;
065
066 public void setPrincipalHRAttributesDao(PrincipalHRAttributesDao principalHRAttributesDao) {
067 this.principalHRAttributesDao = principalHRAttributesDao;
068 }
069
070 @Override
071 public List<ApprovalLeaveSummaryRow> getLeaveApprovalSummaryRows(List<TKPerson> persons, CalendarEntries payCalendarEntries, List<Date> leaveSummaryDates) {
072 Date payBeginDate = payCalendarEntries.getBeginPeriodDate();
073 Date payEndDate = payCalendarEntries.getEndPeriodDate();
074 List<ApprovalLeaveSummaryRow> rowList = new ArrayList<ApprovalLeaveSummaryRow>();
075
076 for(TKPerson aPerson : persons) {
077 String principalId = aPerson.getPrincipalId();
078 ApprovalLeaveSummaryRow aRow = new ApprovalLeaveSummaryRow();
079 List<Note> notes = new ArrayList<Note>();
080 // List<String> warnings = new ArrayList<String>();
081 Map<String, Set<String>> allMessages = new HashMap<String, Set<String>>();
082 aRow.setName(aPerson.getPrincipalName());
083 aRow.setPrincipalId(aPerson.getPrincipalId());
084
085 String lastApprovedString = "No previous approved leave calendar information";
086 LeaveCalendarDocumentHeader lastApprovedDoc = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getMaxEndDateApprovedLeaveCalendar(principalId);
087 if(lastApprovedDoc != null) {
088 lastApprovedString = "Last Approved: " + (new SimpleDateFormat("MMM yyyy")).format(lastApprovedDoc.getBeginDate());
089 }
090 aRow.setLastApproveMessage(lastApprovedString);
091
092 LeaveCalendarDocumentHeader aDoc = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, payEndDate);
093 if(aDoc != null) {
094 aRow.setDocumentId(aDoc.getDocumentId());
095 aRow.setApprovalStatus(TkConstants.DOC_ROUTE_STATUS.get(aDoc.getDocumentStatus()));
096 notes = getNotesForDocument(aDoc.getDocumentId());
097 }
098 List<LeaveCalendarDocumentHeader> docList = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getApprovalDelinquentDocumentHeaders(principalId);
099 if(docList.size() > LMConstants.DELINQUENT_LEAVE_CALENDARS_LIMIT ) {
100 aRow.setMoreThanOneCalendar(true);
101 }
102
103 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, payBeginDate, payEndDate);
104
105 aRow.setLeaveBlockList(leaveBlocks);
106 Map<Date, Map<String, BigDecimal>> earnCodeLeaveHours = getEarnCodeLeaveHours(leaveBlocks, leaveSummaryDates);
107 aRow.setEarnCodeLeaveHours(earnCodeLeaveHours);
108 aRow.setNotes(notes);
109
110 allMessages = findWarnings(aDoc, payCalendarEntries, leaveBlocks);
111
112 Map<String,Set<String>> transactionalMessages = findTransactionsWithinPeriod(aDoc, payCalendarEntries);
113
114 allMessages.get("infoMessages").addAll(transactionalMessages.get("infoMessages"));
115 allMessages.get("warningMessages").addAll(transactionalMessages.get("warningMessages"));
116 allMessages.get("actionMessages").addAll(transactionalMessages.get("actionMessages"));
117
118 List<String> warningMessages = new ArrayList<String>();
119 warningMessages.addAll(allMessages.get("warningMessages"));
120 warningMessages.addAll(allMessages.get("infoMessages"));
121 warningMessages.addAll(allMessages.get("actionMessages"));
122
123 aRow.setWarnings(warningMessages); //these are only warning messages.
124
125 rowList.add(aRow);
126 }
127
128 return rowList;
129 }
130
131 private Map<String,Set<String>> findTransactionsWithinPeriod(LeaveCalendarDocumentHeader aDoc,
132 CalendarEntries payCalendarEntries) {
133 Map<String,Set<String>> allMessages = new HashMap<String,Set<String>>();
134
135 allMessages.put("actionMessages", new HashSet<String>());
136 allMessages.put("infoMessages", new HashSet<String>());
137 allMessages.put("warningMessages", new HashSet<String>());
138 if(aDoc != null) {
139 allMessages = LeaveCalendarValidationUtil.validatePendingTransactions(aDoc.getPrincipalId(), payCalendarEntries.getBeginPeriodDate(), payCalendarEntries.getEndPeriodDate());
140 }
141 return allMessages;
142 }
143
144 private Map<String, Set<String>> findWarnings(LeaveCalendarDocumentHeader doc, CalendarEntries calendarEntry, List<LeaveBlock> leaveBlocks) {
145 // List<String> warnings = LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(leaveBlocks);
146 Map<String, Set<String>> allMessages= LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(leaveBlocks);
147 //get LeaveSummary and check for warnings
148 if (doc != null) {
149 Map<String, ArrayList<String>> eligibilities;
150 try {
151 eligibilities = TkServiceLocator.getBalanceTransferService().getEligibleTransfers(calendarEntry, doc.getPrincipalId());
152 } catch (Exception e) {
153 eligibilities = null;
154 }
155 if (eligibilities != null) {
156 for (Entry<String,ArrayList<String>> entry : eligibilities.entrySet()) {
157 for(String accrualRuleId : entry.getValue()) {
158 AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
159 if (rule != null) {
160 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(rule.getLmAccrualCategoryId());
161 if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.TRANSFER)) {
162 //Todo: add link to balance transfer
163 allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance."); //warningMessages
164 } else if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
165 //Todo: compute and display amount of time lost.
166 allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance."); //warningMessages
167 }
168 //will never contain PAYOUT action transfers.
169 }
170 }
171 }
172 }
173 Map<String, ArrayList<String>> payoutEligible;
174 try {
175 payoutEligible = TkServiceLocator.getLeavePayoutService().getEligiblePayouts(calendarEntry, doc.getPrincipalId());
176 } catch (Exception e) {
177 payoutEligible = null;
178 }
179 if (payoutEligible != null) {
180 for (Entry<String,ArrayList<String>> entry : payoutEligible.entrySet()) {
181 for(String accrualRuleId : entry.getValue()) {
182 AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
183 if (rule != null) {
184 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(rule.getLmAccrualCategoryId());
185 allMessages.get("warningMessages").add("Accrual category '" + accrualCategory.getAccrualCategory() + "' is over max balance.");
186 }
187 // should never contain LOSE or TRANSFER max balance actions.
188 }
189 }
190 }
191 } return allMessages;
192 }
193
194 @Override
195 public Map<Date, Map<String, BigDecimal>> getEarnCodeLeaveHours(List<LeaveBlock> leaveBlocks, List<Date> leaveSummaryDates) {
196 Map<Date, Map<String, BigDecimal>> earnCodeLeaveHours = new LinkedHashMap<Date, Map<String, BigDecimal>>();
197
198 for (Date leaveSummaryDate : leaveSummaryDates) {
199 earnCodeLeaveHours.put(leaveSummaryDate, new LinkedHashMap<String, BigDecimal>());
200 }
201
202 for (LeaveBlock lb : leaveBlocks) {
203 DateTime leaveDate = new DateTime(lb.getLeaveDate()).toLocalDate().toDateTimeAtStartOfDay();
204
205 if (earnCodeLeaveHours.get(leaveDate.toDate()) != null) {
206 Map<String, BigDecimal> leaveHours = earnCodeLeaveHours.get(leaveDate.toDate());
207
208 BigDecimal amount = lb.getLeaveAmount();
209 String key = lb.getEarnCode() + "|" + lb.getRequestStatus();
210 if (leaveHours.get(lb.getEarnCode()) != null) {
211 amount = leaveHours.get(key).add(lb.getLeaveAmount());
212 }
213
214 leaveHours.put(key, amount);
215 }
216 }
217
218 return earnCodeLeaveHours;
219 }
220
221 public Map<String, LeaveCalendarDocumentHeader> getLeaveDocumentHeaderMap(List<TKPerson> persons, Date payBeginDate, Date payEndDate) {
222 Map<String, LeaveCalendarDocumentHeader> leaveDocumentHeaderMap = new LinkedHashMap<String, LeaveCalendarDocumentHeader>();
223 if (CollectionUtils.isNotEmpty(persons)) {
224 for (TKPerson person : persons) {
225 String principalId = person.getPrincipalId();
226 LeaveCalendarDocumentHeader aHeader = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, payEndDate);
227 if(aHeader != null) {
228 leaveDocumentHeaderMap.put(principalId, aHeader);
229 }
230
231 }
232 }
233 return leaveDocumentHeaderMap;
234 }
235
236 @Override
237 public List<Map<String, Object>> getLeaveApprovalDetailSections(LeaveCalendarDocumentHeader lcdh) {
238
239 List<Map<String, Object>> acRows = new ArrayList<Map<String, Object>>();
240
241 String principalId = lcdh.getPrincipalId();
242 CalendarEntries calendarEntry = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(lcdh.getBeginDate(), lcdh.getEndDate());
243 if(calendarEntry != null) {
244 Date beginDate = calendarEntry.getBeginPeriodDate();
245 Date endDate = calendarEntry.getEndPeriodDate();
246 LeaveSummary leaveSummary;
247 List<Date> leaveSummaryDates = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryDates(calendarEntry);
248 try {
249 leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId, calendarEntry);
250 } catch (Exception e) {
251 leaveSummary = null;
252 }
253 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
254 Map<Date, Map<String, BigDecimal>> accrualCategoryLeaveHours = getAccrualCategoryLeaveHours(leaveBlocks, leaveSummaryDates);
255
256 //get all accrual categories of this employee
257 PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, endDate);
258 if(pha != null) {
259 List<AccrualCategory> acList = TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), new java.sql.Date(endDate.getTime()));
260 for(AccrualCategory ac : acList) {
261 List<BigDecimal> acDayDetails = new ArrayList<BigDecimal>();
262 Map<String, Object> displayMap = new HashMap<String, Object>();
263 BigDecimal totalAmount = BigDecimal.ZERO;
264 displayMap.put("accrualCategory", ac.getAccrualCategory());
265 int index = 0;
266 for (Date leaveSummaryDate : leaveSummaryDates) {
267 acDayDetails.add(index, null);
268 if (accrualCategoryLeaveHours.get(leaveSummaryDate) != null) {
269 Map<String, BigDecimal> leaveHours = accrualCategoryLeaveHours.get(leaveSummaryDate);
270 if (leaveHours.containsKey(ac.getAccrualCategory())) {
271 BigDecimal amount = leaveHours.get(ac.getAccrualCategory());
272 totalAmount = totalAmount.add(amount);
273 acDayDetails.set(index, amount);
274 }
275 }
276 index++;
277 }
278 LeaveSummaryRow lsr = leaveSummary == null ? null : leaveSummary.getLeaveSummaryRowForAccrualCtgy(ac.getAccrualCategory());
279 displayMap.put("periodUsage", totalAmount);
280 displayMap.put("availableBalance", BigDecimal.ZERO);
281 displayMap.put("availableBalance", lsr == null ? BigDecimal.ZERO : lsr.getLeaveBalance());
282 displayMap.put("daysDetail", acDayDetails);
283 displayMap.put("daysSize", acDayDetails.size());
284 acRows.add(displayMap);
285 }
286 }
287
288 }
289 return acRows;
290 }
291
292 @Override
293 public List<Note> getNotesForDocument(String documentNumber) {
294 return KewApiServiceLocator.getNoteService().getNotes(documentNumber);
295 }
296
297 @Override
298 public Map<Date, Map<String, BigDecimal>> getAccrualCategoryLeaveHours(List<LeaveBlock> leaveBlocks, List<Date> leaveSummaryDates) {
299 Map<Date, Map<String, BigDecimal>> accrualCategoryLeaveHours = new LinkedHashMap<Date, Map<String, BigDecimal>>();
300
301 for (Date leaveSummaryDate : leaveSummaryDates) {
302 accrualCategoryLeaveHours.put(leaveSummaryDate, new LinkedHashMap<String, BigDecimal>());
303 }
304
305 for (LeaveBlock lb : leaveBlocks) {
306 DateTime leaveDate = new DateTime(lb.getLeaveDate()).toLocalDate().toDateTimeAtStartOfDay();
307
308 AccrualCategory ac = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(lb.getAccrualCategory(), lb.getLeaveDate());
309 if (ac != null && ac.getShowOnGrid().equals("Y")) {
310 if (accrualCategoryLeaveHours.get(leaveDate.toDate()) != null) {
311 Map<String, BigDecimal> leaveHours = accrualCategoryLeaveHours.get(leaveDate.toDate());
312
313 BigDecimal amount = lb.getLeaveAmount();
314 if (leaveHours.get(ac.getAccrualCategory()) != null) {
315 amount = leaveHours.get(ac.getAccrualCategory()).add(lb.getLeaveAmount());
316 }
317
318 leaveHours.put(ac.getAccrualCategory(), amount);
319 }
320 }
321 }
322
323 return accrualCategoryLeaveHours;
324 }
325
326 @Override
327 public List<String> getUniqueLeavePayGroupsForPrincipalIds(List<String> principalIds) {
328 return principalHRAttributesDao.getUniqueLeavePayGroupsForPrincipalIds(principalIds);
329 }
330
331 @Override
332 public List<CalendarEntries> getAllLeavePayCalendarEntriesForApprover(String principalId, Date currentDate) {
333 TKUser tkUser = TKContext.getUser();
334 Set<String> principals = new HashSet<String>();
335 DateTime minDt = new DateTime(currentDate,
336 TKUtils.getSystemDateTimeZone());
337 minDt = minDt.minusDays(DAYS_WINDOW_DELTA);
338 Set<Long> approverWorkAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas();
339
340 // Get all of the principals within our window of time.
341 for (Long waNum : approverWorkAreas) {
342 List<Assignment> assignments = TkServiceLocator
343 .getAssignmentService().getActiveAssignmentsForWorkArea(waNum, TKUtils.getTimelessDate(currentDate));
344
345 if (assignments != null) {
346 for (Assignment assignment : assignments) {
347 principals.add(assignment.getPrincipalId());
348 }
349 }
350 }
351 List<LeaveCalendarDocumentHeader> documentHeaders = new ArrayList<LeaveCalendarDocumentHeader>();
352 for(String pid : principals) {
353 documentHeaders.addAll(TkServiceLocator.getLeaveCalendarDocumentHeaderService().getAllDocumentHeadersForPricipalId(pid));
354 }
355 Set<CalendarEntries> payPeriodSet = new HashSet<CalendarEntries>();
356 for(LeaveCalendarDocumentHeader lcdh : documentHeaders) {
357 CalendarEntries pe = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(lcdh.getBeginDate(), lcdh.getEndDate());
358 if(pe != null) {
359 payPeriodSet.add(pe);
360 }
361 }
362 List<CalendarEntries> ppList = new ArrayList<CalendarEntries>(payPeriodSet);
363
364 return ppList;
365 }
366 @Override
367 public void removeNonLeaveEmployees(List<String> principalIds) {
368 if(CollectionUtils.isNotEmpty(principalIds)) {
369 java.sql.Date asOfDate = TKUtils.getTimelessDate(null);
370 List<String> idList = new ArrayList<String>();
371 idList.addAll(principalIds);
372 for(String principalId: idList) {
373 boolean leaveFlag = false;
374 List<Assignment> activeAssignments = TkServiceLocator.getAssignmentService().getAssignments(principalId, asOfDate);
375 if(CollectionUtils.isNotEmpty(activeAssignments)) {
376 for(Assignment assignment : activeAssignments) {
377 if(assignment != null && assignment.getJob() != null && assignment.getJob().isEligibleForLeave()) {
378 leaveFlag = true;
379 break;
380 }
381 }
382 if(!leaveFlag) { // employee is not eligible for leave, remove the id from principalIds
383 principalIds.remove(principalId);
384 }
385 }
386 }
387 }
388 }
389
390 @Override
391 public List<String> getLeavePrincipalIdsWithSearchCriteria(List<String> workAreaList, String calendarGroup, java.sql.Date effdt, java.sql.Date beginDate, java.sql.Date endDate) {
392 if (CollectionUtils.isEmpty(workAreaList)) {
393 return new ArrayList<String>();
394 }
395
396 List<String> principalIds = TkServiceLocator.getAssignmentService().getPrincipalIds(workAreaList, effdt, beginDate, endDate);
397 TkServiceLocator.getLeaveApprovalService().removeNonLeaveEmployees(principalIds);
398
399 if(CollectionUtils.isEmpty(principalIds)) {
400 return new ArrayList<String>();
401 }
402 // use unique principalIds and selected calendarGroup to get unique ids from principalHRAttributes table
403 List<String> idList = CollectionUtils.isEmpty(principalIds) ?
404 new ArrayList<String> ()
405 : TkServiceLocator.getPrincipalHRAttributeService()
406 .getActiveEmployeesIdForLeaveCalendarAndIdList(calendarGroup, principalIds, endDate);
407
408 return idList;
409 }
410
411 @Override
412 public Map<String, LeaveCalendarDocumentHeader> getPrincipalDocumehtHeader(List<TKPerson> persons, Date payBeginDate, Date payEndDate) {
413 Map<String, LeaveCalendarDocumentHeader> principalDocumentHeader = new LinkedHashMap<String, LeaveCalendarDocumentHeader>();
414 for (TKPerson person : persons) {
415 String principalId = person.getPrincipalId();
416 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, DateUtils.addMilliseconds(payEndDate, 1));
417 if(lcdh != null) {
418 principalDocumentHeader.put(principalId, lcdh);
419 }
420 }
421 return principalDocumentHeader;
422 }
423
424 @Override
425 public boolean isActiveAssignmentFoundOnJobFlsaStatus(String principalId,
426 String flsaStatus, boolean chkForLeaveEligible) {
427 boolean isActiveAssFound = false;
428 java.sql.Date asOfDate = TKUtils.getTimelessDate(null);
429 List<Assignment> activeAssignments = TkServiceLocator
430 .getAssignmentService().getAssignments(principalId, asOfDate);
431 if (activeAssignments != null && !activeAssignments.isEmpty()) {
432 for (Assignment assignment : activeAssignments) {
433 if (assignment != null
434 && assignment.getJob() != null
435 && assignment.getJob().getFlsaStatus() != null
436 && assignment.getJob().getFlsaStatus()
437 .equalsIgnoreCase(flsaStatus)) {
438 if (chkForLeaveEligible) {
439 isActiveAssFound = assignment.getJob()
440 .isEligibleForLeave();
441 if (!isActiveAssFound) {
442 continue;
443 }
444 }
445 isActiveAssFound = true;
446 break;
447 }
448 }
449 }
450 return isActiveAssFound;
451 }
452 }