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.earncode.service; 017 018 import java.math.BigDecimal; 019 import java.sql.Date; 020 import java.util.ArrayList; 021 import java.util.Comparator; 022 import java.util.LinkedHashMap; 023 import java.util.LinkedList; 024 import java.util.List; 025 import java.util.Map; 026 import java.util.Set; 027 028 import org.apache.commons.lang.StringUtils; 029 import org.kuali.hr.job.Job; 030 import org.kuali.hr.lm.LMConstants; 031 import org.kuali.hr.lm.accrual.AccrualCategory; 032 import org.kuali.hr.lm.earncodesec.EarnCodeSecurity; 033 import org.kuali.hr.lm.earncodesec.EarnCodeType; 034 import org.kuali.hr.time.assignment.Assignment; 035 import org.kuali.hr.time.collection.rule.TimeCollectionRule; 036 import org.kuali.hr.time.earncode.EarnCode; 037 import org.kuali.hr.time.earncode.dao.EarnCodeDao; 038 import org.kuali.hr.time.principal.PrincipalHRAttributes; 039 import org.kuali.hr.time.roles.TkUserRoles; 040 import org.kuali.hr.time.service.base.TkServiceLocator; 041 import org.kuali.hr.time.util.TKContext; 042 import org.kuali.hr.time.util.TKUser; 043 import org.kuali.hr.time.util.TKUtils; 044 import org.kuali.hr.time.util.TkConstants; 045 import org.kuali.hr.time.workarea.WorkArea; 046 import org.kuali.rice.krad.util.GlobalVariables; 047 048 import com.google.common.collect.Ordering; 049 050 public class EarnCodeServiceImpl implements EarnCodeService { 051 052 private EarnCodeDao earnCodeDao; 053 054 public void setEarnCodeDao(EarnCodeDao earnCodeDao) { 055 this.earnCodeDao = earnCodeDao; 056 } 057 058 public List<EarnCode> getEarnCodesForLeaveAndTime(Assignment a, Date asOfDate, boolean isLeavePlanningCalendar) { 059 // This method combining both leave calendar and timesheet calendar earn codes may never be used, but it is available. 060 // It was specified in kpme-1745, "Implement getEarnCodesForLeaveAndTime and call both of the above methods and return in one collection." 061 List<EarnCode> earnCodes = getEarnCodesForTime(a, asOfDate); 062 List<EarnCode> leaveEarnCodes = getEarnCodesForLeave(a, asOfDate, isLeavePlanningCalendar); 063 // the following list processing does work as hoped, comparing the objects' data, rather than their references to memory structures. 064 earnCodes.removeAll(leaveEarnCodes); //ensures no overlap during the addAll 065 earnCodes.addAll(leaveEarnCodes); 066 067 return earnCodes; 068 } 069 070 public List<EarnCode> getEarnCodesForTime(Assignment a, Date asOfDate) { 071 //getEarnCodesForTime and getEarnCodesForLeave have some overlapping logic, but they were separated so that they could follow their own distinct logic, so consolidation of logic is not desirable. 072 073 if (a == null) throw new RuntimeException("No assignment parameter."); 074 Job job = a.getJob(); 075 if (job == null || job.getPayTypeObj() == null) throw new RuntimeException("Null job or null job pay type on assignment."); 076 077 List<EarnCode> earnCodes = new LinkedList<EarnCode>(); 078 String earnTypeCode = EarnCodeType.TIME.getCode(); 079 080 TimeCollectionRule tcr = a.getTimeCollectionRule(); 081 082 boolean isClockUser = tcr == null || tcr.isClockUserFl(); 083 boolean isUsersTimesheet = StringUtils.equals(TKContext.getPrincipalId(),a.getPrincipalId()); 084 085 // Reg earn codes will typically not be defined in the earn code security table 086 EarnCode regularEarnCode = getEarnCode(job.getPayTypeObj().getRegEarnCode(), asOfDate); 087 if (regularEarnCode == null) { 088 throw new RuntimeException("No regular earn code defined for job pay type."); 089 } else { 090 // if you are a clock user and this is your timesheet and you are processing the reg earn code, do not add this earn code. Use the clock in/out mechanism. 091 if (isClockUser && isUsersTimesheet) { 092 // do not add reg earn code. use clock. 093 } else { 094 earnCodes.add(regularEarnCode); 095 } 096 } 097 098 List<String> listAccrualCategories = new LinkedList<String>(); 099 String accrualCategory; 100 101 // first make a list of the accrual categories available to the user's Leave Plan (yes, leave plan), for later comparison. 102 PrincipalHRAttributes principalHRAttributes = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(job.getPrincipalId(), asOfDate); 103 boolean fmlaEligible = principalHRAttributes.isFmlaEligible(); 104 boolean workersCompEligible = principalHRAttributes.isWorkersCompEligible(); 105 106 String leavePlan = principalHRAttributes.getLeavePlan(); 107 if (leavePlan != null) { 108 for (AccrualCategory accrualCategories : TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(leavePlan, asOfDate)) { 109 accrualCategory = accrualCategories.getAccrualCategory(); 110 if(accrualCategory != null) { 111 listAccrualCategories.add(accrualCategory); 112 } 113 } 114 } 115 116 // get all earn codes by user security, then we'll filter on accrual category first as we process them. 117 List<EarnCodeSecurity> decs = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), asOfDate); 118 for (EarnCodeSecurity dec : decs) { 119 120 boolean addEarnCode = addEarnCodeBasedOnEmployeeApproverSettings(dec, a, asOfDate); 121 if (addEarnCode) { 122 123 // allow types Time AND Both 124 if (earnTypeCode.equals(dec.getEarnCodeType()) || EarnCodeType.BOTH.getCode().equals(dec.getEarnCodeType())) { 125 EarnCode ec = getEarnCode(dec.getEarnCode(), asOfDate); 126 127 // make sure we got something back from the earn code dao 128 if (ec != null) { 129 130 // now that we have a list of security earn codes, compare their accrual categories to the user's accrual category list. 131 // we also allow earn codes that have no accrual category assigned. 132 if (listAccrualCategories.contains(ec.getAccrualCategory()) || ec.getAccrualCategory() == null) { 133 134 // if the user's fmla flag is Yes, that means we are not restricting codes based on this flag, so any code is shown. 135 // if the fmla flag on a code is yes they can see it. (allow) 136 // if the fmla flag on a code is no they should see it. (allow) 137 // if the user's fmla flag is No, 138 // they can see any codes which are fmla=no. (allow) 139 // they can not see codes with fmla=yes. (exclude earn code) 140 // the fmla earn codes=no do not require any exclusion 141 // the only action required is if the fmla user flag=no: exclude those codes with fmla=yes. 142 143 if ( (fmlaEligible || ec.getFmla().equals("N")) ) { 144 //only want usage accrual balance actions 145 if (StringUtils.equals(ec.getAccrualBalanceAction(), LMConstants.ACCRUAL_BALANCE_ACTION.USAGE)) { 146 // go on, we are allowing these three combinations: YY, YN, NN 147 148 // apply the same logic as FMLA to the Worker Compensation flags. 149 if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) { 150 // go on, we are allowing these three combinations: YY, YN, NN. 151 152 // determine if the holiday earn code should be displayed. 153 if ( showEarnCodeIfHoliday(ec, dec) ) { 154 // non-Holiday earn code will go on, Holiday earn code must meet some requirements in the method. 155 if ( !StringUtils.equals(regularEarnCode.toString(), dec.getEarnCode()) ) { 156 // add earn code if it is not the reg earn code. 157 earnCodes.add(ec); 158 } 159 } 160 } 161 } 162 } 163 } 164 } 165 } 166 } 167 } 168 169 return earnCodes; 170 } 171 172 public List<EarnCode> getEarnCodesForLeave(Assignment a, Date asOfDate, boolean isLeavePlanningCalendar) { 173 //getEarnCodesForTime and getEarnCodesForLeave have some overlapping logic, but they were separated so that they could follow their own distinct logic, so consolidation of logic is not desirable. 174 175 if (a == null) throw new RuntimeException("No assignment parameter."); 176 Job job = a.getJob(); 177 if (job == null || job.getPayTypeObj() == null) throw new RuntimeException("Null job or null job pay type on assignment."); 178 179 List<EarnCode> earnCodes = new LinkedList<EarnCode>(); 180 String earnTypeCode = EarnCodeType.LEAVE.getCode(); 181 // skip getting the regular earn code for Leave Calendar 182 183 List<String> listAccrualCategories = new LinkedList<String>(); 184 String accrualCategory; 185 186 // first make a list of the accrual categories available to the user's leave plan, for later comparison. 187 PrincipalHRAttributes principalHRAttributes = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(job.getPrincipalId(), asOfDate); 188 boolean fmlaEligible = principalHRAttributes.isFmlaEligible(); 189 boolean workersCompEligible = principalHRAttributes.isWorkersCompEligible(); 190 191 String leavePlan = principalHRAttributes.getLeavePlan(); 192 if (leavePlan != null) { 193 for (AccrualCategory accrualCategories : TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(leavePlan, asOfDate)) { 194 accrualCategory = accrualCategories.getAccrualCategory(); 195 if(accrualCategory != null) { 196 listAccrualCategories.add(accrualCategory); 197 } 198 } 199 } 200 201 // get all earn codes by user security, then we'll filter on accrual category first as we process them. 202 List<EarnCodeSecurity> decs = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), asOfDate); 203 for (EarnCodeSecurity dec : decs) { 204 205 boolean addEarnCode = addEarnCodeBasedOnEmployeeApproverSettings(dec, a, asOfDate); 206 if (addEarnCode) { 207 208 // allow types Leave AND Both 209 if (earnTypeCode.equals(dec.getEarnCodeType()) || EarnCodeType.BOTH.getCode().equals(dec.getEarnCodeType())) { 210 EarnCode ec = getEarnCode(dec.getEarnCode(), asOfDate); 211 212 // make sure we got something back from the earn code dao 213 if (ec != null) { 214 215 // now that we have a list of security earn codes, compare their accrual categories to the user's accrual category list. 216 // we also allow earn codes that have no accrual category assigned. 217 if (listAccrualCategories.contains(ec.getAccrualCategory()) || ec.getAccrualCategory() == null) { 218 219 // if the user's fmla flag is Yes, that means we are not restricting codes based on this flag, so any code is shown. 220 // if the fmla flag on a code is yes they can see it. (allow) 221 // if the fmla flag on a code is no they should see it. (allow) 222 // if the user's fmla flag is No, 223 // they can see any codes which are fmla=no. (allow) 224 // they can not see codes with fmla=yes. (exclude earn code) 225 // the fmla earn codes=no do not require any exclusion 226 // the only action required is if the fmla user flag=no: exclude those codes with fmla=yes. 227 228 if ( (fmlaEligible || ec.getFmla().equals("N")) ) { 229 // go on, we are allowing these three combinations: YY, YN, NN 230 231 // Apply the same logic as FMLA to the Worker Compensation flags. 232 if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) { 233 // go on, we are allowing these three combinations: YY, YN, NN. 234 235 //accrual balance action needs to be usage 236 if (StringUtils.equals(ec.getAccrualBalanceAction(), LMConstants.ACCRUAL_BALANCE_ACTION.USAGE)) { 237 // now process the scheduled leave flag, but only for the Planning Calendar, not for the Reporting Calendar. 238 // determine if the planning calendar is in effect. 239 if (isLeavePlanningCalendar) { 240 241 // if the allow_schd_leave flag=yes, add the earn code 242 if (ec.getAllowScheduledLeave().equals("Y")) { 243 earnCodes.add(ec); 244 } 245 246 } else { 247 // this is a reporting calendar, so ignore scheduled leave flag, and add this earn code. 248 earnCodes.add(ec); 249 } 250 } 251 } 252 } 253 } 254 } 255 } 256 } 257 } // end of decs loop 258 259 return earnCodes; 260 } 261 262 private boolean addEarnCodeBasedOnEmployeeApproverSettings(EarnCodeSecurity security, Assignment a, Date asOfDate) { 263 boolean addEarnCode = false; 264 if (security.isEmployee() && 265 (StringUtils.equals(TKUser.getCurrentTargetPerson().getEmployeeId(), GlobalVariables.getUserSession().getPerson().getEmployeeId()))) { 266 addEarnCode = true; 267 } 268 // Check approver flag 269 if (!addEarnCode && security.isApprover()) { 270 Set<Long> workAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas(); 271 for (Long wa : workAreas) { 272 WorkArea workArea = TkServiceLocator.getWorkAreaService().getWorkArea(wa, asOfDate); 273 if (workArea!= null && a.getWorkArea().compareTo(workArea.getWorkArea())==0) { 274 addEarnCode = true; 275 break; 276 } 277 } 278 } 279 return addEarnCode; 280 } 281 282 private boolean showEarnCodeIfHoliday(EarnCode earnCode, EarnCodeSecurity security) { 283 if (earnCode.getEarnCode().equals(TkConstants.HOLIDAY_EARN_CODE)) { 284 if (security.isApprover() || TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).isSystemAdmin()) { 285 return true; 286 } else { 287 return false; 288 } 289 } else { 290 return true; 291 } 292 } 293 294 @Override 295 public List<EarnCode> getEarnCodesForPrincipal(String principalId, Date asOfDate, boolean isLeavePlanningCalendar) { 296 List<EarnCode> earnCodes = new LinkedList<EarnCode>(); 297 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(principalId, asOfDate); 298 for (Assignment assignment : assignments) { 299 List<EarnCode> assignmentEarnCodes = getEarnCodesForLeave(assignment, asOfDate, isLeavePlanningCalendar); 300 // the following list processing does work as hoped, comparing the objects' data, rather than their references to memory structures. 301 earnCodes.removeAll(assignmentEarnCodes); //ensures no overlap during the addAll 302 earnCodes.addAll(assignmentEarnCodes); 303 } 304 305 return earnCodes; 306 } 307 308 public EarnCode getEarnCode(String earnCode, Date asOfDate) { 309 return earnCodeDao.getEarnCode(earnCode, asOfDate); 310 } 311 312 @Override 313 public String getEarnCodeType(String earnCode, Date asOfDate) { 314 EarnCode earnCodeObj = getEarnCode(earnCode, asOfDate); 315 return earnCodeObj.getEarnCodeType(); 316 } 317 318 @Override 319 public EarnCode getEarnCodeById(String earnCodeId) { 320 return earnCodeDao.getEarnCodeById(earnCodeId); 321 } 322 323 public List<EarnCode> getOvertimeEarnCodes(Date asOfDate){ 324 return earnCodeDao.getOvertimeEarnCodes(asOfDate); 325 } 326 327 public List<String> getOvertimeEarnCodesStrs(Date asOfDate){ 328 List<String> ovtEarnCodeStrs = new ArrayList<String>(); 329 List<EarnCode> ovtEarnCodes = getOvertimeEarnCodes(asOfDate); 330 if(ovtEarnCodes != null){ 331 for(EarnCode ovtEc : ovtEarnCodes){ 332 ovtEarnCodeStrs.add(ovtEc.getEarnCode()); 333 } 334 } 335 return ovtEarnCodeStrs; 336 } 337 338 @Override 339 public int getEarnCodeCount(String earnCode) { 340 return earnCodeDao.getEarnCodeCount(earnCode); 341 } 342 343 @Override 344 public int getNewerEarnCodeCount(String earnCode, Date effdt) { 345 return earnCodeDao.getNewerEarnCodeCount(earnCode, effdt); 346 } 347 348 @Override 349 public BigDecimal roundHrsWithEarnCode(BigDecimal hours, EarnCode earnCode) { 350 String roundOption = LMConstants.ROUND_OPTION_MAP.get(earnCode.getRoundingOption()); 351 BigDecimal fractScale = new BigDecimal(earnCode.getFractionalTimeAllowed()); 352 if(roundOption == null) { 353 throw new RuntimeException("Rounding option of Earn Code " + earnCode.getEarnCode() + " is not recognized."); 354 } 355 BigDecimal roundedHours = hours; 356 if(roundOption.equals("Traditional")) { 357 roundedHours = hours.setScale(fractScale.scale(), BigDecimal.ROUND_HALF_EVEN); 358 } else if(roundOption.equals("Truncate")) { 359 roundedHours = hours.setScale(fractScale.scale(), BigDecimal.ROUND_DOWN); 360 } 361 return roundedHours; 362 } 363 364 @Override 365 public Map<String, String> getEarnCodesForDisplay(String principalId, boolean isLeavePlanningCalendar) { 366 return getEarnCodesForDisplayWithEffectiveDate(principalId, TKUtils.getCurrentDate(), isLeavePlanningCalendar); 367 } 368 369 public List<EarnCode> getEarnCodes(String earnCode, String ovtEarnCode, String descr, String leavePlan, String accrualCategory, Date fromEffdt, Date toEffdt, String active, String showHist) { 370 return earnCodeDao.getEarnCodes(earnCode, ovtEarnCode, descr, leavePlan, accrualCategory, fromEffdt, toEffdt, active, showHist); 371 } 372 373 @Override 374 public Map<String, String> getEarnCodesForDisplayWithEffectiveDate(String principalId, Date asOfDate, boolean isLeavePlanningCalendar) { 375 List<EarnCode> earnCodes = this.getEarnCodesForPrincipal(principalId, asOfDate, isLeavePlanningCalendar); 376 377 Date currentDate = TKUtils.getCurrentDate(); 378 boolean futureDate = asOfDate.after(currentDate); 379 List<EarnCode> copyList = new ArrayList<EarnCode>(); 380 copyList.addAll(earnCodes); 381 for (EarnCode earnCode : copyList) { 382 if ( futureDate 383 && !earnCode.getAllowScheduledLeave().equalsIgnoreCase("Y")) { 384 earnCodes.remove(earnCode); 385 } 386 } 387 Comparator<EarnCode> earnCodeComparator = new Comparator<EarnCode>() { 388 @Override 389 public int compare(EarnCode ec1, EarnCode ec2) { 390 return ec1.getEarnCode().compareToIgnoreCase(ec2.getEarnCode()); 391 } 392 }; 393 // Order by leaveCode ascending 394 Ordering<EarnCode> ordering = Ordering.from(earnCodeComparator); 395 396 Map<String, String> earnCodesForDisplay = new LinkedHashMap<String, String>(); 397 for (EarnCode earnCode : ordering.sortedCopy(earnCodes)) { 398 earnCodesForDisplay.put(earnCode.getEarnCodeKeyForDisplay(), earnCode.getEarnCodeValueForDisplay()); 399 } 400 return earnCodesForDisplay; 401 } 402 403 }