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 }