View Javadoc
1   /**
2    * Copyright 2004-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.kpme.tklm.leave.payout.service;
17  
18  import java.math.BigDecimal;
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.log4j.Logger;
26  import org.joda.time.LocalDate;
27  import org.kuali.kpme.core.api.accrualcategory.AccrualCategory;
28  import org.kuali.kpme.core.api.accrualcategory.rule.AccrualCategoryRule;
29  import org.kuali.kpme.core.service.HrServiceLocator;
30  import org.kuali.kpme.core.util.HrConstants;
31  import org.kuali.kpme.core.util.HrContext;
32  import org.kuali.kpme.core.util.TKUtils;
33  import org.kuali.kpme.tklm.api.leave.block.LeaveBlock;
34  import org.kuali.kpme.tklm.api.leave.override.EmployeeOverrideContract;
35  import org.kuali.kpme.tklm.common.LMConstants;
36  import org.kuali.kpme.tklm.leave.block.LeaveBlockBo;
37  import org.kuali.kpme.tklm.leave.override.EmployeeOverride;
38  import org.kuali.kpme.tklm.leave.payout.LeavePayout;
39  import org.kuali.kpme.tklm.leave.payout.dao.LeavePayoutDao;
40  import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
41  import org.kuali.rice.kew.api.exception.WorkflowException;
42  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
43  import org.kuali.rice.krad.service.KRADServiceLocator;
44  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
45  import org.kuali.rice.krad.util.GlobalVariables;
46  import org.kuali.rice.krad.util.KRADConstants;
47  import org.kuali.rice.krad.util.ObjectUtils;
48  
49  public class LeavePayoutServiceImpl implements LeavePayoutService {
50  
51      private LeavePayoutDao leavePayoutDao;
52      private static final Logger LOG = Logger.getLogger(LeavePayoutServiceImpl.class);
53  
54      @Override
55      public List<LeavePayout> getAllLeavePayoutsForPrincipalId(
56              String principalId) {
57          return leavePayoutDao.getAllLeavePayoutsForPrincipalId(principalId);
58      }
59  
60      @Override
61      public List<LeavePayout> getAllLeavePayoutsForPrincipalIdAsOfDate(
62              String principalId, LocalDate effectiveDate) {
63          return leavePayoutDao.getAllLeavePayoutsForPrincipalIdAsOfDate(principalId,effectiveDate);
64      }
65  
66      @Override
67      public List<LeavePayout> getAllLeavePayoutsByEffectiveDate(
68      		LocalDate effectiveDate) {
69          return leavePayoutDao.getAllLeavePayoutsByEffectiveDate(effectiveDate);
70      }
71  
72      @Override
73      public LeavePayout getLeavePayoutById(String lmLeavePayoutId) {
74          return leavePayoutDao.getLeavePayoutById(lmLeavePayoutId);
75      }
76  
77      public LeavePayoutDao getLeavePayoutDao() {
78          return leavePayoutDao;
79      }
80  
81      public void setLeavePayoutDao(LeavePayoutDao leavePayoutDao) {
82          this.leavePayoutDao = leavePayoutDao;
83      }
84  
85  	@Override
86  	public LeavePayout initializePayout(String principalId,
87  			String accrualCategoryRule, BigDecimal accruedBalance,
88  			LocalDate effectiveDate) {
89  		//Initially, principals may be allowed to edit the transfer amount when prompted to submit this balance transfer, however,
90  		//a base transfer amount together with a forfeited amount is calculated to bring the balance back to its limit in accordance
91  		//with transfer limits.
92  		LeavePayout leavePayout = null;
93  		AccrualCategoryRule accrualRule = HrServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRule);
94  
95  		if(ObjectUtils.isNotNull(accrualRule) && ObjectUtils.isNotNull(accruedBalance)) {
96  			leavePayout = new LeavePayout();
97  			//Leave summary is not a requirement, per se, but the information it contains is.
98  			//The only thing that is obtained from leave summary is the accrued balance of the leave summary row matching the
99  			//passed accrualCategoryRules accrual category.
100 			//These two objects are essential to balance transfers triggered when the employee submits their leave calendar for approval.
101 			//Neither of these objects should be null, otherwise this method could not have been called.
102 			AccrualCategory fromAccrualCategory = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
103 			BigDecimal fullTimeEngagement = HrServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate);
104 			
105 			// AccrualRule.maxBalance == null -> no balance limit. No balance limit -> no accrual triggered transfer / payout / lose.
106 			// execution point should not be here if max balance on accrualRule is null, unless there exists an employee override.
107 			BigDecimal maxBalance = accrualRule.getMaxBalance();
108 			BigDecimal adjustedMaxBalance = maxBalance.multiply(fullTimeEngagement).setScale(2);
109 			
110 			BigDecimal maxPayoutAmount = null;
111 			BigDecimal adjustedMaxPayoutAmount = null;
112 			if(ObjectUtils.isNotNull(accrualRule.getMaxPayoutAmount())) {
113 				maxPayoutAmount = new BigDecimal(accrualRule.getMaxPayoutAmount());
114 				adjustedMaxPayoutAmount = maxPayoutAmount.multiply(fullTimeEngagement).setScale(2);
115 			}
116 			else {
117 				// no limit on transfer amount
118 				maxPayoutAmount = new BigDecimal(Long.MAX_VALUE);
119 				adjustedMaxPayoutAmount = maxPayoutAmount;
120 			}
121 
122 			BigDecimal maxCarryOver = null;
123 			BigDecimal adjustedMaxCarryOver = null;
124 			if(ObjectUtils.isNotNull(accrualRule.getMaxCarryOver())) {
125 				maxCarryOver = new BigDecimal(accrualRule.getMaxCarryOver());
126 				adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
127 			}
128 			else {
129 				//no limit to carry over.
130 				maxCarryOver = new BigDecimal(Long.MAX_VALUE);
131 				adjustedMaxCarryOver = maxCarryOver;
132 			}
133 
134             EmployeeOverrideContract maxBalanceOverride = LmServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MB", effectiveDate);
135             EmployeeOverrideContract maxPayoutAmountOverride = LmServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MPA", effectiveDate);
136             EmployeeOverrideContract maxAnnualCarryOverOverride = LmServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MAC", effectiveDate);
137 			//Do not pro-rate override values for FTE.
138 			if(maxBalanceOverride != null)
139 				adjustedMaxBalance = new BigDecimal(maxBalanceOverride.getOverrideValue());
140 			if(maxPayoutAmountOverride != null)
141 				adjustedMaxPayoutAmount = new BigDecimal(maxPayoutAmountOverride.getOverrideValue());
142 			if(maxAnnualCarryOverOverride != null)
143 				adjustedMaxCarryOver = new BigDecimal(maxAnnualCarryOverOverride.getOverrideValue());
144 			
145 			
146 			BigDecimal transferAmount = accruedBalance.subtract(adjustedMaxBalance);
147 			if(transferAmount.compareTo(adjustedMaxPayoutAmount) > 0) {
148 				//there's forfeiture.
149 				//bring transfer amount down to the adjusted maximum transfer amount, and place excess in forfeiture.
150 				//accruedBalance - adjustedMaxPayoutAmount - adjustedMaxBalance = forfeiture.
151 				//transferAmount = accruedBalance - adjustedMaxBalance; forfeiture = transferAmount - adjustedMaxPayoutAmount.
152 				BigDecimal forfeiture = transferAmount.subtract(adjustedMaxPayoutAmount).setScale(2);
153 				forfeiture = forfeiture.stripTrailingZeros();
154 				leavePayout.setForfeitedAmount(forfeiture);
155 				leavePayout.setPayoutAmount(adjustedMaxPayoutAmount);
156 			}
157 			else {
158 				leavePayout.setPayoutAmount(transferAmount);
159 				leavePayout.setForfeitedAmount(BigDecimal.ZERO);
160 			}
161 				
162 			assert(adjustedMaxBalance.compareTo(accruedBalance.subtract(leavePayout.getPayoutAmount().add(leavePayout.getForfeitedAmount()))) == 0);
163 			
164 			// Max Carry Over logic for Year End transfers.
165 			if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
166 				if(ObjectUtils.isNotNull(maxCarryOver)) {
167 					
168 					if(ObjectUtils.isNull(adjustedMaxCarryOver))
169 						adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
170 					//otherwise, adjustedMaxCarryOver has an employee override value, which trumps accrual rule defined MAC.
171 					//At this point, transfer amount and forfeiture have been set so that the new accrued balance will be the
172 					//adjusted max balance, so this amount is used to check against carry over.
173 					if(adjustedMaxBalance.compareTo(adjustedMaxCarryOver) > 0) {
174 						BigDecimal carryOverDiff = adjustedMaxBalance.subtract(adjustedMaxCarryOver);
175 						
176 						if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),HrConstants.ACTION_AT_MAX_BALANCE.LOSE)){
177 							//add carry over excess to forfeiture.
178 							leavePayout.setForfeitedAmount(leavePayout.getForfeitedAmount().add(carryOverDiff));
179 						}
180 						else {
181 							//maximize the transfer amount.
182 							BigDecimal potentialPayoutAmount = leavePayout.getPayoutAmount().add(carryOverDiff);
183 	
184 							//Can this entire amount be added to the transfer amount??
185 							if(potentialPayoutAmount.compareTo(adjustedMaxPayoutAmount) <= 0) {
186 								//yes
187 								leavePayout.setPayoutAmount(leavePayout.getPayoutAmount().add(carryOverDiff));
188 							}
189 							else {
190 								//no
191 								BigDecimal carryOverExcess = potentialPayoutAmount.subtract(adjustedMaxPayoutAmount);
192 								//move excess to forfeiture
193 								leavePayout.setForfeitedAmount(leavePayout.getForfeitedAmount().add(carryOverExcess));
194 								//the remainder (if any) can be added to the transfer amount ( unless action is LOSE ).
195 								leavePayout.setPayoutAmount(leavePayout.getPayoutAmount().add(carryOverDiff.subtract(carryOverExcess)));
196 								
197 								assert(adjustedMaxCarryOver.compareTo(accruedBalance.subtract(leavePayout.getPayoutAmount().add(leavePayout.getForfeitedAmount()))) == 0);
198 							}
199 						}
200 					}
201 					//otherwise, given balance will be at or under the max annual carry over.
202 				}
203 			}
204 			
205 			leavePayout.setEffectiveLocalDate(effectiveDate);
206 			leavePayout.setAccrualCategoryRule(accrualCategoryRule);
207 			leavePayout.setFromAccrualCategory(fromAccrualCategory.getAccrualCategory());
208 			leavePayout.setPrincipalId(principalId);
209             leavePayout.setUserPrincipalId(HrContext.getPrincipalId());
210 			leavePayout.setEarnCode(accrualRule.getMaxPayoutEarnCode());
211 
212 		}
213 		return leavePayout;
214 	}
215 
216 	@Override
217 	public LeavePayout payout(LeavePayout leavePayout) {
218 		if(ObjectUtils.isNull(leavePayout)) {
219 //			throw new RuntimeException("did not supply a valid LeavePayout object.");
220 			LOG.error("did not supply a valid LeavePayout object.");
221 			return null;
222 		} else {
223 			List<LeaveBlockBo> leaveBlocks = new ArrayList<LeaveBlockBo>();
224 			BigDecimal transferAmount = leavePayout.getPayoutAmount();
225 			LeaveBlockBo aLeaveBlock = null;
226 			
227 			if(ObjectUtils.isNotNull(transferAmount)) {
228 				if(transferAmount.compareTo(BigDecimal.ZERO) > 0) {
229 		
230 					aLeaveBlock = new LeaveBlockBo();
231 					//Create a leave block that adds the adjusted transfer amount to the "transfer to" accrual category.
232 					aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
233 					aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
234 					aLeaveBlock.setEarnCode(leavePayout.getEarnCode());
235 					aLeaveBlock.setAccrualCategory(leavePayout.getEarnCodeObj().getAccrualCategory());
236 					aLeaveBlock.setDescription("Amount payed out");
237 					aLeaveBlock.setLeaveAmount(leavePayout.getPayoutAmount());
238 					aLeaveBlock.setAccrualGenerated(true);
239 					aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
240 					aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
241 					aLeaveBlock.setRequestStatus(HrConstants.REQUEST_STATUS.REQUESTED);
242 					aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
243 					aLeaveBlock.setBlockId(0L);
244 
245 					//Want to store the newly created leave block id on this maintainable object
246 					//when the status of the maintenance document encapsulating this maintainable changes
247 					//the id will be used to fetch and update the leave block statuses.
248 					LeaveBlock lb = LmServiceLocator.getLeaveBlockService().saveLeaveBlock(LeaveBlockBo.to(aLeaveBlock), GlobalVariables.getUserSession().getPrincipalId());
249 
250 			    	leavePayout.setPayoutLeaveBlockId(lb.getLmLeaveBlockId());
251 					
252 					//Create leave block that removes the correct transfer amount from the originating accrual category.
253 					aLeaveBlock = new LeaveBlockBo();
254 					aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
255 					aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
256 					aLeaveBlock.setEarnCode(leavePayout.getFromAccrualCategoryObj().getEarnCode());
257 					aLeaveBlock.setAccrualCategory(leavePayout.getFromAccrualCategory());
258 					aLeaveBlock.setDescription("Payout amount");
259 					aLeaveBlock.setLeaveAmount(leavePayout.getPayoutAmount().negate());
260 					aLeaveBlock.setAccrualGenerated(true);
261 					aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
262 					aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
263 					aLeaveBlock.setRequestStatus(HrConstants.REQUEST_STATUS.REQUESTED);
264 					aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
265 					aLeaveBlock.setBlockId(0L);
266 					
267 					//Want to store the newly created leave block id on this maintainable object.
268 					//when the status of the maintenance document encapsulating this maintainable changes
269 					//the id will be used to fetch and update the leave block statuses.
270 					lb = LmServiceLocator.getLeaveBlockService().saveLeaveBlock(LeaveBlockBo.to(aLeaveBlock), GlobalVariables.getUserSession().getPrincipalId());
271 
272 			    	leavePayout.setPayoutFromLeaveBlockId(lb.getLmLeaveBlockId());
273 				}
274 			}
275 			
276 			BigDecimal forfeitedAmount = leavePayout.getForfeitedAmount();
277 			if(ObjectUtils.isNotNull(forfeitedAmount)) {
278 				//Any amount forfeited must come out of the originating accrual category in order to bring balance back to max.
279 				if(forfeitedAmount.compareTo(BigDecimal.ZERO) > 0) {
280 					//for balance transfers with action = lose, transfer amount must be moved to forfeitedAmount
281 					aLeaveBlock = new LeaveBlockBo();
282 					aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
283 					aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
284 					aLeaveBlock.setEarnCode(leavePayout.getFromAccrualCategoryObj().getEarnCode());
285 					aLeaveBlock.setAccrualCategory(leavePayout.getFromAccrualCategory());
286 					aLeaveBlock.setDescription(LMConstants.PAYOUT_FORFEIT_LB_DESCRIPTION);
287 					aLeaveBlock.setLeaveAmount(forfeitedAmount.negate());
288 					aLeaveBlock.setAccrualGenerated(true);
289 					aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
290 					aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
291 					aLeaveBlock.setRequestStatus(HrConstants.REQUEST_STATUS.REQUESTED);
292 					aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
293 					aLeaveBlock.setBlockId(0L);
294 					
295 					//Want to store the newly created leave block id on this maintainable object
296 					//when the status of the maintenance document encapsulating this maintainable changes
297 					//the id will be used to fetch and update the leave block statuses.
298 					LeaveBlock lb = LmServiceLocator.getLeaveBlockService().saveLeaveBlock(LeaveBlockBo.to(aLeaveBlock), GlobalVariables.getUserSession().getPrincipalId());
299 
300 			    	leavePayout.setForfeitedLeaveBlockId(lb.getLmLeaveBlockId());
301 				}
302 			}
303 			return leavePayout;
304 		}
305 	}
306 
307 	@Override
308 	public void submitToWorkflow(LeavePayout leavePayout)
309 			throws WorkflowException {
310 		
311 		//leavePayout.setStatus(HrConstants.ROUTE_STATUS.ENROUTE);
312 
313 	/*MaintenanceDocument document = KRADServiceLocatorWeb.getMaintenanceDocumentService().setupNewMaintenanceDocument(LeavePayout.class.getName(),
314 				"LeavePayoutDocumentType",KRADConstants.MAINTENANCE_NEW_ACTION);*/
315         MaintenanceDocument document =  (MaintenanceDocument) KRADServiceLocatorWeb.getDocumentService().getNewDocument("LeavePayoutDocumentType");
316         document.getDocumentHeader().setDocumentDescription(TKUtils.getDocumentDescription(leavePayout.getPrincipalId(), leavePayout.getEffectiveLocalDate()));
317 		Map<String,String[]> params = new HashMap<String,String[]>();
318 		
319 		KRADServiceLocatorWeb.getMaintenanceDocumentService().setupMaintenanceObject(document, KRADConstants.MAINTENANCE_NEW_ACTION, params);
320 		
321 		LeavePayout lpObj = (LeavePayout) document.getNewMaintainableObject().getDataObject();
322 		
323 		lpObj.setAccrualCategoryRule(leavePayout.getAccrualCategoryRule());
324 		lpObj.setEffectiveDate(leavePayout.getEffectiveDate());
325 		lpObj.setLeaveCalendarDocumentId(leavePayout.getLeaveCalendarDocumentId());
326 		lpObj.setForfeitedAmount(leavePayout.getForfeitedAmount());
327 		lpObj.setFromAccrualCategory(leavePayout.getFromAccrualCategory());
328 		lpObj.setPrincipalId(leavePayout.getPrincipalId());
329 		lpObj.setEarnCode(leavePayout.getEarnCode());
330 		lpObj.setPayoutAmount(leavePayout.getPayoutAmount());
331 		lpObj.setDocumentHeaderId(document.getDocumentHeader().getWorkflowDocument().getDocumentId());
332 		
333 		//document.getNewMaintainableObject().setDataObject(lpObj);
334 		KRADServiceLocatorWeb.getDocumentService().saveDocument(document);
335 		document.getDocumentHeader().getWorkflowDocument().saveDocument("");
336 
337 		document.getDocumentHeader().getWorkflowDocument().route("");
338 	}
339 
340 	@Override
341 	public List<LeavePayout> getLeavePayouts(String viewPrincipal,
342 			LocalDate beginPeriodDate, LocalDate endPeriodDate) {
343 		return leavePayoutDao.getLeavePayouts(viewPrincipal, beginPeriodDate, endPeriodDate);
344 	}
345 
346 	@Override
347 	public void saveOrUpdate(LeavePayout payout) {
348         KRADServiceLocator.getBusinessObjectService().save(payout);
349 	}
350 
351 	@Override
352 	public List<LeavePayout> getLeavePayouts(String principalId, String fromAccrualCategory, String payoutAmount, String earnCode, String forfeitedAmount, LocalDate fromEffdt, LocalDate toEffdt) {
353 		return leavePayoutDao.getLeavePayouts(principalId, fromAccrualCategory, payoutAmount, earnCode, forfeitedAmount, fromEffdt, toEffdt);
354 	}
355 }