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.lm.leavepayout.service; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.apache.commons.lang.time.DateUtils; 020 import org.joda.time.DateTime; 021 import org.joda.time.Interval; 022 import org.kuali.hr.lm.LMConstants; 023 import org.kuali.hr.lm.accrual.AccrualCategory; 024 import org.kuali.hr.lm.accrual.AccrualCategoryRule; 025 import org.kuali.hr.lm.leavepayout.LeavePayout; 026 import org.kuali.hr.lm.employeeoverride.EmployeeOverride; 027 import org.kuali.hr.lm.leaveSummary.LeaveSummary; 028 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow; 029 import org.kuali.hr.lm.leaveblock.LeaveBlock; 030 import org.kuali.hr.lm.leaveblock.LeaveBlockHistory; 031 import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument; 032 import org.kuali.hr.lm.leavepayout.LeavePayout; 033 import org.kuali.hr.lm.leavepayout.dao.LeavePayoutDao; 034 035 import org.kuali.hr.lm.leaveplan.LeavePlan; 036 import org.kuali.hr.time.calendar.CalendarEntries; 037 import org.kuali.hr.time.earncode.EarnCode; 038 import org.kuali.hr.time.principal.PrincipalHRAttributes; 039 import org.kuali.hr.time.service.base.TkServiceLocator; 040 import org.kuali.hr.time.util.TKContext; 041 import org.kuali.hr.time.util.TKUtils; 042 import org.kuali.hr.time.util.TkConstants; 043 import org.kuali.rice.kew.api.exception.WorkflowException; 044 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName; 045 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 046 import org.kuali.rice.krad.maintenance.MaintenanceDocument; 047 import org.kuali.rice.krad.service.KRADServiceLocator; 048 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 049 import org.kuali.rice.krad.util.KRADConstants; 050 import org.kuali.rice.krad.util.ObjectUtils; 051 052 import edu.emory.mathcs.backport.java.util.Collections; 053 054 import java.math.BigDecimal; 055 import java.sql.Date; 056 import java.util.ArrayList; 057 import java.util.Calendar; 058 import java.util.Comparator; 059 import java.util.HashMap; 060 import java.util.HashSet; 061 import java.util.List; 062 import java.util.Map; 063 import java.util.Set; 064 065 public class LeavePayoutServiceImpl implements LeavePayoutService { 066 067 private LeavePayoutDao leavePayoutDao; 068 069 @Override 070 public List<LeavePayout> getAllLeavePayoutsForPrincipalId( 071 String principalId) { 072 return leavePayoutDao.getAllLeavePayoutsForPrincipalId(principalId); 073 } 074 075 @Override 076 public List<LeavePayout> getAllLeavePayoutsForPrincipalIdAsOfDate( 077 String principalId, Date effectiveDate) { 078 return leavePayoutDao.getAllLeavePayoutsForPrincipalIdAsOfDate(principalId,effectiveDate); 079 } 080 081 @Override 082 public List<LeavePayout> getAllLeavePayoutsByEffectiveDate( 083 Date effectiveDate) { 084 return leavePayoutDao.getAllLeavePayoutsByEffectiveDate(effectiveDate); 085 } 086 087 @Override 088 public LeavePayout getLeavePayoutById(String lmLeavePayoutId) { 089 return leavePayoutDao.getLeavePayoutById(lmLeavePayoutId); 090 } 091 092 public LeavePayoutDao getLeavePayoutDao() { 093 return leavePayoutDao; 094 } 095 096 public void setLeavePayoutDao(LeavePayoutDao leavePayoutDao) { 097 this.leavePayoutDao = leavePayoutDao; 098 } 099 100 @Override 101 public LeavePayout initializePayout(String principalId, 102 String accrualCategoryRule, BigDecimal accruedBalance, 103 Date effectiveDate) { 104 //Initially, principals may be allowed to edit the transfer amount when prompted to submit this balance transfer, however, 105 //a base transfer amount together with a forfeited amount is calculated to bring the balance back to its limit in accordance 106 //with transfer limits. 107 LeavePayout leavePayout = null; 108 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRule); 109 110 if(ObjectUtils.isNotNull(accrualRule) && ObjectUtils.isNotNull(accruedBalance)) { 111 leavePayout = new LeavePayout(); 112 //Leave summary is not a requirement, per se, but the information it contains is. 113 //The only thing that is obtained from leave summary is the accrued balance of the leave summary row matching the 114 //passed accrualCategoryRules accrual category. 115 //These two objects are essential to balance transfers triggered when the employee submits their leave calendar for approval. 116 //Neither of these objects should be null, otherwise this method could not have been called. 117 AccrualCategory fromAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId()); 118 BigDecimal fullTimeEngagement = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate); 119 120 // AccrualRule.maxBalance == null -> no balance limit. No balance limit -> no accrual triggered transfer / payout / lose. 121 // execution point should not be here if max balance on accrualRule is null, unless there exists an employee override. 122 BigDecimal maxBalance = accrualRule.getMaxBalance(); 123 BigDecimal adjustedMaxBalance = maxBalance.multiply(fullTimeEngagement).setScale(2); 124 125 BigDecimal maxPayoutAmount = null; 126 BigDecimal adjustedMaxPayoutAmount = null; 127 if(ObjectUtils.isNotNull(accrualRule.getMaxPayoutAmount())) { 128 maxPayoutAmount = new BigDecimal(accrualRule.getMaxPayoutAmount()); 129 adjustedMaxPayoutAmount = maxPayoutAmount.multiply(fullTimeEngagement).setScale(2); 130 } 131 else { 132 // no limit on transfer amount 133 maxPayoutAmount = new BigDecimal(Long.MAX_VALUE); 134 adjustedMaxPayoutAmount = maxPayoutAmount; 135 } 136 137 BigDecimal maxCarryOver = null; 138 BigDecimal adjustedMaxCarryOver = null; 139 if(ObjectUtils.isNotNull(accrualRule.getMaxCarryOver())) { 140 maxCarryOver = new BigDecimal(accrualRule.getMaxCarryOver()); 141 adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2); 142 } 143 else { 144 //no limit to carry over. 145 maxCarryOver = new BigDecimal(Long.MAX_VALUE); 146 adjustedMaxCarryOver = maxCarryOver; 147 } 148 149 EmployeeOverride maxBalanceOverride = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MB", effectiveDate); 150 EmployeeOverride maxPayoutAmountOverride = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MPA", effectiveDate); 151 EmployeeOverride maxAnnualCarryOverOverride = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MAC", effectiveDate); 152 //Do not pro-rate override values for FTE. 153 if(maxBalanceOverride != null) 154 adjustedMaxBalance = new BigDecimal(maxBalanceOverride.getOverrideValue()); 155 if(maxPayoutAmountOverride != null) 156 adjustedMaxPayoutAmount = new BigDecimal(maxPayoutAmountOverride.getOverrideValue()); 157 if(maxAnnualCarryOverOverride != null) 158 adjustedMaxCarryOver = new BigDecimal(maxAnnualCarryOverOverride.getOverrideValue()); 159 160 161 BigDecimal transferAmount = accruedBalance.subtract(adjustedMaxBalance); 162 if(transferAmount.compareTo(adjustedMaxPayoutAmount) > 0) { 163 //there's forfeiture. 164 //bring transfer amount down to the adjusted maximum transfer amount, and place excess in forfeiture. 165 //accruedBalance - adjustedMaxPayoutAmount - adjustedMaxBalance = forfeiture. 166 //transferAmount = accruedBalance - adjustedMaxBalance; forfeiture = transferAmount - adjustedMaxPayoutAmount. 167 BigDecimal forfeiture = transferAmount.subtract(adjustedMaxPayoutAmount).setScale(2); 168 forfeiture = forfeiture.stripTrailingZeros(); 169 leavePayout.setForfeitedAmount(forfeiture); 170 leavePayout.setPayoutAmount(adjustedMaxPayoutAmount); 171 } 172 else { 173 leavePayout.setPayoutAmount(transferAmount); 174 leavePayout.setForfeitedAmount(BigDecimal.ZERO); 175 } 176 177 assert(adjustedMaxBalance.compareTo(accruedBalance.subtract(leavePayout.getPayoutAmount().add(leavePayout.getForfeitedAmount()))) == 0); 178 179 // Max Carry Over logic for Year End transfers. 180 if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) { 181 if(ObjectUtils.isNotNull(maxCarryOver)) { 182 183 if(ObjectUtils.isNull(adjustedMaxCarryOver)) 184 adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2); 185 //otherwise, adjustedMaxCarryOver has an employee override value, which trumps accrual rule defined MAC. 186 //At this point, transfer amount and forfeiture have been set so that the new accrued balance will be the 187 //adjusted max balance, so this amount is used to check against carry over. 188 if(adjustedMaxBalance.compareTo(adjustedMaxCarryOver) > 0) { 189 BigDecimal carryOverDiff = adjustedMaxBalance.subtract(adjustedMaxCarryOver); 190 191 if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)){ 192 //add carry over excess to forfeiture. 193 leavePayout.setForfeitedAmount(leavePayout.getForfeitedAmount().add(carryOverDiff)); 194 } 195 else { 196 //maximize the transfer amount. 197 BigDecimal potentialPayoutAmount = leavePayout.getPayoutAmount().add(carryOverDiff); 198 199 //Can this entire amount be added to the transfer amount?? 200 if(potentialPayoutAmount.compareTo(adjustedMaxPayoutAmount) <= 0) { 201 //yes 202 leavePayout.setPayoutAmount(leavePayout.getPayoutAmount().add(carryOverDiff)); 203 } 204 else { 205 //no 206 BigDecimal carryOverExcess = potentialPayoutAmount.subtract(adjustedMaxPayoutAmount); 207 //move excess to forfeiture 208 leavePayout.setForfeitedAmount(leavePayout.getForfeitedAmount().add(carryOverExcess)); 209 //the remainder (if any) can be added to the transfer amount ( unless action is LOSE ). 210 leavePayout.setPayoutAmount(leavePayout.getPayoutAmount().add(carryOverDiff.subtract(carryOverExcess))); 211 212 assert(adjustedMaxCarryOver.compareTo(accruedBalance.subtract(leavePayout.getPayoutAmount().add(leavePayout.getForfeitedAmount()))) == 0); 213 } 214 } 215 } 216 //otherwise, given balance will be at or under the max annual carry over. 217 } 218 } 219 220 leavePayout.setEffectiveDate(effectiveDate); 221 leavePayout.setAccrualCategoryRule(accrualCategoryRule); 222 leavePayout.setFromAccrualCategory(fromAccrualCategory.getAccrualCategory()); 223 leavePayout.setPrincipalId(principalId); 224 leavePayout.setEarnCode(accrualRule.getMaxPayoutEarnCode()); 225 226 } 227 return leavePayout; 228 } 229 230 @Override 231 public LeavePayout payout(LeavePayout leavePayout) { 232 if(ObjectUtils.isNull(leavePayout)) 233 throw new RuntimeException("did not supply a valid LeavePayout object."); 234 else { 235 List<LeaveBlock> leaveBlocks = new ArrayList<LeaveBlock>(); 236 BigDecimal transferAmount = leavePayout.getPayoutAmount(); 237 LeaveBlock aLeaveBlock = null; 238 239 if(ObjectUtils.isNotNull(transferAmount)) { 240 if(transferAmount.compareTo(BigDecimal.ZERO) > 0) { 241 242 aLeaveBlock = new LeaveBlock(); 243 //Create a leave block that adds the adjusted transfer amount to the "transfer to" accrual category. 244 aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId()); 245 aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate()); 246 aLeaveBlock.setEarnCode(leavePayout.getEarnCode()); 247 aLeaveBlock.setAccrualCategory(leavePayout.getEarnCodeObj().getAccrualCategory()); 248 aLeaveBlock.setDescription("Amount payed out"); 249 aLeaveBlock.setLeaveAmount(leavePayout.getPayoutAmount()); 250 aLeaveBlock.setAccrualGenerated(true); 251 aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId()); 252 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT); 253 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED); 254 aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId()); 255 aLeaveBlock.setBlockId(0L); 256 257 //Want to store the newly created leave block id on this maintainable object 258 //when the status of the maintenance document encapsulating this maintainable changes 259 //the id will be used to fetch and update the leave block statuses. 260 aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock); 261 262 leavePayout.setPayoutLeaveBlockId(aLeaveBlock.getLmLeaveBlockId()); 263 // save history 264 LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock); 265 lbh.setAction(LMConstants.ACTION.ADD); 266 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh); 267 leaveBlocks.add(aLeaveBlock); 268 269 //Create leave block that removes the correct transfer amount from the originating accrual category. 270 aLeaveBlock = new LeaveBlock(); 271 aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId()); 272 aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate()); 273 aLeaveBlock.setEarnCode(leavePayout.getFromAccrualCategoryObj().getEarnCode()); 274 aLeaveBlock.setAccrualCategory(leavePayout.getFromAccrualCategory()); 275 aLeaveBlock.setDescription("Payout amount"); 276 aLeaveBlock.setLeaveAmount(leavePayout.getPayoutAmount().negate()); 277 aLeaveBlock.setAccrualGenerated(true); 278 aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId()); 279 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT); 280 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED); 281 aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId()); 282 aLeaveBlock.setBlockId(0L); 283 284 //Want to store the newly created leave block id on this maintainable object. 285 //when the status of the maintenance document encapsulating this maintainable changes 286 //the id will be used to fetch and update the leave block statuses. 287 aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock); 288 289 leavePayout.setPayoutFromLeaveBlockId(aLeaveBlock.getLmLeaveBlockId()); 290 // save history 291 lbh = new LeaveBlockHistory(aLeaveBlock); 292 lbh.setAction(LMConstants.ACTION.ADD); 293 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh); 294 295 leaveBlocks.add(aLeaveBlock); 296 } 297 } 298 299 BigDecimal forfeitedAmount = leavePayout.getForfeitedAmount(); 300 if(ObjectUtils.isNotNull(forfeitedAmount)) { 301 //Any amount forfeited must come out of the originating accrual category in order to bring balance back to max. 302 if(forfeitedAmount.compareTo(BigDecimal.ZERO) > 0) { 303 //for balance transfers with action = lose, transfer amount must be moved to forfeitedAmount 304 aLeaveBlock = new LeaveBlock(); 305 aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId()); 306 aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate()); 307 aLeaveBlock.setEarnCode(leavePayout.getFromAccrualCategoryObj().getEarnCode()); 308 aLeaveBlock.setAccrualCategory(leavePayout.getFromAccrualCategory()); 309 aLeaveBlock.setDescription("Forfeited payout amount"); 310 aLeaveBlock.setLeaveAmount(forfeitedAmount.negate()); 311 aLeaveBlock.setAccrualGenerated(true); 312 aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId()); 313 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT); 314 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED); 315 aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId()); 316 aLeaveBlock.setBlockId(0L); 317 318 //Want to store the newly created leave block id on this maintainable object 319 //when the status of the maintenance document encapsulating this maintainable changes 320 //the id will be used to fetch and update the leave block statuses. 321 aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock); 322 323 leavePayout.setForfeitedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId()); 324 // save history 325 LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock); 326 lbh.setAction(LMConstants.ACTION.ADD); 327 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh); 328 329 leaveBlocks.add(aLeaveBlock); 330 } 331 } 332 333 return leavePayout; 334 } 335 } 336 337 @Override 338 public void submitToWorkflow(LeavePayout leavePayout) 339 throws WorkflowException { 340 //leavePayout.setStatus(TkConstants.ROUTE_STATUS.ENROUTE); 341 EntityNamePrincipalName principalName = null; 342 if (leavePayout.getPrincipalId() != null) { 343 principalName = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(leavePayout.getPrincipalId()); 344 } 345 346 MaintenanceDocument document = KRADServiceLocatorWeb.getMaintenanceDocumentService().setupNewMaintenanceDocument(LeavePayout.class.getName(), 347 "LeavePayoutDocumentType",KRADConstants.MAINTENANCE_NEW_ACTION); 348 349 String personName = (principalName != null && principalName.getDefaultName() != null) ? principalName.getDefaultName().getCompositeName() : StringUtils.EMPTY; 350 String date = TKUtils.formatDate(new java.sql.Date(leavePayout.getEffectiveDate().getTime())); 351 document.getDocumentHeader().setDocumentDescription(personName + " (" + leavePayout.getPrincipalId() + ") - " + date); 352 Map<String,String[]> params = new HashMap<String,String[]>(); 353 354 KRADServiceLocatorWeb.getMaintenanceDocumentService().setupMaintenanceObject(document, KRADConstants.MAINTENANCE_NEW_ACTION, params); 355 LeavePayout lpObj = (LeavePayout) document.getNewMaintainableObject().getDataObject(); 356 357 lpObj.setAccrualCategoryRule(leavePayout.getAccrualCategoryRule()); 358 lpObj.setEffectiveDate(leavePayout.getEffectiveDate()); 359 lpObj.setLeaveCalendarDocumentId(leavePayout.getLeaveCalendarDocumentId()); 360 lpObj.setForfeitedAmount(leavePayout.getForfeitedAmount()); 361 lpObj.setFromAccrualCategory(leavePayout.getFromAccrualCategory()); 362 lpObj.setPrincipalId(leavePayout.getPrincipalId()); 363 lpObj.setEarnCode(leavePayout.getEarnCode()); 364 lpObj.setPayoutAmount(leavePayout.getPayoutAmount()); 365 lpObj.setDocumentHeaderId(document.getDocumentHeader().getWorkflowDocument().getDocumentId()); 366 367 document.getNewMaintainableObject().setDataObject(lpObj); 368 KRADServiceLocatorWeb.getDocumentService().saveDocument(document); 369 document.getDocumentHeader().getWorkflowDocument().saveDocument(""); 370 371 document.getDocumentHeader().getWorkflowDocument().route(""); 372 } 373 374 @Override 375 public List<LeavePayout> getLeavePayouts(String viewPrincipal, 376 Date beginPeriodDate, Date endPeriodDate) { 377 // TODO Auto-generated method stub 378 return leavePayoutDao.getLeavePayouts(viewPrincipal, beginPeriodDate, endPeriodDate); 379 } 380 381 @Override 382 public void saveOrUpdate(LeavePayout payout) { 383 leavePayoutDao.saveOrUpdate(payout); 384 } 385 }