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, boolean includeRegularEarnCode) { 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 || includeRegularEarnCode ) { 092 earnCodes.add(regularEarnCode); 093 } 094 } 095 096 List<String> listAccrualCategories = new LinkedList<String>(); 097 String accrualCategory; 098 099 // first make a list of the accrual categories available to the user's Leave Plan (yes, leave plan), for later comparison. 100 PrincipalHRAttributes principalHRAttributes = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(job.getPrincipalId(), asOfDate); 101 boolean fmlaEligible = principalHRAttributes.isFmlaEligible(); 102 boolean workersCompEligible = principalHRAttributes.isWorkersCompEligible(); 103 104 String leavePlan = principalHRAttributes.getLeavePlan(); 105 if (leavePlan != null) { 106 for (AccrualCategory accrualCategories : TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(leavePlan, asOfDate)) { 107 accrualCategory = accrualCategories.getAccrualCategory(); 108 if(accrualCategory != null) { 109 listAccrualCategories.add(accrualCategory); 110 } 111 } 112 } 113 114 // get all earn codes by user security, then we'll filter on accrual category first as we process them. 115 List<EarnCodeSecurity> decs = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), asOfDate); 116 for (EarnCodeSecurity dec : decs) { 117 118 boolean addEarnCode = addEarnCodeBasedOnEmployeeApproverSettings(dec, a, asOfDate); 119 if (addEarnCode) { 120 121 // allow types Time AND Both 122 if (earnTypeCode.equals(dec.getEarnCodeType()) || EarnCodeType.BOTH.getCode().equals(dec.getEarnCodeType())) { 123 EarnCode ec = getEarnCode(dec.getEarnCode(), asOfDate); 124 125 // make sure we got something back from the earn code dao 126 if (ec != null) { 127 128 // if the user's fmla flag is Yes, that means we are not restricting codes based on this flag, so any code is shown. 129 // if the fmla flag on a code is yes they can see it. (allow) 130 // if the fmla flag on a code is no they should see it. (allow) 131 // if the user's fmla flag is No, 132 // they can see any codes which are fmla=no. (allow) 133 // they can not see codes with fmla=yes. (exclude earn code) 134 // the fmla earn codes=no do not require any exclusion 135 // the only action required is if the fmla user flag=no: exclude those codes with fmla=yes. 136 137 if ( (fmlaEligible || ec.getFmla().equals("N")) ) { 138 if (ec.getAccrualCategory() == null 139 || (listAccrualCategories.contains(ec.getAccrualCategory()) 140 && LMConstants.ACCRUAL_BALANCE_ACTION.USAGE.equals(ec.getAccrualBalanceAction()))) { 141 // go on, we are allowing these three combinations: YY, YN, NN 142 143 // apply the same logic as FMLA to the Worker Compensation flags. 144 if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) { 145 // go on, we are allowing these three combinations: YY, YN, NN. 146 147 // determine if the holiday earn code should be displayed. 148 if ( showEarnCodeIfHoliday(ec, dec) ) { 149 // non-Holiday earn code will go on, Holiday earn code must meet some requirements in the method. 150 if ( !StringUtils.equals(regularEarnCode.toString(), dec.getEarnCode()) ) { 151 // add earn code if it is not the reg earn code. 152 earnCodes.add(ec); 153 } 154 } 155 } 156 } 157 } 158 } 159 } 160 } 161 } 162 163 return earnCodes; 164 } 165 166 public List<EarnCode> getEarnCodesForTime(Assignment a, Date asOfDate) { 167 return getEarnCodesForTime(a, asOfDate, false); 168 } 169 170 public List<EarnCode> getEarnCodesForLeave(Assignment a, Date asOfDate, boolean isLeavePlanningCalendar) { 171 //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. 172 173 if (a == null) throw new RuntimeException("No assignment parameter."); 174 Job job = a.getJob(); 175 if (job == null || job.getPayTypeObj() == null) throw new RuntimeException("Null job or null job pay type on assignment."); 176 177 List<EarnCode> earnCodes = new LinkedList<EarnCode>(); 178 String earnTypeCode = EarnCodeType.LEAVE.getCode(); 179 // skip getting the regular earn code for Leave Calendar 180 181 List<String> listAccrualCategories = new LinkedList<String>(); 182 String accrualCategory; 183 184 // first make a list of the accrual categories available to the user's leave plan, for later comparison. 185 PrincipalHRAttributes principalHRAttributes = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(job.getPrincipalId(), asOfDate); 186 boolean fmlaEligible = principalHRAttributes.isFmlaEligible(); 187 boolean workersCompEligible = principalHRAttributes.isWorkersCompEligible(); 188 189 String leavePlan = principalHRAttributes.getLeavePlan(); 190 if (leavePlan != null) { 191 for (AccrualCategory accrualCategories : TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(leavePlan, asOfDate)) { 192 accrualCategory = accrualCategories.getAccrualCategory(); 193 if(accrualCategory != null) { 194 listAccrualCategories.add(accrualCategory); 195 } 196 } 197 } 198 199 // get all earn codes by user security, then we'll filter on accrual category first as we process them. 200 List<EarnCodeSecurity> decs = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), asOfDate); 201 for (EarnCodeSecurity dec : decs) { 202 203 boolean addEarnCode = addEarnCodeBasedOnEmployeeApproverSettings(dec, a, asOfDate); 204 if (addEarnCode) { 205 206 // allow types Leave AND Both 207 if (earnTypeCode.equals(dec.getEarnCodeType()) || EarnCodeType.BOTH.getCode().equals(dec.getEarnCodeType())) { 208 EarnCode ec = getEarnCode(dec.getEarnCode(), asOfDate); 209 210 // make sure we got something back from the earn code dao 211 if (ec != null) { 212 213 // if the user's fmla flag is Yes, that means we are not restricting codes based on this flag, so any code is shown. 214 // if the fmla flag on a code is yes they can see it. (allow) 215 // if the fmla flag on a code is no they should see it. (allow) 216 // if the user's fmla flag is No, 217 // they can see any codes which are fmla=no. (allow) 218 // they can not see codes with fmla=yes. (exclude earn code) 219 // the fmla earn codes=no do not require any exclusion 220 // the only action required is if the fmla user flag=no: exclude those codes with fmla=yes. 221 222 if ( (fmlaEligible || ec.getFmla().equals("N")) ) { 223 if (listAccrualCategories.contains(ec.getAccrualCategory()) || ec.getAccrualCategory() == null) { 224 if (StringUtils.equals(ec.getAccrualBalanceAction(), LMConstants.ACCRUAL_BALANCE_ACTION.USAGE)) { 225 // go on, we are allowing these three combinations: YY, YN, NN. 226 227 // Apply the same logic as FMLA to the Worker Compensation flags. 228 if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) { 229 // go on, we are allowing these three combinations: YY, YN, NN. 230 231 // now process the scheduled leave flag, but only for the Planning Calendar, not for the Reporting Calendar. 232 // determine if the planning calendar is in effect. 233 if (isLeavePlanningCalendar) { 234 235 // if the allow_schd_leave flag=yes, add the earn code 236 if (ec.getAllowScheduledLeave().equals("Y")) { 237 earnCodes.add(ec); 238 } 239 240 } else { 241 // this is a reporting calendar, so ignore scheduled leave flag, and add this earn code. 242 earnCodes.add(ec); 243 } 244 } 245 } 246 } 247 } 248 } 249 } 250 } 251 } // end of decs loop 252 253 return earnCodes; 254 } 255 256 private boolean addEarnCodeBasedOnEmployeeApproverSettings(EarnCodeSecurity security, Assignment a, Date asOfDate) { 257 boolean addEarnCode = false; 258 if (security.isEmployee() && 259 (StringUtils.equals(TKUser.getCurrentTargetPerson().getEmployeeId(), GlobalVariables.getUserSession().getPerson().getEmployeeId()))) { 260 addEarnCode = true; 261 } 262 // Check approver flag 263 if (!addEarnCode && security.isApprover()) { 264 Set<Long> workAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas(); 265 for (Long wa : workAreas) { 266 WorkArea workArea = TkServiceLocator.getWorkAreaService().getWorkArea(wa, asOfDate); 267 if (workArea!= null && a.getWorkArea().compareTo(workArea.getWorkArea())==0) { 268 addEarnCode = true; 269 break; 270 } 271 } 272 } 273 return addEarnCode; 274 } 275 276 private boolean showEarnCodeIfHoliday(EarnCode earnCode, EarnCodeSecurity security) { 277 if (earnCode.getEarnCode().equals(TkConstants.HOLIDAY_EARN_CODE)) { 278 if (security.isApprover() || TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).isSystemAdmin()) { 279 return true; 280 } else { 281 return false; 282 } 283 } else { 284 return true; 285 } 286 } 287 288 @Override 289 public List<EarnCode> getEarnCodesForPrincipal(String principalId, Date asOfDate, boolean isLeavePlanningCalendar) { 290 List<EarnCode> earnCodes = new LinkedList<EarnCode>(); 291 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(principalId, asOfDate); 292 for (Assignment assignment : assignments) { 293 List<EarnCode> assignmentEarnCodes = getEarnCodesForLeave(assignment, asOfDate, isLeavePlanningCalendar); 294 // the following list processing does work as hoped, comparing the objects' data, rather than their references to memory structures. 295 earnCodes.removeAll(assignmentEarnCodes); //ensures no overlap during the addAll 296 earnCodes.addAll(assignmentEarnCodes); 297 } 298 299 return earnCodes; 300 } 301 302 public EarnCode getEarnCode(String earnCode, Date asOfDate) { 303 return earnCodeDao.getEarnCode(earnCode, asOfDate); 304 } 305 306 @Override 307 public String getEarnCodeType(String earnCode, Date asOfDate) { 308 EarnCode earnCodeObj = getEarnCode(earnCode, asOfDate); 309 return earnCodeObj.getEarnCodeType(); 310 } 311 312 @Override 313 public EarnCode getEarnCodeById(String earnCodeId) { 314 return earnCodeDao.getEarnCodeById(earnCodeId); 315 } 316 317 public List<EarnCode> getOvertimeEarnCodes(Date asOfDate){ 318 return earnCodeDao.getOvertimeEarnCodes(asOfDate); 319 } 320 321 public List<String> getOvertimeEarnCodesStrs(Date asOfDate){ 322 List<String> ovtEarnCodeStrs = new ArrayList<String>(); 323 List<EarnCode> ovtEarnCodes = getOvertimeEarnCodes(asOfDate); 324 if(ovtEarnCodes != null){ 325 for(EarnCode ovtEc : ovtEarnCodes){ 326 ovtEarnCodeStrs.add(ovtEc.getEarnCode()); 327 } 328 } 329 return ovtEarnCodeStrs; 330 } 331 332 @Override 333 public int getEarnCodeCount(String earnCode) { 334 return earnCodeDao.getEarnCodeCount(earnCode); 335 } 336 337 @Override 338 public int getNewerEarnCodeCount(String earnCode, Date effdt) { 339 return earnCodeDao.getNewerEarnCodeCount(earnCode, effdt); 340 } 341 342 @Override 343 public BigDecimal roundHrsWithEarnCode(BigDecimal hours, EarnCode earnCode) { 344 String roundOption = LMConstants.ROUND_OPTION_MAP.get(earnCode.getRoundingOption()); 345 BigDecimal fractScale = new BigDecimal(earnCode.getFractionalTimeAllowed()); 346 if(roundOption == null) { 347 throw new RuntimeException("Rounding option of Earn Code " + earnCode.getEarnCode() + " is not recognized."); 348 } 349 BigDecimal roundedHours = hours; 350 if(roundOption.equals("Traditional")) { 351 roundedHours = hours.setScale(fractScale.scale(), BigDecimal.ROUND_HALF_EVEN); 352 } else if(roundOption.equals("Truncate")) { 353 roundedHours = hours.setScale(fractScale.scale(), BigDecimal.ROUND_DOWN); 354 } 355 return roundedHours; 356 } 357 358 @Override 359 public Map<String, String> getEarnCodesForDisplay(String principalId, boolean isLeavePlanningCalendar) { 360 return getEarnCodesForDisplayWithEffectiveDate(principalId, TKUtils.getCurrentDate(), isLeavePlanningCalendar); 361 } 362 363 public List<EarnCode> getEarnCodes(String earnCode, String ovtEarnCode, String descr, String leavePlan, String accrualCategory, Date fromEffdt, Date toEffdt, String active, String showHist) { 364 return earnCodeDao.getEarnCodes(earnCode, ovtEarnCode, descr, leavePlan, accrualCategory, fromEffdt, toEffdt, active, showHist); 365 } 366 367 @Override 368 public Map<String, String> getEarnCodesForDisplayWithEffectiveDate(String principalId, Date asOfDate, boolean isLeavePlanningCalendar) { 369 List<EarnCode> earnCodes = this.getEarnCodesForPrincipal(principalId, asOfDate, isLeavePlanningCalendar); 370 371 Date currentDate = TKUtils.getCurrentDate(); 372 boolean futureDate = asOfDate.after(currentDate); 373 List<EarnCode> copyList = new ArrayList<EarnCode>(); 374 copyList.addAll(earnCodes); 375 for (EarnCode earnCode : copyList) { 376 if ( futureDate 377 && !earnCode.getAllowScheduledLeave().equalsIgnoreCase("Y")) { 378 earnCodes.remove(earnCode); 379 } 380 } 381 Comparator<EarnCode> earnCodeComparator = new Comparator<EarnCode>() { 382 @Override 383 public int compare(EarnCode ec1, EarnCode ec2) { 384 return ec1.getEarnCode().compareToIgnoreCase(ec2.getEarnCode()); 385 } 386 }; 387 // Order by leaveCode ascending 388 Ordering<EarnCode> ordering = Ordering.from(earnCodeComparator); 389 390 Map<String, String> earnCodesForDisplay = new LinkedHashMap<String, String>(); 391 for (EarnCode earnCode : ordering.sortedCopy(earnCodes)) { 392 earnCodesForDisplay.put(earnCode.getEarnCodeKeyForDisplay(), earnCode.getEarnCodeValueForDisplay()); 393 } 394 return earnCodesForDisplay; 395 } 396 397 }