View Javadoc

1   /**
2    * Copyright 2004-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.hr.lm.leavecalendar.validation;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.joda.time.DateTime;
21  import org.joda.time.DateTimeConstants;
22  import org.kuali.hr.lm.LMConstants;
23  import org.kuali.hr.lm.accrual.AccrualCategory;
24  import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
25  import org.kuali.hr.lm.leave.web.LeaveCalendarWSForm;
26  import org.kuali.hr.lm.leaveSummary.LeaveSummary;
27  import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
28  import org.kuali.hr.lm.leaveblock.LeaveBlock;
29  import org.kuali.hr.time.calendar.CalendarEntries;
30  import org.kuali.hr.time.earncode.EarnCode;
31  import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
32  import org.kuali.hr.time.service.base.TkServiceLocator;
33  import org.kuali.hr.time.util.TKContext;
34  import org.kuali.hr.time.util.TKUtils;
35  import org.kuali.hr.time.util.TkConstants;
36  
37  import java.math.BigDecimal;
38  import java.sql.Date;
39  import java.util.*;
40  
41  import org.kuali.rice.kew.api.KewApiServiceLocator;
42  import org.kuali.rice.kew.api.document.DocumentStatus;
43  import org.kuali.rice.krad.util.ObjectUtils;
44  
45  public class LeaveCalendarValidationUtil {
46      
47      //begin KPME-1263
48      public static List<String> validateLeaveAccrualRuleMaxUsage(LeaveCalendarWSForm lcf) {
49      	LeaveBlock updatedLeaveBlock = null;
50      	if(lcf.getLeaveBlockId() != null) {
51      		updatedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lcf.getLeaveBlockId());
52      	}
53      	return validateLeaveAccrualRuleMaxUsage(lcf.getLeaveSummary(), lcf.getSelectedEarnCode(), lcf.getStartDate(),
54      			lcf.getEndDate(), lcf.getLeaveAmount(), updatedLeaveBlock);
55      }
56  
57  	public static List<String> validateLeaveAccrualRuleMaxUsage(LeaveSummary ls, String selectedEarnCode, String leaveStartDateString,
58  			String leaveEndDateString, BigDecimal leaveAmount, LeaveBlock updatedLeaveBlock) {
59      	List<String> errors = new ArrayList<String>();
60          String principalId = TKContext.getTargetPrincipalId();
61      	long daysSpan = TKUtils.getDaysBetween(TKUtils.formatDateString(leaveStartDateString), TKUtils.formatDateString(leaveEndDateString));
62      	if(ls != null && CollectionUtils.isNotEmpty(ls.getLeaveSummaryRows())) {
63  	    	BigDecimal oldLeaveAmount = null;
64  	    	boolean earnCodeChanged = false;
65      		if(updatedLeaveBlock != null) {
66      			if(!updatedLeaveBlock.getEarnCode().equals(selectedEarnCode)) {
67      				earnCodeChanged = true;
68      			}
69      			if(!updatedLeaveBlock.getLeaveAmount().equals(leaveAmount)) {
70      				oldLeaveAmount = updatedLeaveBlock.getLeaveAmount();
71      			}
72      		}
73      		Date aDate = TKUtils.formatDateString(leaveEndDateString);
74  	    	EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, aDate);
75  	    	if(earnCodeObj != null) {
76  	    		AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(earnCodeObj.getAccrualCategory(), aDate);
77  	    		if(accrualCategory != null) {
78  	    			List<LeaveSummaryRow> rows = ls.getLeaveSummaryRows();
79  	    			for(LeaveSummaryRow aRow : rows) {
80  	    				if(aRow.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) {
81  	    					//Does employee have overrides in place?
82  	    					List<EmployeeOverride> employeeOverrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId,TKUtils.formatDateString(leaveEndDateString));
83  	    					String leavePlan = accrualCategory.getLeavePlan();
84  	    					BigDecimal maxUsage = aRow.getUsageLimit();
85  	    					for(EmployeeOverride eo : employeeOverrides) {
86  	    						if(eo.getLeavePlan().equals(leavePlan) && eo.getAccrualCategory().equals(aRow.getAccrualCategory())) {
87  	    							if(eo.getOverrideType().equals("MU") && eo.isActive()) {
88  	    								if(eo.getOverrideValue()!=null && !eo.getOverrideValue().equals(""))
89  	    									maxUsage = new BigDecimal(eo.getOverrideValue());
90  	    								else // no limit flag
91  	    									maxUsage = null;
92  	    							}
93  	    						}
94  	    					}
95  	    					BigDecimal ytdUsage = aRow.getYtdApprovedUsage();
96  	    					BigDecimal pendingLeaveBalance = aRow.getPendingLeaveRequests();
97  	    					BigDecimal desiredUsage = new BigDecimal(0);
98  	    					if(pendingLeaveBalance!=null) {
99  	    						if(oldLeaveAmount!=null) {
100 	    							
101 	    							if(!earnCodeChanged || 
102 	    									updatedLeaveBlock.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) {
103    			    						pendingLeaveBalance = pendingLeaveBalance.subtract(oldLeaveAmount.abs());
104 	    							}
105 	    						}
106  
107     							desiredUsage = desiredUsage.add(pendingLeaveBalance);
108 	    					}
109    							
110 	    					desiredUsage = desiredUsage.add(leaveAmount.multiply(new BigDecimal(daysSpan+1)));
111 
112    							if(ytdUsage!=null) {
113     							desiredUsage = desiredUsage.add(ytdUsage);
114     						}
115 	    					if(maxUsage!=null) {
116 		    					if(desiredUsage.compareTo(maxUsage) > 0 ) {
117 		    						errors.add("This leave request would exceed the usage limit for " + aRow.getAccrualCategory());                        //errorMessages
118 		    					}
119 	    					}
120 	    				}
121 	    			}
122 	    		}
123 	    	}
124     	}
125     	return errors;
126     }
127 	//End KPME-1263
128 
129 	//TODO: Move to WarningService
130 	public static Map<String, Set<String>> validatePendingTransactions(String principalId, Date fromDate, Date toDate) {
131 		Map<String, Set<String>> allMessages = new HashMap<String, Set<String>>();
132 		
133         Set<String> actionMessages = new HashSet<String>();
134         Set<String> infoMessages = new HashSet<String>();
135         Set<String> warningMessages = new HashSet<String>();
136         
137         allMessages.put("actionMessages", actionMessages);
138         allMessages.put("infoMessages", infoMessages);
139         allMessages.put("warningMessages", warningMessages);
140         List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(principalId, fromDate, toDate, LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
141         Set<String> workflowDocIds = new HashSet<String>();
142         for(LeaveBlock lb : leaveBlocks) {
143         	if(lb.getTransactionalDocId() != null)
144         		workflowDocIds.add(lb.getTransactionalDocId());
145         	else
146         		if(lb.getDescription().contains("Forfeited balance transfer amount"))
147         			allMessages.get("infoMessages").add("A max balance action that forfeited accrued leave occurred on this calendar");
148         }
149         for(String workflowDocId : workflowDocIds) {
150             DocumentStatus status = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(workflowDocId);
151             
152             if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.FINAL)) {
153             	allMessages.get("infoMessages").add("A transfer action occurred on this calendar");
154             }
155             else if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.ENROUTE)) {
156             	allMessages.get("actionMessages").add("A pending balance transfer exists on this calendar. It must be finalized before this calendar can be approved");
157             }
158             else
159             	allMessages.get("warningMessages").add("A balance transfer document exists for this calendar with status neither final or enroute");
160         }
161         
162         leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(principalId, fromDate, toDate, LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
163         workflowDocIds = new HashSet<String>();
164         for(LeaveBlock lb : leaveBlocks) {
165         	if(lb.getTransactionalDocId() != null)
166         		workflowDocIds.add(lb.getTransactionalDocId());
167         }
168         for(String workflowDocId : workflowDocIds) {
169             DocumentStatus status = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(workflowDocId);
170 
171             if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.FINAL)) {
172             	allMessages.get("infoMessages").add("A payout action occurred on this calendar");
173             }
174             else if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.ENROUTE)) {
175             	allMessages.get("actionMessages").add("A pending payout exists on this calendar. It must be finalized before this calendar can be approved");
176             }
177             else
178             	allMessages.get("warningMessages").add("A payout document exists for this calendar with status neither final or enroute");
179         }
180 /*        //TODO: Re-combine balance transfer and leave payout...
181         List<BalanceTransfer> transfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(principalId, fromDate, toDate);
182         for(BalanceTransfer transfer : transfers) {
183         	if(StringUtils.equals(transfer.getStatus(), TkConstants.ROUTE_STATUS.ENROUTE)) {
184         		allMessages.get("actionMessages").add("A pending balance transfer exists on this calendar. It must be finalized before this calendar can be approved");	//action
185         	}
186     		if(StringUtils.equals(transfer.getStatus() ,TkConstants.ROUTE_STATUS.FINAL)) {
187     			if(StringUtils.isEmpty(transfer.getSstoId())) {
188 	            	if(transfer.getTransferAmount().compareTo(BigDecimal.ZERO) == 0 && transfer.getAmountTransferred().compareTo(BigDecimal.ZERO) == 0) {
189 	            		if(transfer.getForfeitedAmount() != null && transfer.getForfeitedAmount().signum() != 0)
190 	            			allMessages.get("infoMessages").add("A transfer action that forfeited leave occured on this calendar");	//info
191 	            	}
192 	            	else
193 	            		allMessages.get("infoMessages").add("A transfer action occurred on this calendar");	//info
194     			}
195     			else
196     				allMessages.get("infoMessages").add("System scheduled time off was transferred on this calendar");	//info
197     		}
198     		if(StringUtils.equals(transfer.getStatus() ,TkConstants.ROUTE_STATUS.DISAPPROVED)) {
199     			if(StringUtils.isEmpty(transfer.getSstoId())) {
200     	        	if(transfer.getTransferAmount().compareTo(BigDecimal.ZERO) == 0 && transfer.getAmountTransferred().compareTo(BigDecimal.ZERO) == 0) {
201     	        		if(transfer.getForfeitedAmount() != null && transfer.getForfeitedAmount().signum() != 0)
202     	        			allMessages.get("infoMessages").add("A transfer action that forfeited leave occured on this calendar");	//info
203     	        	}
204     	        	else
205     	        		allMessages.get("infoMessages").add("A transfer action occurred on this calendar");	//info
206     			}
207     		}
208         }
209         
210         List<LeavePayout> payouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(principalId, fromDate, toDate);
211         for(LeavePayout payout : payouts) {
212         	if(StringUtils.equals(payout.getStatus(), TkConstants.ROUTE_STATUS.ENROUTE)) {
213         		allMessages.get("actionMessages").add("A pending payout exists on this calendar. It must be finalized before this calendar can be approved");
214         	}
215     		if(StringUtils.equals(payout.getStatus() ,TkConstants.ROUTE_STATUS.FINAL)) {
216             	if(payout.getPayoutAmount().compareTo(BigDecimal.ZERO) == 0) {
217             		if(payout.getForfeitedAmount() != null && payout.getForfeitedAmount().signum() != 0)
218             			allMessages.get("infoMessages").add("A payout action that forfeited leave occured on this calendar");
219             	}
220             	else
221             		allMessages.get("infoMessages").add("A payout action occurred on this calendar");
222     		}
223     		if(StringUtils.equals(payout.getStatus() ,TkConstants.ROUTE_STATUS.DISAPPROVED)) {
224 	        	if(payout.getPayoutAmount().compareTo(BigDecimal.ZERO) == 0) {
225 	        		if(payout.getForfeitedAmount() != null && payout.getForfeitedAmount().signum() != 0)
226 	        			allMessages.get("infoMessages").add("A disapproved payout that forfeited leave occured on this calendar");
227 	        	}
228 	        	else
229 	        		allMessages.get("infoMessages").add("A disapproved payout occurred on this calendar");
230     		}
231         }*/
232         
233         return allMessages;
234 	}
235 	
236     // get warning messages associated with earn codes of leave blocks
237     public static Map<String, Set<String>> getWarningMessagesForLeaveBlocks(List<LeaveBlock> leaveBlocks) {
238 //        List<String> warningMessages = new ArrayList<String>();
239         Map<String, Set<String>> allMessages = new HashMap<String, Set<String>>();
240         
241         Set<String> actionMessages = new HashSet<String>();
242         Set<String> infoMessages = new HashSet<String>();
243         Set<String> warningMessages = new HashSet<String>();
244 
245         if (CollectionUtils.isNotEmpty(leaveBlocks)) {
246             for(LeaveBlock lb : leaveBlocks) {
247                 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(lb.getEarnCode(), lb.getLeaveDate());
248                 if(ec != null) {
249                     EarnCodeGroup eg = TkServiceLocator.getEarnCodeGroupService().getEarnCodeGroupForEarnCode(lb.getEarnCode(), lb.getLeaveDate());
250                     if(eg != null && !StringUtils.isEmpty(eg.getWarningText())) {
251                         warningMessages.add(eg.getWarningText());
252                     }
253                 }
254             }
255         }
256         allMessages.put("actionMessages", actionMessages);
257         allMessages.put("infoMessages", infoMessages);
258         allMessages.put("warningMessages", warningMessages);
259 
260 //        warningMessages.addAll(aSet);
261         return allMessages;
262     }
263 
264     public static List<String> validateAvailableLeaveBalance(LeaveCalendarWSForm lcf) {
265     	LeaveBlock updatedLeaveBlock = null;
266     	if(lcf.getLeaveBlockId() != null) {
267 			updatedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lcf.getLeaveBlockId());
268     	}
269     	return validateAvailableLeaveBalanceForUsage(lcf.getSelectedEarnCode(), lcf.getStartDate(), lcf.getEndDate(), lcf.getLeaveAmount(), updatedLeaveBlock);
270     }
271 
272     public static List<String> validateAvailableLeaveBalanceForUsage(String earnCode, String leaveStartDateString, String leaveEndDateString,
273     		BigDecimal leaveAmount, LeaveBlock updatedLeaveBlock) {
274     	List<String> errors = new ArrayList<String>();
275     	boolean earnCodeChanged = false;
276     	BigDecimal oldAmount = null;
277     	
278 		if(updatedLeaveBlock != null) {
279 			if(!updatedLeaveBlock.getEarnCode().equals(earnCode)) {
280 				earnCodeChanged = true;
281 			}
282 			if(!updatedLeaveBlock.getLeaveAmount().equals(leaveAmount)) {
283 				oldAmount = updatedLeaveBlock.getLeaveAmount();
284 			}
285 		}
286 		Date startDate = TKUtils.formatDateString(leaveStartDateString);
287 		Date endDate = TKUtils.formatDateString(leaveEndDateString);
288 		long daysSpan = TKUtils.getDaysBetween(startDate,endDate);
289     	EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(earnCode, endDate);
290     	if(earnCodeObj != null && earnCodeObj.getAllowNegativeAccrualBalance().equals("N")) {
291     		AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(earnCodeObj.getAccrualCategory(), endDate);
292     		if(accrualCategory != null) {
293 				java.util.Date nextIntervalDate = TkServiceLocator.getAccrualService().getNextAccrualIntervalDate(accrualCategory.getAccrualEarnInterval(), endDate);
294 				// get the usage checking cut off Date, normally it's the day before the next interval date
295 				java.util.Date usageEndDate = nextIntervalDate;
296 				if(nextIntervalDate.compareTo(endDate) > 0) {
297 					Calendar aCal = Calendar.getInstance();
298 					aCal.setTime(nextIntervalDate);
299 					aCal.add(Calendar.DAY_OF_YEAR, -1);
300 					usageEndDate = aCal.getTime();
301 				}
302 				// use the end of the year as the interval date for usage checking of no-accrual hours,
303 				// normally no-accrual hours are from banked/transferred system scheduled time offs
304 				if(accrualCategory.getAccrualEarnInterval().equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
305 					Calendar aCal = Calendar.getInstance();
306 					aCal.setTime(endDate);
307 					aCal.set(Calendar.MONTH, Calendar.DECEMBER);
308 					aCal.set(Calendar.DAY_OF_MONTH, 31);
309 					nextIntervalDate = aCal.getTime();
310 					usageEndDate = nextIntervalDate;
311 				}
312 				BigDecimal availableBalance = TkServiceLocator.getLeaveSummaryService()
313 							.getLeaveBalanceForAccrCatUpToDate(TKContext.getTargetPrincipalId(), startDate, endDate, accrualCategory.getAccrualCategory(), usageEndDate);
314 
315 				if(oldAmount!=null) {
316 					if(!earnCodeChanged ||
317 							updatedLeaveBlock.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) {
318 						availableBalance = availableBalance.add(oldAmount.abs());
319 					}
320 				}
321 				//multiply by days in span in case the user has also edited the start/end dates.
322 				BigDecimal desiredUsage = leaveAmount.multiply(new BigDecimal(daysSpan+1));
323 				if(desiredUsage.compareTo(availableBalance) >  0 ) {
324 					errors.add("Requested leave amount " + desiredUsage.toString() + " is greater than available leave balance " + availableBalance.toString());      //errorMessages
325 				}
326     		}
327     	}
328     
329     	return errors;
330     }
331     
332     // KPME-2010
333     public static List<String> validateSpanningWeeks(LeaveCalendarWSForm lcf) {
334     	boolean spanningWeeks = lcf.getSpanningWeeks().equalsIgnoreCase("y");
335     	DateTime startTemp = new DateTime(TKUtils.convertDateStringToTimestamp(lcf.getStartDate()).getTime());
336         DateTime endTemp = new DateTime(TKUtils.convertDateStringToTimestamp(lcf.getEndDate()).getTime());
337     	
338         List<String> errors = new ArrayList<String>();
339     	boolean valid = true;
340     	while ((startTemp.isBefore(endTemp) || startTemp.isEqual(endTemp)) && valid) {
341            	if (!spanningWeeks && 
342         		(startTemp.getDayOfWeek() == DateTimeConstants.SATURDAY || startTemp.getDayOfWeek() == DateTimeConstants.SUNDAY)) {
343         		valid = false;
344         	}
345         	startTemp = startTemp.plusDays(1);
346         }
347         if (!valid) {
348         	errors.add("Weekend day is selected, but include weekends checkbox is not checked");            //errorMessages
349         }
350     	return errors;
351     }
352 }