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 }