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    }