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.time.accrual.service;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.ArrayList;
021    import java.util.HashMap;
022    import java.util.LinkedHashMap;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.joda.time.DateTime;
028    import org.kuali.hr.lm.LMConstants;
029    import org.kuali.hr.lm.accrual.AccrualCategory;
030    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
031    import org.kuali.hr.time.accrual.TimeOffAccrual;
032    import org.kuali.hr.time.accrual.dao.TimeOffAccrualDao;
033    import org.kuali.hr.time.earncode.EarnCode;
034    import org.kuali.hr.time.principal.PrincipalHRAttributes;
035    import org.kuali.hr.time.service.base.TkServiceLocator;
036    import org.kuali.hr.time.timeblock.TimeBlock;
037    import org.kuali.hr.time.timesheet.TimesheetDocument;
038    import org.kuali.hr.time.util.TkConstants;
039    
040    public class TimeOffAccrualServiceImpl implements TimeOffAccrualService {
041    
042            public static final String ACCRUAL_CATEGORY_KEY = "accrualCategory";
043            public static final String ACCRUAL_RATE_KEY = "accrualRate";
044            public static final String ACCRUAL_NAME_KEY = "accrualName";
045            public static final String YEARLY_CARRYOVER_KEY = "yearlyCarryover";
046            public static final String HOURS_ACCRUED_KEY = "hoursAccrued";
047            public static final String HOURS_TAKEN_KEY = "hoursTaken";
048            public static final String HOURS_ADJUST_KEY = "hoursAdjust";
049            public static final String TOTAL_HOURS_KEY = "totalHours";
050            public static final String EFF_DATE_KEY = "effdt";
051    
052            private TimeOffAccrualDao timeOffAccrualDao;
053    
054            public void setTimeOffAccrualDao(TimeOffAccrualDao timeOffAccrualDao) {
055                    this.timeOffAccrualDao = timeOffAccrualDao;
056            }
057    
058            @Override
059            public List<TimeOffAccrual> getTimeOffAccruals(String principalId, Date asOfDate) {
060                    return timeOffAccrualDao.getActiveTimeOffAccruals(principalId, asOfDate);
061            }
062    
063            @Override
064            public List<Map<String, Object>> getTimeOffAccrualsCalc(String principalId, Date asOfDate) {
065                    List<Map<String, Object>> timeOffAccrualsCalc = new ArrayList<Map<String, Object>>();
066                    Map<String,String> accrualCatToDescr = new HashMap<String, String>();
067                    String accrualRate = "";
068                    
069                    for (TimeOffAccrual timeOffAccrual : getTimeOffAccruals(principalId, asOfDate)) {
070                            String accrualCatDescr = accrualCatToDescr.get(timeOffAccrual.getAccrualCategory());
071                            //if no accrual cat description found look up accrual category and find one
072                            if (StringUtils.isBlank(accrualCatDescr)){
073                                    AccrualCategory accrualCat = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(timeOffAccrual.getAccrualCategory(), asOfDate);
074                                    if (accrualCat != null) {
075                                            accrualCatDescr = accrualCat.getDescr();
076                                            accrualCatToDescr.put(accrualCat.getAccrualCategory(), accrualCatDescr);
077                                    
078                                            DateTime currentDate = new DateTime();
079                                            List<AccrualCategoryRule> accrualCategoryRules = accrualCat.getAccrualCategoryRules();
080                                            
081                                            PrincipalHRAttributes principalHRAttributes = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, asOfDate);
082                                            if (principalHRAttributes == null) {
083                                                    throw new RuntimeException("Cannot find principal hr attributes for " + principalId);
084                                            }
085                                            Date serviceDate = principalHRAttributes.getServiceDate();
086                                            if (serviceDate == null) {
087                                                    throw new RuntimeException("Cannot find service date on principal hr attribute for " + principalId);
088                                            }
089                                            DateTime jodaServiceDate = new DateTime(serviceDate);
090                                            
091                                            Long serviceUnits= new Long(0);
092                                            // for each accrualCategoryRule, compare start/end with the length of service, get corresponding rate and unit
093                                            for(AccrualCategoryRule accrualCategoryRule : accrualCategoryRules){
094                                                    String serviceUnitOfTime = accrualCategoryRule.getServiceUnitOfTime();
095                                                    
096                                                    if ( serviceUnitOfTime.equals(LMConstants.SERVICE_TIME_YEAR)){
097                                                            serviceUnits = new Long(currentDate.getYear() - jodaServiceDate.getYear()) + 1;
098                                                    }
099                                                    if ( serviceUnitOfTime.equals(LMConstants.SERVICE_TIME_MONTHS)){
100                                                            serviceUnits = new Long(currentDate.getYear() - jodaServiceDate.getYear()) * 12 + 
101                                                            new Long(currentDate.getMonthOfYear() - jodaServiceDate.getMonthOfYear()) ;
102                                                    }
103                                                    
104                                                    if ( serviceUnits >= accrualCategoryRule.getStart() && serviceUnits < accrualCategoryRule.getEnd()){
105                                                            accrualRate = accrualCategoryRule.getAccrualRate().toString() + " " + accrualCat.getUnitOfTime();
106                                                            break;
107                                                    }
108                                            }
109                                    }
110                            }
111                            Map<String, Object> output = new LinkedHashMap<String, Object>();
112                            output.put(ACCRUAL_CATEGORY_KEY, accrualCatDescr + "("+timeOffAccrual.getAccrualCategory()+")");
113                            output.put(ACCRUAL_RATE_KEY, accrualRate);
114                            output.put(ACCRUAL_NAME_KEY, timeOffAccrual.getAccrualCategory());
115                            output.put(YEARLY_CARRYOVER_KEY, timeOffAccrual.getYearlyCarryover());
116                            output.put(HOURS_ACCRUED_KEY, timeOffAccrual.getHoursAccrued());
117                            output.put(HOURS_TAKEN_KEY, timeOffAccrual.getHoursTaken());
118                            output.put(HOURS_ADJUST_KEY, timeOffAccrual.getHoursAdjust());
119                            BigDecimal totalHours = timeOffAccrual.getYearlyCarryover().add(timeOffAccrual.getHoursAccrued().subtract(timeOffAccrual.getHoursTaken()).add(timeOffAccrual.getHoursAdjust()));
120                            output.put(TOTAL_HOURS_KEY, totalHours);
121                            output.put(EFF_DATE_KEY, timeOffAccrual.getEffectiveDate());
122                            
123                            
124    
125                            timeOffAccrualsCalc.add(output);
126                    }
127    
128                    return timeOffAccrualsCalc;
129            }
130    
131            public List<String> validateAccrualHoursLimit(TimesheetDocument timesheetDocument) {
132             String pId = "";
133             if (timesheetDocument != null) {
134                 pId = timesheetDocument.getPrincipalId();
135             }
136             
137            return validateAccrualHoursLimit(pId, timesheetDocument.getTimeBlocks(),  timesheetDocument.getAsOfDate());
138            
139        }
140            
141            public List<String> validateAccrualHoursLimit(String pId, List<TimeBlock> tbList, Date asOfDate) {
142                     List<String> warningMessages = new ArrayList<String>();
143    
144             List<Map<String, Object>> calcList = this.getTimeOffAccrualsCalc(pId, asOfDate);
145             
146               if (tbList.isEmpty()) {
147                 return warningMessages;
148             }
149             List<String> accruals = new ArrayList<String>();
150             for (Map<String, Object> aMap : calcList) {
151                    accruals.add((String) aMap.get(ACCRUAL_CATEGORY_KEY));
152             }
153             for (Map<String, Object> aMap : calcList) {
154                 String accrualCategory = (String) aMap.get(ACCRUAL_NAME_KEY);
155                 List<TimeBlock> warningTbs = new ArrayList<TimeBlock>();
156                 BigDecimal totalForAccrCate = this.totalForAccrCate(accrualCategory, tbList, warningTbs);
157                 //if there is no timeblocks for this category no warning is necessary 
158                 if(totalForAccrCate.compareTo(BigDecimal.ZERO)==0){
159                     continue;
160                 }
161                 BigDecimal balanceHrs = (((BigDecimal)aMap.get(YEARLY_CARRYOVER_KEY)).add((BigDecimal)aMap.get(HOURS_ACCRUED_KEY)).subtract((BigDecimal)aMap.get(HOURS_TAKEN_KEY)).add((BigDecimal)aMap.get(HOURS_ADJUST_KEY)));
162                 
163                 if (totalForAccrCate.compareTo(balanceHrs) == 1) {
164                    String msg = "Warning: Total hours entered (" + totalForAccrCate.toString() + ") for Accrual Category \"" + (String) aMap.get(ACCRUAL_CATEGORY_KEY) + "\" has exceeded balance (" + balanceHrs.toString() + "). Problem Time Blocks are:<br/>";
165                    for(TimeBlock tb : warningTbs) {
166                            msg += "Earn code: " + tb.getEarnCode()+ " Hours: " + tb.getHours().toString() + " on Date " + (tb.getBeginTimeDisplay() != null ? tb.getBeginTimeDisplay().toString(TkConstants.DT_BASIC_DATE_FORMAT) : "") + "<br/>";
167                    }
168                    warningMessages.add(msg);
169    
170                 }
171             }
172             return warningMessages;
173        }
174            public List<String> validateAccrualHoursLimitByEarnCode(TimesheetDocument timesheetDocument, String earnCode) {
175                     List<String> warningMessages = new ArrayList<String>();
176             String pId = "";
177            if (timesheetDocument != null) {
178                pId = timesheetDocument.getPrincipalId();
179            }
180            List<Map<String, Object>> calcList = this.getTimeOffAccrualsCalc(pId, timesheetDocument.getAsOfDate());
181    
182            List<TimeBlock> tbList = timesheetDocument.getTimeBlocks();
183            if (tbList.isEmpty()) {
184                return warningMessages;
185            }
186            List<String> accruals = new ArrayList<String>();
187            for (Map<String, Object> aMap : calcList) {
188                    accruals.add((String) aMap.get(ACCRUAL_CATEGORY_KEY));
189             }
190            
191            List<AccrualCategory> accrualCategories = TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategories(timesheetDocument.getAsOfDate());
192        
193            for(AccrualCategory accrualCategory : accrualCategories){
194             if(!accruals.contains(accrualCategory.getAccrualCategory()) && !StringUtils.equals(TkConstants.HOLIDAY_EARN_CODE, accrualCategory.getAccrualCategory())){
195                    Map<String, Object> accrualData = new LinkedHashMap<String, Object>();
196                            accrualData.put(ACCRUAL_CATEGORY_KEY, accrualCategory.getAccrualCategory());
197                            accrualData.put(YEARLY_CARRYOVER_KEY, new BigDecimal(0.00));
198                            accrualData.put(HOURS_ACCRUED_KEY, new BigDecimal(0.00));
199                            accrualData.put(HOURS_TAKEN_KEY, new BigDecimal(0.00));
200                            accrualData.put(HOURS_ADJUST_KEY, new BigDecimal(0.00));
201                            calcList.add(accrualData);
202             }
203            }
204            for (Map<String, Object> aMap : calcList) {
205                String accrualCategory = (String) aMap.get(ACCRUAL_CATEGORY_KEY);
206                List<TimeBlock> warningTbs = new ArrayList<TimeBlock>();
207                BigDecimal totalForAccrCate = this.totalForAccrCate(accrualCategory, tbList, warningTbs);
208                BigDecimal balanceHrs = (((BigDecimal)aMap.get(YEARLY_CARRYOVER_KEY)).add((BigDecimal)aMap.get(HOURS_ACCRUED_KEY)).subtract((BigDecimal)aMap.get(HOURS_TAKEN_KEY)).add((BigDecimal)aMap.get(HOURS_ADJUST_KEY)));
209                
210                if (totalForAccrCate.compareTo(balanceHrs) == 1) {
211                    if (accrualCategory.equals(earnCode)) {
212                            String msg = "Warning: Total hours entered (" + totalForAccrCate.toString() + ") for Accrual Category \"" + (String) aMap.get(ACCRUAL_CATEGORY_KEY) + "\" has exceeded balance (" + balanceHrs.toString() + "). Problem Time Blocks are: ";
213                            for(TimeBlock tb : warningTbs) {
214                                    msg += "Earn code: " + tb.getEarnCode()+ " Hours: " + tb.getHours().toString() + " on Date " + (tb.getBeginTimeDisplay() != null ? tb.getBeginTimeDisplay().toString(TkConstants.DT_BASIC_DATE_FORMAT) : "") + " ";
215                            }
216                            
217                           warningMessages.add(msg);
218                    }
219    
220                }
221            }
222            return warningMessages;
223       }
224    
225        public BigDecimal totalForAccrCate(String accrualCategory, List<TimeBlock> tbList, List<TimeBlock> warningTbs) {
226            BigDecimal total = BigDecimal.ZERO;
227            for (TimeBlock tb : tbList) {
228                String earnCode = tb.getEarnCode();
229                Date asOfDate = new java.sql.Date(tb.getBeginTimestamp().getTime());
230                EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(earnCode, asOfDate);
231                String accrCate = "";
232                if (ec != null) {
233                    accrCate = ec.getAccrualCategory();
234                    if (accrCate != null) {
235                        if (accrCate.equals(accrualCategory)) {
236                            total = total.add(tb.getHours());
237                            warningTbs.add(tb);
238                        }
239                    }
240                }
241            }
242            return total;
243        }
244    
245            @Override
246            public TimeOffAccrual getTimeOffAccrual(Long laTimeOffAccrualId) {
247                    return timeOffAccrualDao.getTimeOffAccrual(laTimeOffAccrualId);
248            }
249            
250            @Override
251            public int getTimeOffAccrualCount(String accrualCategory, Date effectiveDate, String principalId, String lmAccrualId) {
252                    return timeOffAccrualDao.getTimeOffAccrualCount(accrualCategory, effectiveDate, principalId, lmAccrualId);
253            }
254    
255        @Override
256        public  List<TimeOffAccrual> getTimeOffAccruals(String principalId, String accrualCategory){
257            return timeOffAccrualDao.getTimeOffAccruals(principalId, accrualCategory);
258        }
259    }