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.leavecalendar.validation;
017
018 import org.apache.commons.collections.CollectionUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.joda.time.DateTime;
021 import org.joda.time.DateTimeConstants;
022 import org.kuali.hr.lm.LMConstants;
023 import org.kuali.hr.lm.accrual.AccrualCategory;
024 import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
025 import org.kuali.hr.lm.leave.web.LeaveCalendarWSForm;
026 import org.kuali.hr.lm.leaveSummary.LeaveSummary;
027 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
028 import org.kuali.hr.lm.leaveblock.LeaveBlock;
029 import org.kuali.hr.time.calendar.CalendarEntries;
030 import org.kuali.hr.time.earncode.EarnCode;
031 import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
032 import org.kuali.hr.time.service.base.TkServiceLocator;
033 import org.kuali.hr.time.util.TKContext;
034 import org.kuali.hr.time.util.TKUtils;
035 import org.kuali.hr.time.util.TkConstants;
036
037 import java.math.BigDecimal;
038 import java.sql.Date;
039 import java.util.*;
040
041 import org.kuali.rice.kew.api.KewApiServiceLocator;
042 import org.kuali.rice.kew.api.document.DocumentStatus;
043 import org.kuali.rice.krad.util.ObjectUtils;
044
045 public class LeaveCalendarValidationUtil {
046
047 //begin KPME-1263
048 public static List<String> validateLeaveAccrualRuleMaxUsage(LeaveCalendarWSForm lcf) {
049 LeaveBlock updatedLeaveBlock = null;
050 if(lcf.getLeaveBlockId() != null) {
051 updatedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lcf.getLeaveBlockId());
052 }
053 return validateLeaveAccrualRuleMaxUsage(lcf.getLeaveSummary(), lcf.getSelectedEarnCode(), lcf.getStartDate(),
054 lcf.getEndDate(), lcf.getLeaveAmount(), updatedLeaveBlock);
055 }
056
057 public static List<String> validateLeaveAccrualRuleMaxUsage(LeaveSummary ls, String selectedEarnCode, String leaveStartDateString,
058 String leaveEndDateString, BigDecimal leaveAmount, LeaveBlock updatedLeaveBlock) {
059 List<String> errors = new ArrayList<String>();
060 String principalId = TKContext.getTargetPrincipalId();
061 long daysSpan = TKUtils.getDaysBetween(TKUtils.formatDateString(leaveStartDateString), TKUtils.formatDateString(leaveEndDateString));
062 if(ls != null && CollectionUtils.isNotEmpty(ls.getLeaveSummaryRows())) {
063 BigDecimal oldLeaveAmount = null;
064 boolean earnCodeChanged = false;
065 if(updatedLeaveBlock != null) {
066 if(!updatedLeaveBlock.getEarnCode().equals(selectedEarnCode)) {
067 earnCodeChanged = true;
068 }
069 if(!updatedLeaveBlock.getLeaveAmount().equals(leaveAmount)) {
070 oldLeaveAmount = updatedLeaveBlock.getLeaveAmount();
071 }
072 }
073 Date aDate = TKUtils.formatDateString(leaveEndDateString);
074 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, aDate);
075 if(earnCodeObj != null) {
076 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(earnCodeObj.getAccrualCategory(), aDate);
077 if(accrualCategory != null) {
078 List<LeaveSummaryRow> rows = ls.getLeaveSummaryRows();
079 for(LeaveSummaryRow aRow : rows) {
080 if(aRow.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) {
081 //Does employee have overrides in place?
082 List<EmployeeOverride> employeeOverrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId,TKUtils.formatDateString(leaveEndDateString));
083 String leavePlan = accrualCategory.getLeavePlan();
084 BigDecimal maxUsage = aRow.getUsageLimit();
085 for(EmployeeOverride eo : employeeOverrides) {
086 if(eo.getLeavePlan().equals(leavePlan) && eo.getAccrualCategory().equals(aRow.getAccrualCategory())) {
087 if(eo.getOverrideType().equals("MU") && eo.isActive()) {
088 if(eo.getOverrideValue()!=null && !eo.getOverrideValue().equals(""))
089 maxUsage = new BigDecimal(eo.getOverrideValue());
090 else // no limit flag
091 maxUsage = null;
092 }
093 }
094 }
095 BigDecimal ytdUsage = aRow.getYtdApprovedUsage();
096 BigDecimal pendingLeaveBalance = aRow.getPendingLeaveRequests();
097 BigDecimal desiredUsage = new BigDecimal(0);
098 if(pendingLeaveBalance!=null) {
099 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 }