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