View Javadoc

1   /**
2    * Copyright 2004-2013 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.hr.lm.leavepayout.service;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.lang.time.DateUtils;
20  import org.joda.time.DateTime;
21  import org.joda.time.Interval;
22  import org.kuali.hr.lm.LMConstants;
23  import org.kuali.hr.lm.accrual.AccrualCategory;
24  import org.kuali.hr.lm.accrual.AccrualCategoryRule;
25  import org.kuali.hr.lm.leavepayout.LeavePayout;
26  import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
27  import org.kuali.hr.lm.leaveSummary.LeaveSummary;
28  import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
29  import org.kuali.hr.lm.leaveblock.LeaveBlock;
30  import org.kuali.hr.lm.leaveblock.LeaveBlockHistory;
31  import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
32  import org.kuali.hr.lm.leavepayout.LeavePayout;
33  import org.kuali.hr.lm.leavepayout.dao.LeavePayoutDao;
34  
35  import org.kuali.hr.lm.leaveplan.LeavePlan;
36  import org.kuali.hr.time.calendar.CalendarEntries;
37  import org.kuali.hr.time.earncode.EarnCode;
38  import org.kuali.hr.time.principal.PrincipalHRAttributes;
39  import org.kuali.hr.time.service.base.TkServiceLocator;
40  import org.kuali.hr.time.util.TKContext;
41  import org.kuali.hr.time.util.TKUtils;
42  import org.kuali.hr.time.util.TkConstants;
43  import org.kuali.rice.kew.api.exception.WorkflowException;
44  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
45  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
46  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
47  import org.kuali.rice.krad.service.KRADServiceLocator;
48  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
49  import org.kuali.rice.krad.util.KRADConstants;
50  import org.kuali.rice.krad.util.ObjectUtils;
51  
52  import edu.emory.mathcs.backport.java.util.Collections;
53  
54  import java.math.BigDecimal;
55  import java.sql.Date;
56  import java.util.ArrayList;
57  import java.util.Calendar;
58  import java.util.Comparator;
59  import java.util.HashMap;
60  import java.util.HashSet;
61  import java.util.List;
62  import java.util.Map;
63  import java.util.Set;
64  
65  public class LeavePayoutServiceImpl implements LeavePayoutService {
66  
67      private LeavePayoutDao leavePayoutDao;
68  
69      @Override
70      public List<LeavePayout> getAllLeavePayoutsForPrincipalId(
71              String principalId) {
72          return leavePayoutDao.getAllLeavePayoutsForPrincipalId(principalId);
73      }
74  
75      @Override
76      public List<LeavePayout> getAllLeavePayoutsForPrincipalIdAsOfDate(
77              String principalId, Date effectiveDate) {
78          return leavePayoutDao.getAllLeavePayoutsForPrincipalIdAsOfDate(principalId,effectiveDate);
79      }
80  
81      @Override
82      public List<LeavePayout> getAllLeavePayoutsByEffectiveDate(
83              Date effectiveDate) {
84          return leavePayoutDao.getAllLeavePayoutsByEffectiveDate(effectiveDate);
85      }
86  
87      @Override
88      public LeavePayout getLeavePayoutById(String lmLeavePayoutId) {
89          return leavePayoutDao.getLeavePayoutById(lmLeavePayoutId);
90      }
91  
92      public LeavePayoutDao getLeavePayoutDao() {
93          return leavePayoutDao;
94      }
95  
96      public void setLeavePayoutDao(LeavePayoutDao leavePayoutDao) {
97          this.leavePayoutDao = leavePayoutDao;
98      }
99  
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 }