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 aRow.setName(aPerson.getPrincipalName());
082 aRow.setPrincipalId(aPerson.getPrincipalId());
083
084 String lastApprovedString = "No previous approved leave calendar information";
085 LeaveCalendarDocumentHeader lastApprovedDoc = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getMaxEndDateApprovedLeaveCalendar(principalId);
086 if(lastApprovedDoc != null) {
087 lastApprovedString = "Last Approved: " + (new SimpleDateFormat("MMM yyyy")).format(lastApprovedDoc.getBeginDate());
088 }
089 aRow.setLastApproveMessage(lastApprovedString);
090
091 LeaveCalendarDocumentHeader aDoc = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, payEndDate);
092 if(aDoc != null) {
093 aRow.setDocumentId(aDoc.getDocumentId());
094 aRow.setApprovalStatus(TkConstants.DOC_ROUTE_STATUS.get(aDoc.getDocumentStatus()));
095 notes = getNotesForDocument(aDoc.getDocumentId());
096 }
097 List<LeaveCalendarDocumentHeader> docList = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getApprovalDelinquentDocumentHeaders(principalId);
098 if(docList.size() > LMConstants.DELINQUENT_LEAVE_CALENDARS_LIMIT ) {
099 aRow.setMoreThanOneCalendar(true);
100 }
101
102 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, payBeginDate, payEndDate);
103
104 aRow.setLeaveBlockList(leaveBlocks);
105 Map<Date, Map<String, BigDecimal>> earnCodeLeaveHours = getEarnCodeLeaveHours(leaveBlocks, leaveSummaryDates);
106 aRow.setEarnCodeLeaveHours(earnCodeLeaveHours);
107 aRow.setNotes(notes);
108
109 Map<String, Set<String>> allMessages = findWarnings(principalId, payCalendarEntries, leaveBlocks);
110
111 Map<String,Set<String>> transactionalMessages = findTransactionsWithinPeriod(aDoc, payCalendarEntries);
112
113 allMessages.get("infoMessages").addAll(transactionalMessages.get("infoMessages"));
114 allMessages.get("warningMessages").addAll(transactionalMessages.get("warningMessages"));
115 allMessages.get("actionMessages").addAll(transactionalMessages.get("actionMessages"));
116
117 List<String> warningMessages = new ArrayList<String>();
118 warningMessages.addAll(allMessages.get("warningMessages"));
119 warningMessages.addAll(allMessages.get("infoMessages"));
120 warningMessages.addAll(allMessages.get("actionMessages"));
121
122 aRow.setWarnings(warningMessages); //these are only warning messages.
123
124 rowList.add(aRow);
125 }
126
127 return rowList;
128 }
129
130 private Map<String,Set<String>> findTransactionsWithinPeriod(LeaveCalendarDocumentHeader aDoc,
131 CalendarEntries payCalendarEntries) {
132 Map<String,Set<String>> allMessages = new HashMap<String,Set<String>>();
133
134 allMessages.put("actionMessages", new HashSet<String>());
135 allMessages.put("infoMessages", new HashSet<String>());
136 allMessages.put("warningMessages", new HashSet<String>());
137 if(aDoc != null) {
138 allMessages = LeaveCalendarValidationUtil.validatePendingTransactions(aDoc.getPrincipalId(), payCalendarEntries.getBeginPeriodDate(), payCalendarEntries.getEndPeriodDate());
139 }
140 return allMessages;
141 }
142
143 private Map<String, Set<String>> findWarnings(String principalId, CalendarEntries calendarEntry, List<LeaveBlock> leaveBlocks) {
144 // List<String> warnings = LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(leaveBlocks);
145 Map<String, Set<String>> allMessages= LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(leaveBlocks);
146 //get LeaveSummary and check for warnings
147 Map<String, Set<LeaveBlock>> eligibilities;
148 try {
149 eligibilities = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry, principalId);
150 } catch (Exception e) {
151 eligibilities = null;
152 }
153 if (eligibilities != null) {
154 for (Entry<String,Set<LeaveBlock>> entry : eligibilities.entrySet()) {
155 for(LeaveBlock block : entry.getValue()) {
156
157 AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(block.getAccrualCategoryRuleId());
158 if (rule != null) {
159 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(rule.getLmAccrualCategoryId());
160 if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.TRANSFER)) {
161 //Todo: add link to balance transfer
162 allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance."); //warningMessages
163 } else if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
164 //Todo: compute and display amount of time lost.
165 allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance."); //warningMessages
166 } else if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.PAYOUT)){
167 //Todo: display information about the payout
168 allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance."); //warningMessages
169
170 }
171
172 }
173 }
174 }
175 }
176 return allMessages;
177 }
178
179 @Override
180 public Map<Date, Map<String, BigDecimal>> getEarnCodeLeaveHours(List<LeaveBlock> leaveBlocks, List<Date> leaveSummaryDates) {
181 Map<Date, Map<String, BigDecimal>> earnCodeLeaveHours = new LinkedHashMap<Date, Map<String, BigDecimal>>();
182
183 for (Date leaveSummaryDate : leaveSummaryDates) {
184 earnCodeLeaveHours.put(leaveSummaryDate, new LinkedHashMap<String, BigDecimal>());
185 }
186
187 for (LeaveBlock lb : leaveBlocks) {
188 DateTime leaveDate = new DateTime(lb.getLeaveDate()).toLocalDate().toDateTimeAtStartOfDay();
189
190 if (earnCodeLeaveHours.get(leaveDate.toDate()) != null) {
191 Map<String, BigDecimal> leaveHours = earnCodeLeaveHours.get(leaveDate.toDate());
192
193 BigDecimal amount = lb.getLeaveAmount();
194 String key = lb.getEarnCode() + "|" + lb.getRequestStatus() + "|" + lb.getLeaveBlockType();
195 if (leaveHours.get(key) != null) {
196 amount = leaveHours.get(key).add(lb.getLeaveAmount());
197 }
198
199 leaveHours.put(key, amount);
200 }
201 }
202
203 return earnCodeLeaveHours;
204 }
205
206 public Map<String, LeaveCalendarDocumentHeader> getLeaveDocumentHeaderMap(List<TKPerson> persons, Date payBeginDate, Date payEndDate) {
207 Map<String, LeaveCalendarDocumentHeader> leaveDocumentHeaderMap = new LinkedHashMap<String, LeaveCalendarDocumentHeader>();
208 if (CollectionUtils.isNotEmpty(persons)) {
209 for (TKPerson person : persons) {
210 String principalId = person.getPrincipalId();
211 LeaveCalendarDocumentHeader aHeader = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, payEndDate);
212 if(aHeader != null) {
213 leaveDocumentHeaderMap.put(principalId, aHeader);
214 }
215
216 }
217 }
218 return leaveDocumentHeaderMap;
219 }
220
221 @Override
222 public List<Map<String, Object>> getLeaveApprovalDetailSections(LeaveCalendarDocumentHeader lcdh) {
223
224 List<Map<String, Object>> acRows = new ArrayList<Map<String, Object>>();
225
226 String principalId = lcdh.getPrincipalId();
227 CalendarEntries calendarEntry = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(lcdh.getDocumentId()).getCalendarEntry();
228 //CalendarEntries calendarEntry = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(lcdh.getBeginDate(), lcdh.getEndDate());
229 if(calendarEntry != null) {
230 Date beginDate = calendarEntry.getBeginPeriodDate();
231 Date endDate = calendarEntry.getEndPeriodDate();
232 LeaveSummary leaveSummary;
233 List<Date> leaveSummaryDates = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryDates(calendarEntry);
234 try {
235 leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId, calendarEntry);
236 } catch (Exception e) {
237 leaveSummary = null;
238 }
239 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
240 Map<Date, Map<String, BigDecimal>> accrualCategoryLeaveHours = getAccrualCategoryLeaveHours(leaveBlocks, leaveSummaryDates);
241
242 //get all accrual categories of this employee
243 PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, endDate);
244 if(pha != null) {
245 List<AccrualCategory> acList = TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), new java.sql.Date(endDate.getTime()));
246 for(AccrualCategory ac : acList) {
247 List<BigDecimal> acDayDetails = new ArrayList<BigDecimal>();
248 Map<String, Object> displayMap = new HashMap<String, Object>();
249 BigDecimal totalAmount = BigDecimal.ZERO;
250 displayMap.put("accrualCategory", ac.getAccrualCategory());
251 int index = 0;
252 for (Date leaveSummaryDate : leaveSummaryDates) {
253 acDayDetails.add(index, null);
254 if (accrualCategoryLeaveHours.get(leaveSummaryDate) != null) {
255 Map<String, BigDecimal> leaveHours = accrualCategoryLeaveHours.get(leaveSummaryDate);
256 if (leaveHours.containsKey(ac.getAccrualCategory())) {
257 BigDecimal amount = leaveHours.get(ac.getAccrualCategory());
258 totalAmount = totalAmount.add(amount);
259 acDayDetails.set(index, amount);
260 }
261 }
262 index++;
263 }
264 LeaveSummaryRow lsr = leaveSummary == null ? null : leaveSummary.getLeaveSummaryRowForAccrualCtgy(ac.getAccrualCategory());
265 displayMap.put("periodUsage", totalAmount);
266 displayMap.put("availableBalance", BigDecimal.ZERO);
267 displayMap.put("availableBalance", lsr == null ? BigDecimal.ZERO : lsr.getLeaveBalance());
268 displayMap.put("daysDetail", acDayDetails);
269 displayMap.put("daysSize", acDayDetails.size());
270 acRows.add(displayMap);
271 }
272 }
273
274 }
275 return acRows;
276 }
277
278 @Override
279 public List<Note> getNotesForDocument(String documentNumber) {
280 return KewApiServiceLocator.getNoteService().getNotes(documentNumber);
281 }
282
283 @Override
284 public Map<Date, Map<String, BigDecimal>> getAccrualCategoryLeaveHours(List<LeaveBlock> leaveBlocks, List<Date> leaveSummaryDates) {
285 Map<Date, Map<String, BigDecimal>> accrualCategoryLeaveHours = new LinkedHashMap<Date, Map<String, BigDecimal>>();
286
287 for (Date leaveSummaryDate : leaveSummaryDates) {
288 accrualCategoryLeaveHours.put(leaveSummaryDate, new LinkedHashMap<String, BigDecimal>());
289 }
290
291 for (LeaveBlock lb : leaveBlocks) {
292 DateTime leaveDate = new DateTime(lb.getLeaveDate()).toLocalDate().toDateTimeAtStartOfDay();
293
294 AccrualCategory ac = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(lb.getAccrualCategory(), lb.getLeaveDate());
295 if (ac != null && ac.getShowOnGrid().equals("Y")) {
296 if (accrualCategoryLeaveHours.get(leaveDate.toDate()) != null) {
297 Map<String, BigDecimal> leaveHours = accrualCategoryLeaveHours.get(leaveDate.toDate());
298
299 BigDecimal amount = lb.getLeaveAmount();
300 if (leaveHours.get(ac.getAccrualCategory()) != null) {
301 amount = leaveHours.get(ac.getAccrualCategory()).add(lb.getLeaveAmount());
302 }
303
304 leaveHours.put(ac.getAccrualCategory(), amount);
305 }
306 }
307 }
308
309 return accrualCategoryLeaveHours;
310 }
311
312 @Override
313 public List<String> getUniqueLeavePayGroupsForPrincipalIds(List<String> principalIds) {
314 return principalHRAttributesDao.getUniqueLeavePayGroupsForPrincipalIds(principalIds);
315 }
316
317 @Override
318 public List<CalendarEntries> getAllLeavePayCalendarEntriesForApprover(String principalId, Date currentDate) {
319 Set<String> principals = new HashSet<String>();
320 Set<Long> approverWorkAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas();
321
322 // Get all of the principals within our window of time.
323 for (Long waNum : approverWorkAreas) {
324 List<Assignment> assignments = TkServiceLocator
325 .getAssignmentService().getActiveAssignmentsForWorkArea(waNum, TKUtils.getTimelessDate(currentDate));
326
327 if (assignments != null) {
328 for (Assignment assignment : assignments) {
329 principals.add(assignment.getPrincipalId());
330 }
331 }
332 }
333 List<LeaveCalendarDocumentHeader> documentHeaders = new ArrayList<LeaveCalendarDocumentHeader>();
334 for(String pid : principals) {
335 documentHeaders.addAll(TkServiceLocator.getLeaveCalendarDocumentHeaderService().getAllDocumentHeadersForPricipalId(pid));
336 }
337 Set<CalendarEntries> payPeriodSet = new HashSet<CalendarEntries>();
338 for(LeaveCalendarDocumentHeader lcdh : documentHeaders) {
339 CalendarEntries pe = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(lcdh.getDocumentId()).getCalendarEntry();
340 if(pe != null) {
341 payPeriodSet.add(pe);
342 }
343 }
344 List<CalendarEntries> ppList = new ArrayList<CalendarEntries>(payPeriodSet);
345
346 return ppList;
347 }
348 @Override
349 public void removeNonLeaveEmployees(List<String> principalIds) {
350 if(CollectionUtils.isNotEmpty(principalIds)) {
351 java.sql.Date asOfDate = TKUtils.getTimelessDate(null);
352 List<String> idList = new ArrayList<String>();
353 idList.addAll(principalIds);
354 for(String principalId: idList) {
355 boolean leaveFlag = false;
356 List<Assignment> activeAssignments = TkServiceLocator.getAssignmentService().getAssignments(principalId, asOfDate);
357 if(CollectionUtils.isNotEmpty(activeAssignments)) {
358 for(Assignment assignment : activeAssignments) {
359 if(assignment != null && assignment.getJob() != null && assignment.getJob().isEligibleForLeave()) {
360 leaveFlag = true;
361 break;
362 }
363 }
364 if(!leaveFlag) { // employee is not eligible for leave, remove the id from principalIds
365 principalIds.remove(principalId);
366 }
367 }
368 }
369 }
370 }
371
372 @Override
373 public List<String> getLeavePrincipalIdsWithSearchCriteria(List<String> workAreaList, String calendarGroup, java.sql.Date effdt, java.sql.Date beginDate, java.sql.Date endDate) {
374 if (CollectionUtils.isEmpty(workAreaList)) {
375 return new ArrayList<String>();
376 }
377
378 List<String> principalIds = TkServiceLocator.getAssignmentService().getPrincipalIds(workAreaList, effdt, beginDate, endDate);
379 TkServiceLocator.getLeaveApprovalService().removeNonLeaveEmployees(principalIds);
380
381 if(CollectionUtils.isEmpty(principalIds)) {
382 return new ArrayList<String>();
383 }
384 // use unique principalIds and selected calendarGroup to get unique ids from principalHRAttributes table
385 List<String> idList = CollectionUtils.isEmpty(principalIds) ?
386 new ArrayList<String> ()
387 : TkServiceLocator.getPrincipalHRAttributeService()
388 .getActiveEmployeesIdForLeaveCalendarAndIdList(calendarGroup, principalIds, endDate);
389
390 return idList;
391 }
392
393 @Override
394 public Map<String, LeaveCalendarDocumentHeader> getPrincipalDocumehtHeader(List<TKPerson> persons, Date payBeginDate, Date payEndDate) {
395 Map<String, LeaveCalendarDocumentHeader> principalDocumentHeader = new LinkedHashMap<String, LeaveCalendarDocumentHeader>();
396 for (TKPerson person : persons) {
397 String principalId = person.getPrincipalId();
398 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, payEndDate);
399 if(lcdh != null) {
400 principalDocumentHeader.put(principalId, lcdh);
401 }
402 }
403 return principalDocumentHeader;
404 }
405
406 @Override
407 public boolean isActiveAssignmentFoundOnJobFlsaStatus(String principalId,
408 String flsaStatus, boolean chkForLeaveEligible) {
409 boolean isActiveAssFound = false;
410 java.sql.Date asOfDate = TKUtils.getTimelessDate(null);
411 List<Assignment> activeAssignments = TkServiceLocator
412 .getAssignmentService().getAssignments(principalId, asOfDate);
413 if (activeAssignments != null && !activeAssignments.isEmpty()) {
414 for (Assignment assignment : activeAssignments) {
415 if (assignment != null
416 && assignment.getJob() != null
417 && assignment.getJob().getFlsaStatus() != null
418 && assignment.getJob().getFlsaStatus()
419 .equalsIgnoreCase(flsaStatus)) {
420 if (chkForLeaveEligible) {
421 isActiveAssFound = assignment.getJob()
422 .isEligibleForLeave();
423 if (!isActiveAssFound) {
424 continue;
425 }
426 }
427 isActiveAssFound = true;
428 break;
429 }
430 }
431 }
432 return isActiveAssFound;
433 }
434 }