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.balancetransfer.service;
17  
18  import java.math.BigDecimal;
19  import java.sql.Date;
20  import java.util.ArrayList;
21  import java.util.Calendar;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.collections.CollectionUtils;
27  import org.apache.commons.lang.StringUtils;
28  import org.apache.commons.lang.time.DateUtils;
29  import org.joda.time.Interval;
30  import org.kuali.hr.lm.LMConstants;
31  import org.kuali.hr.lm.accrual.AccrualCategory;
32  import org.kuali.hr.lm.accrual.AccrualCategoryRule;
33  import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
34  import org.kuali.hr.lm.balancetransfer.dao.BalanceTransferDao;
35  import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
36  import org.kuali.hr.lm.leaveSummary.LeaveSummary;
37  import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
38  import org.kuali.hr.lm.leaveblock.LeaveBlock;
39  import org.kuali.hr.lm.leaveblock.LeaveBlockHistory;
40  import org.kuali.hr.lm.leaveplan.LeavePlan;
41  import org.kuali.hr.time.calendar.CalendarEntries;
42  import org.kuali.hr.time.principal.PrincipalHRAttributes;
43  import org.kuali.hr.time.service.base.TkServiceLocator;
44  import org.kuali.hr.time.util.TKUtils;
45  import org.kuali.hr.time.util.TkConstants;
46  import org.kuali.rice.kew.api.exception.WorkflowException;
47  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
48  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
49  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
50  import org.kuali.rice.krad.service.KRADServiceLocator;
51  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
52  import org.kuali.rice.krad.util.KRADConstants;
53  import org.kuali.rice.krad.util.ObjectUtils;
54  
55  public class BalanceTransferServiceImpl implements BalanceTransferService {
56  
57  	private BalanceTransferDao balanceTransferDao;
58  	
59  	@Override
60  	public List<BalanceTransfer> getAllBalanceTransfersForPrincipalId(
61  			String principalId) {
62  		return balanceTransferDao.getAllBalanceTransfersForPrincipalId(principalId);
63  	}
64  
65  	@Override
66  	public List<BalanceTransfer> getAllBalanceTransferForPrincipalIdAsOfDate(
67  			String principalId, Date effectiveDate) {
68  		return balanceTransferDao.getAllBalanceTransferForPrincipalIdAsOfDate(principalId,effectiveDate);
69  	}
70  
71  	@Override
72  	public List<BalanceTransfer> getAllBalanceTransferByEffectiveDate(
73  			Date effectiveDate) {
74  		return balanceTransferDao.getAllBalanceTransferByEffectiveDate(effectiveDate);
75  	}
76  
77  	@Override
78  	public BalanceTransfer getBalanceTransferById(String balanceTransferId) {
79  		return balanceTransferDao.getBalanceTransferById(balanceTransferId);
80  	}
81  	
82  	@Override
83  	public BalanceTransfer initializeTransfer(String principalId, String accrualCategoryRule, BigDecimal accruedBalance, Date effectiveDate) {
84  		//Initially, principals may be allowed to edit the transfer amount when prompted to submit this balance transfer, however,
85  		//a base transfer amount together with a forfeited amount is calculated to bring the balance back to its limit in accordance
86  		//with transfer limits.
87  		BalanceTransfer bt = null;
88  		AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRule);
89  
90  		if(ObjectUtils.isNotNull(accrualRule) && ObjectUtils.isNotNull(accruedBalance)) {
91  			bt = new BalanceTransfer();
92  			//Leave summary is not a requirement, per se, but the information it contains is.
93  			//The only thing that is obtained from leave summary is the accrued balance of the leave summary row matching the
94  			//passed accrualCategoryRules accrual category.
95  			//These two objects are essential to balance transfers triggered when the employee submits their leave calendar for approval.
96  			//Neither of these objects should be null, otherwise this method could not have been called.
97  			AccrualCategory fromAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
98  			AccrualCategory toAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getMaxBalanceTransferToAccrualCategory(),effectiveDate);
99  			BigDecimal fullTimeEngagement = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate);
100 			
101 			BigDecimal transferConversionFactor = null;
102 			if(ObjectUtils.isNotNull(accrualRule.getMaxBalanceTransferConversionFactor()))
103 				transferConversionFactor = accrualRule.getMaxBalanceTransferConversionFactor();
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 maxTransferAmount = null;
111 			BigDecimal adjustedMaxTransferAmount = null;
112 			if(ObjectUtils.isNotNull(accrualRule.getMaxTransferAmount())) {
113 				maxTransferAmount = new BigDecimal(accrualRule.getMaxTransferAmount());
114 				adjustedMaxTransferAmount = maxTransferAmount.multiply(fullTimeEngagement).setScale(2);
115 			}
116 			else {
117 				// no limit on transfer amount
118 				maxTransferAmount = new BigDecimal(Long.MAX_VALUE);
119 				adjustedMaxTransferAmount = maxTransferAmount;
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 			List<EmployeeOverride> overrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId, effectiveDate);
135 			for(EmployeeOverride override : overrides) {
136 				if(StringUtils.equals(override.getAccrualCategory(),fromAccrualCategory.getAccrualCategory())) {
137 					if(StringUtils.equals(override.getOverrideType(),"MB"))
138 						adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
139 					//override values are not pro-rated for FTE.
140 					if(StringUtils.equals(override.getOverrideType(),"MTA"))
141 						adjustedMaxTransferAmount = new BigDecimal(override.getOverrideValue());
142 					if(StringUtils.equals(override.getOverrideType(),"MAC"))
143 						adjustedMaxCarryOver = new BigDecimal(override.getOverrideValue());
144 				}
145 			}
146 			
147 			
148 			BigDecimal transferAmount = accruedBalance.subtract(adjustedMaxBalance);
149 			if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
150 				//Move all time in excess of employee's fte adjusted max balance to forfeiture.
151 				bt.setForfeitedAmount(transferAmount);
152 				//There is no transfer to another accrual category.
153 				bt.setTransferAmount(BigDecimal.ZERO);
154 				bt.setAmountTransferred(BigDecimal.ZERO);
155 				// to accrual category is a required field on maintenance document. Set as from accrual category
156 				// to remove validation errors when routing, approving, etc.
157 				bt.setToAccrualCategory(fromAccrualCategory.getAccrualCategory());
158 			}
159 			else {
160 				// ACTION_AT_MAX_BAL = TRANSFER
161 				bt.setToAccrualCategory(toAccrualCategory.getAccrualCategory());
162 				if(transferAmount.compareTo(adjustedMaxTransferAmount) > 0) {
163 					//there's forfeiture.
164 					//bring transfer amount down to the adjusted maximum transfer amount, and place excess in forfeiture.
165 					//accruedBalance - adjustedMaxTransferAmount - adjustedMaxBalance = forfeiture.
166 					//transferAmount = accruedBalance - adjustedMaxBalance; forfeiture = transferAmount - adjustedMaxTransferAmount.
167 					BigDecimal forfeiture = transferAmount.subtract(adjustedMaxTransferAmount).setScale(2);
168 					forfeiture = forfeiture.stripTrailingZeros();
169 					bt.setForfeitedAmount(forfeiture);
170 					bt.setTransferAmount(adjustedMaxTransferAmount);
171 				}
172 				else {
173 					bt.setTransferAmount(transferAmount);
174 					bt.setForfeitedAmount(BigDecimal.ZERO);
175 				}
176 			}
177 			
178 			// Max Carry Over logic for Year End transfers.
179 			if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
180 				if(ObjectUtils.isNotNull(maxCarryOver)) {
181 					if(ObjectUtils.isNull(adjustedMaxCarryOver))
182 						adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
183 					//otherwise, adjustedMaxCarryOver has an employee override value, which trumps accrual rule defined MAC.
184 					//At this point, transfer amount and forfeiture have been set so that the new accrued balance will be the
185 					//adjusted max balance, so this amount is used to check against carry over.
186 					if(adjustedMaxBalance.compareTo(adjustedMaxCarryOver) > 0) {
187 						BigDecimal carryOverDiff = adjustedMaxBalance.subtract(adjustedMaxCarryOver);
188 						
189 						if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)){
190 							//add carry over excess to forfeiture.
191 							bt.setForfeitedAmount(bt.getForfeitedAmount().add(carryOverDiff));
192 						}
193 						else {
194 							//maximize the transfer amount.
195 							BigDecimal potentialTransferAmount = bt.getTransferAmount().add(carryOverDiff);
196 	
197 							//Can this amount be added to the transfer amount??
198 							if(potentialTransferAmount.compareTo(adjustedMaxTransferAmount) <= 0) {
199 								//yes
200 								bt.setTransferAmount(bt.getTransferAmount().add(carryOverDiff));
201 							}
202 							else {
203 								//no
204 								BigDecimal carryOverExcess = potentialTransferAmount.subtract(adjustedMaxTransferAmount);
205 								//move excess to forfeiture
206 								bt.setForfeitedAmount(bt.getForfeitedAmount().add(carryOverExcess));
207 								//the remainder (if any) can be added to the transfer amount ( unless action is LOSE ).
208 								bt.setTransferAmount(bt.getTransferAmount().add(carryOverDiff.subtract(carryOverExcess)));
209 								assert(bt.getTransferAmount().compareTo(adjustedMaxTransferAmount)==0);
210 							}
211 						}
212 					}
213 					//otherwise, given balance will be at or under the max annual carry over.
214 				}
215 			}
216 			
217 			bt.setEffectiveDate(effectiveDate);
218 			bt.setAccrualCategoryRule(accrualCategoryRule);
219 			bt.setFromAccrualCategory(fromAccrualCategory.getAccrualCategory());
220 			bt.setPrincipalId(principalId);
221 			if(ObjectUtils.isNotNull(transferConversionFactor))
222 				bt.setAmountTransferred(bt.getTransferAmount().multiply(transferConversionFactor).setScale(2));
223 			else
224 				bt.setAmountTransferred(bt.getTransferAmount());
225 		}
226 		return bt;
227 	}
228 
229 	@Override
230 	public BalanceTransfer transfer(BalanceTransfer balanceTransfer) {
231 		if(ObjectUtils.isNull(balanceTransfer))
232 			throw new RuntimeException("did not supply a valid BalanceTransfer object.");
233 		else {
234 			List<LeaveBlock> leaveBlocks = new ArrayList<LeaveBlock>();
235 			BigDecimal transferAmount = balanceTransfer.getTransferAmount();
236 			LeaveBlock aLeaveBlock = null;
237 
238 			if(ObjectUtils.isNotNull(balanceTransfer.getAmountTransferred())) {
239 				if(balanceTransfer.getAmountTransferred().compareTo(BigDecimal.ZERO) > 0 ) {
240 
241 					aLeaveBlock = new LeaveBlock();
242 					//Create a leave block that adds the adjusted transfer amount to the "transfer to" accrual category.
243 					aLeaveBlock.setPrincipalId(balanceTransfer.getPrincipalId());
244 					aLeaveBlock.setLeaveDate(balanceTransfer.getEffectiveDate());
245 					aLeaveBlock.setEarnCode(balanceTransfer.getCreditedAccrualCategory().getEarnCode());
246 					aLeaveBlock.setAccrualCategory(balanceTransfer.getToAccrualCategory());
247 					aLeaveBlock.setDescription("Amount transferred");
248 					aLeaveBlock.setLeaveAmount(balanceTransfer.getAmountTransferred());
249 					aLeaveBlock.setAccrualGenerated(true);
250 					aLeaveBlock.setTransactionDocId(balanceTransfer.getDocumentHeaderId());
251 					aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
252 					aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
253 					aLeaveBlock.setBlockId(0L);
254 
255 					//Want to store the newly created leave block id on this maintainable object
256 					//when the status of the maintenance document encapsulating this maintainable changes
257 					//the id will be used to fetch and update the leave block statuses.
258 					aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
259 
260 					balanceTransfer.setAccruedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
261 					// save history
262 					LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock);
263 					lbh.setAction(LMConstants.ACTION.ADD);
264 					TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
265 					leaveBlocks.add(aLeaveBlock);
266 				}
267 			}
268 
269 			if(ObjectUtils.isNotNull(transferAmount)) {
270 				if(transferAmount.compareTo(BigDecimal.ZERO) > 0) {					
271 					//Create leave block that removes the correct transfer amount from the originating accrual category.
272 					aLeaveBlock = new LeaveBlock();
273 					aLeaveBlock.setPrincipalId(balanceTransfer.getPrincipalId());
274 					aLeaveBlock.setLeaveDate(balanceTransfer.getEffectiveDate());
275 					aLeaveBlock.setEarnCode(balanceTransfer.getDebitedAccrualCategory().getEarnCode());
276 					aLeaveBlock.setAccrualCategory(balanceTransfer.getFromAccrualCategory());
277 					aLeaveBlock.setDescription("Transferred amount");
278 					aLeaveBlock.setLeaveAmount(balanceTransfer.getTransferAmount().negate());
279 					aLeaveBlock.setAccrualGenerated(true);
280 					aLeaveBlock.setTransactionDocId(balanceTransfer.getDocumentHeaderId());
281 					aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
282 					aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
283 					aLeaveBlock.setBlockId(0L);
284 
285 					//Want to store the newly created leave block id on this maintainable object.
286 					//when the status of the maintenance document encapsulating this maintainable changes
287 					//the id will be used to fetch and update the leave block statuses.
288 					aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
289 
290 					balanceTransfer.setDebitedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
291 					// save history
292 					LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock);
293 					lbh.setAction(LMConstants.ACTION.ADD);
294 					TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
295 
296 					leaveBlocks.add(aLeaveBlock);
297 				}
298 			}
299 
300 			BigDecimal forfeitedAmount = balanceTransfer.getForfeitedAmount();
301 			if(ObjectUtils.isNotNull(forfeitedAmount)) {
302 				//Any amount forfeited must come out of the originating accrual category in order to bring balance back to max.
303 				if(forfeitedAmount.compareTo(BigDecimal.ZERO) > 0) {
304 					//for balance transfers with action = lose, transfer amount must be moved to forfeitedAmount
305 					aLeaveBlock = new LeaveBlock();
306 					aLeaveBlock.setPrincipalId(balanceTransfer.getPrincipalId());
307 					aLeaveBlock.setLeaveDate(balanceTransfer.getEffectiveDate());
308 					aLeaveBlock.setEarnCode(balanceTransfer.getDebitedAccrualCategory().getEarnCode());
309 					aLeaveBlock.setAccrualCategory(balanceTransfer.getFromAccrualCategory());
310 					aLeaveBlock.setDescription("Forfeited balance transfer amount");
311 					aLeaveBlock.setLeaveAmount(forfeitedAmount.negate());
312 					aLeaveBlock.setAccrualGenerated(true);
313 					aLeaveBlock.setTransactionDocId(balanceTransfer.getDocumentHeaderId());
314 					aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
315 					aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
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 					balanceTransfer.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 balanceTransfer;
334 		}
335 	}
336 	
337 	//getMaxBalanceViolations...
338 	@Override
339 	public Map<String,ArrayList<String>> getEligibleTransfers(CalendarEntries calendarEntry, String principalId) throws Exception {
340 		//Employee override check here, or return base-eligible accrual categories,
341 		//filtering out those that have increased balance limits due to employee override in caller?
342 		//null check inserted to fix LeaveCalendarWebTest failures on kpme-trunk-build-unit #2069	
343 		List<String> eligibleAccrualCategories = new ArrayList<String>();
344 		Map<String, ArrayList<String>> eligibilities = new HashMap<String,ArrayList<String>>();
345 		//TODO: create map for MAX_BAL_ACTION_FREQ in LMConstants
346 		eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE, new ArrayList<String>());
347 		eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END, new ArrayList<String>());
348 		eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND, new ArrayList<String>());
349 		if(ObjectUtils.isNull(calendarEntry)) {
350 			return eligibilities;
351         }
352 			
353 		PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, calendarEntry.getEndPeriodDate());
354 		if(ObjectUtils.isNull(pha)) {
355 			return eligibilities;
356         }
357 		
358 		List<AccrualCategory> accrualCategories = TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), calendarEntry.getEndPeriodDate());
359 
360 		org.kuali.hr.time.calendar.Calendar leaveCalendar = pha.getLeaveCalObj();
361 		CalendarEntries thisLeaveEntry = null;
362 		Interval thisEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
363 		Date asOfDate = TKUtils.getCurrentDate();
364 		if(TKUtils.getCurrentDate().after(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1)))
365 			asOfDate = new Date(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1).getTime());
366 			
367 		if(ObjectUtils.isNotNull(leaveCalendar)) {
368 			for(CalendarEntries entry : leaveCalendar.getCalendarEntries()) {
369 				if(thisEntryInterval.contains(DateUtils.addDays(entry.getEndPeriodTime(),-1).getTime()))
370 					thisLeaveEntry = entry;
371 			}
372 		}
373 		// this calendar entry interval does not contain a leave calendar's rollover date.
374 		if(ObjectUtils.isNull(thisLeaveEntry))
375 			return eligibilities;
376 		//TODO: Find the end period date for the corresponding leave calendar.
377 		// must check if this date falls within the interval of the calendar entries begin / end.
378 		// if so, get the leave blocks and calculate the accrued balance.
379 		//LeaveSummary leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId, getCalendarEntry());
380 		if(!accrualCategories.isEmpty()) {
381 			
382 			LeaveSummary summary = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(principalId, asOfDate);
383 			BigDecimal fte = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, calendarEntry.getEndPeriodDate());
384 			List<EmployeeOverride> overrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId, calendarEntry.getEndPeriodDate());
385 
386 			//null check inserted to fix LeaveCalendarWebTst failures on kpme-trunk-build-unit #2069
387 			for(AccrualCategory accrualCategory : accrualCategories) {
388 				//TODO: Iterate over Accrual Categories within this calendar entry.
389 				AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(accrualCategory, DateUtils.addDays(calendarEntry.getEndPeriodDate(), -1), pha.getServiceDate());
390 				//Employee overrides...
391 				if(ObjectUtils.isNotNull(rule)) {
392 					if(StringUtils.equals(rule.getMaxBalFlag(),"Y")) {
393 						if(StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.TRANSFER)
394 								|| StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
395 							//There is a disagreement between the constant value LMConstants.MAX_BAL_ACTION_FREQ, and the value being
396 							//set on LM_ACCRUAL_CATEGORY_RULES_T table. Temporarily have changed the constant to reflect the field
397 							//value being set for MAX_BAL_ACTION_FREQ when accrual category rule records are saved.
398 							if(ObjectUtils.isNotNull(rule.getMaxBalanceActionFrequency())) {
399 								BigDecimal maxBalance = rule.getMaxBalance();
400 								
401 								LeaveSummaryRow row = summary.getLeaveSummaryRowForAccrualCategory(accrualCategory.getLmAccrualCategoryId());
402 								if(row == null)
403 									continue;
404 								BigDecimal accruedBalance = row.getAccruedBalance();
405 /*								for(LeaveBlock leaveBlock : leaveBlockMap.get(accrualCategory.getAccrualCategory())) {
406 									//TODO: limit leave blocks to those created on or after the calendar year period containing this calendar entry.
407 									if(StringUtils.equals(leaveBlock.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED))
408 										accruedBalance = accruedBalance.add(leaveBlock.getLeaveAmount());
409 								}*/
410 								BigDecimal adjustedMaxBalance = maxBalance.multiply(fte);
411 								BigDecimal maxAnnualCarryOver = null;
412 								if(ObjectUtils.isNotNull(rule.getMaxCarryOver())) {
413 									maxAnnualCarryOver = new BigDecimal(rule.getMaxCarryOver());
414                                 }
415 								BigDecimal adjustedMaxAnnualCarryOver = null;
416 								if(ObjectUtils.isNotNull(maxAnnualCarryOver)) {
417 									adjustedMaxAnnualCarryOver = maxAnnualCarryOver.multiply(fte);
418                                 }
419 									
420 								for(EmployeeOverride override : overrides) {
421 									if(StringUtils.equals(override.getAccrualCategory(),accrualCategory.getAccrualCategory())) {
422 										if(StringUtils.equals(override.getOverrideType(),"MB")) {
423 											adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
424                                         }
425 										if(StringUtils.equals(override.getOverrideType(),"MAC")) {
426 											adjustedMaxAnnualCarryOver = new BigDecimal(override.getOverrideValue());
427                                         }
428 										//override values are not pro-rated.
429 									}
430 								}
431 								//should extend a BalanceTransferBase class, or use an algorithm swapping pattern.
432 								//allow institutions to extend/customize/implement their own max_bal_action_frequency types.
433 								if(StringUtils.equals(rule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
434 									//For year end transfer frequencies...
435 									//Should use an "asOfDate" or effective date for principalHRAttributes. If getting eligibilities for a past calendar,
436 									//pha may not be the same.
437 									LeavePlan lp = TkServiceLocator.getLeavePlanService().getLeavePlan(pha.getLeavePlan(),TKUtils.getCurrentDate());
438 									StringBuilder sb = new StringBuilder("");
439 									String calendarYearStart = lp.getCalendarYearStart();
440 									// mm/dd
441 									sb.append(calendarYearStart+"/");
442 									if(lp.getCalendarYearStartMonth().equals("01") && calendarEntry.getBeginPeriodDate().getMonth() == 11) {
443 										//a calendar may start on 01/15, with monthly intervals.
444 										//calendarEntry.beginPeriodDate.year = calendarYearStart.year - 1
445 										sb.append(DateUtils.toCalendar(DateUtils.addYears(calendarEntry.getBeginPeriodDate(),1)).get(Calendar.YEAR));
446 									}
447 									else {
448 										sb.append(DateUtils.toCalendar(calendarEntry.getBeginPeriodDateTime()).get(Calendar.YEAR));
449                                     }
450 									//if the calendar being submitted is the final calendar in the leave plans calendar year.
451 									//must check the calendar year start month. If its the first month of the year, add a year to the date.
452 									//otherwise, the end period date and the calendar year start date have the same year.
453 									if(thisEntryInterval.contains(DateUtils.addDays(TKUtils.formatDateString(sb.toString()),-1).getTime())) {
454 										//BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
455 										if(accruedBalance.compareTo(adjustedMaxBalance) > 0 ||
456 												(ObjectUtils.isNotNull(adjustedMaxAnnualCarryOver) &&
457 												accruedBalance.compareTo(adjustedMaxAnnualCarryOver) > 0)) {
458 											eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
459 											eligibilities.get(LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END).add(rule.getLmAccrualCategoryRuleId());
460 										}
461 									}
462 									//otherwise its not transferable under year end frequency.
463 								}
464 								else {
465 									//BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
466 									if(accruedBalance.compareTo(adjustedMaxBalance) > 0 ) {
467 										eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
468 										eligibilities.get(rule.getMaxBalanceActionFrequency()).add(rule.getLmAccrualCategoryRuleId());
469 									}
470 								}
471 							}
472 						}
473 					}
474 				}
475 			}
476 		}
477 		return eligibilities;
478 
479 	}
480 
481 	
482 /*	//TODO: Move to LeaveCalendarService or implement as an accessor on LeaveCalendarDocument object.
483 	@Override
484 	public List<String> getEligibleTransfers(CalendarEntries calendarEntry, String principalId, final String actionFrequency) throws Exception {
485 		List<String> eligibleAccrualCategories = new ArrayList<String>();
486 		//Employee override check here, or return base-eligible accrual categories,
487 		//filtering out those that have increased balance limits due to employee override in caller?
488 		if(ObjectUtils.isNotNull(calendarEntry)) {
489 			//null check inserted to fix LeaveCalendarWebTest failures on kpme-trunk-build-unit #2069		
490 			
491 			LeaveSummary leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId,calendarEntry);
492 			PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, calendarEntry.getEndPeriodDate());
493 			
494 			org.kuali.hr.time.calendar.Calendar leaveCalendar = pha.getLeaveCalObj();
495 			CalendarEntries thisLeaveEntry = null;
496 			Interval thisEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
497 
498 			if(ObjectUtils.isNotNull(leaveCalendar)) {
499 				for(CalendarEntries entry : leaveCalendar.getCalendarEntries()) {
500 					if(thisEntryInterval.contains(DateUtils.addSeconds(entry.getEndPeriodTime(),-1).getTime()))
501 						thisLeaveEntry = entry;
502 				}
503 			}
504 			
505 			// this calendar entry interval does not contain a leave calendar's rollover date.
506 			if(ObjectUtils.isNull(thisLeaveEntry))
507 				return eligibleAccrualCategories;
508 			
509 			if(ObjectUtils.isNotNull(leaveSummary)) {
510 				//null check inserted to fix LeaveCalendarWebTst failures on kpme-trunk-build-unit #2069
511 				
512 				for(LeaveSummaryRow lsr : leaveSummary.getLeaveSummaryRows()) {
513 					
514 					String accrualCategoryRuleId = lsr.getAccrualCategoryRuleId();
515 					AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId);
516 					//Employee overrides...
517 					if(ObjectUtils.isNotNull(rule)) {
518 						if(StringUtils.equals(rule.getMaxBalFlag(),"Y")) {
519 							if(StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.TRANSFER)
520 									|| StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
521 								//There is a disagreement between the constant value LMConstants.MAX_BAL_ACTION_FREQ, and the value being
522 								//set on LM_ACCRUAL_CATEGORY_RULES_T table. Temporarily have changed the constant to reflect the field
523 								//value being set for MAX_BAL_ACTION_FREQ when accrual category rule records are saved.
524 								if(StringUtils.equals(rule.getMaxBalanceActionFrequency(),actionFrequency)) {
525 									BigDecimal maxBalance = rule.getMaxBalance();
526 		
527 									BigDecimal fullTimeEngagement = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, TKUtils.getCurrentDate());
528 									BigDecimal adjustedMaxBalance = maxBalance.multiply(fullTimeEngagement);
529 									BigDecimal maxAnnualCarryOver = null;
530 									if(ObjectUtils.isNotNull(rule.getMaxCarryOver()))
531 											maxAnnualCarryOver = new BigDecimal(rule.getMaxCarryOver());
532 									BigDecimal adjustedMaxAnnualCarryOver = null;
533 									if(ObjectUtils.isNotNull(maxAnnualCarryOver))
534 										adjustedMaxAnnualCarryOver = maxAnnualCarryOver.multiply(fullTimeEngagement);
535 										
536 									List<EmployeeOverride> overrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId, TKUtils.getCurrentDate());
537 									for(EmployeeOverride override : overrides) {
538 										if(StringUtils.equals(override.getAccrualCategory(),lsr.getAccrualCategoryId())) {
539 											if(StringUtils.equals(override.getOverrideType(),"MB"))
540 												adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
541 											if(StringUtils.equals(override.getOverrideType(),"MAC"))
542 												adjustedMaxAnnualCarryOver = new BigDecimal(override.getOverrideValue());
543 											//override values are not pro-rated.
544 										}
545 									}
546 									//should extend a BalanceTransferBase class, or use an algorithm swapping pattern.
547 									//allow institutions to extend/customize/implement their own max_bal_action_frequency types.
548 									if(StringUtils.equals(actionFrequency,LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
549 										//For year end transfer frequencies...
550 										LeavePlan lp = TkServiceLocator.getLeavePlanService().getLeavePlan(pha.getLeavePlan(),TKUtils.getCurrentDate());
551 										StringBuilder sb = new StringBuilder("");
552 										String calendarYearStart = lp.getCalendarYearStart();
553 										// mm/dd
554 										sb.append(calendarYearStart+"/");
555 										if(lp.getCalendarYearStartMonth().equals("01") && calendarEntry.getBeginPeriodDate().getMonth() == 11) {
556 											//a calendar may start on 01/15, with monthly intervals.
557 											sb.append(DateUtils.toCalendar(DateUtils.addYears(calendarEntry.getBeginPeriodDate(),1)).get(Calendar.YEAR));
558 										}
559 										else
560 											sb.append(DateUtils.toCalendar(calendarEntry.getBeginPeriodDateTime()).get(Calendar.YEAR));
561 										//if the calendar being submitted is the final calendar in the leave plans calendar year.
562 										//must check the calendar year start month. If its the first month of the year, add a year to the date.
563 										//otherwise, the end period date and the calendar year start date have the same year.
564 										if(thisEntryInterval.contains(DateUtils.addSeconds(TKUtils.formatDateString(sb.toString()),-1).getTime())) {
565 											//BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
566 											if(lsr.getAccruedBalance().compareTo(adjustedMaxBalance) > 0 ||
567 													(ObjectUtils.isNotNull(adjustedMaxAnnualCarryOver) &&
568 													lsr.getAccruedBalance().compareTo(adjustedMaxAnnualCarryOver) > 0)) {
569 												eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
570 											}
571 										}
572 										//otherwise its not transferable under year end frequency.
573 									}
574 									else {
575 										//BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
576 										if(lsr.getAccruedBalance().compareTo(adjustedMaxBalance) > 0 ) {
577 											eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
578 										}
579 									}
580 								}
581 							}
582 						}
583 					}
584 				}
585 			}
586 		}
587 		return eligibleAccrualCategories;
588 	}
589 */
590 	public BalanceTransferDao getBalanceTransferDao() {
591 		return balanceTransferDao;
592 	}
593 	
594 	public void setBalanceTransferDao(BalanceTransferDao balanceTransferDao) {
595 		this.balanceTransferDao = balanceTransferDao;
596 	}
597 
598 	@Override
599 	public void submitToWorkflow(BalanceTransfer balanceTransfer)
600 			throws WorkflowException {
601 		
602 		//balanceTransfer.setStatus(TkConstants.ROUTE_STATUS.ENROUTE);
603         EntityNamePrincipalName principalName = null;
604         if (balanceTransfer.getPrincipalId() != null) {
605             principalName = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(balanceTransfer.getPrincipalId());
606         }
607 
608 		MaintenanceDocument document = KRADServiceLocatorWeb.getMaintenanceDocumentService().setupNewMaintenanceDocument(BalanceTransfer.class.getName(),
609 				"BalanceTransferDocumentType",KRADConstants.MAINTENANCE_NEW_ACTION);
610 
611         String personName = (principalName != null  && principalName.getDefaultName() != null) ? principalName.getDefaultName().getCompositeName() : StringUtils.EMPTY;
612         String date = TKUtils.formatDate(new java.sql.Date(balanceTransfer.getEffectiveDate().getTime()));
613         document.getDocumentHeader().setDocumentDescription(personName + " (" + balanceTransfer.getPrincipalId() + ")  - " + date);
614 		Map<String,String[]> params = new HashMap<String,String[]>();
615 		
616 		KRADServiceLocatorWeb.getMaintenanceDocumentService().setupMaintenanceObject(document, KRADConstants.MAINTENANCE_NEW_ACTION, params);
617 		BalanceTransfer btObj = (BalanceTransfer) document.getNewMaintainableObject().getDataObject();
618 		
619 		btObj.setAccrualCategoryRule(balanceTransfer.getAccrualCategoryRule());
620 		btObj.setEffectiveDate(balanceTransfer.getEffectiveDate());
621 		btObj.setForfeitedAmount(balanceTransfer.getForfeitedAmount());
622 		btObj.setFromAccrualCategory(balanceTransfer.getFromAccrualCategory());
623 		btObj.setPrincipalId(balanceTransfer.getPrincipalId());
624 		btObj.setToAccrualCategory(balanceTransfer.getToAccrualCategory());
625 		btObj.setTransferAmount(balanceTransfer.getTransferAmount());
626 		btObj.setAmountTransferred(balanceTransfer.getAmountTransferred());
627 		btObj.setSstoId(balanceTransfer.getSstoId());
628 		btObj.setDocumentHeaderId(document.getDocumentHeader().getWorkflowDocument().getDocumentId());
629 /*        TkServiceLocator.getBalanceTransferService().saveOrUpdate(btObj);
630 		document.getNewMaintainableObject().setDataObject(btObj);*/
631 		KRADServiceLocatorWeb.getDocumentService().saveDocument(document);
632 		document.getDocumentHeader().getWorkflowDocument().saveDocument("");
633 
634 		document.getDocumentHeader().getWorkflowDocument().route("");
635 		
636 
637 
638 		
639 	}
640 	
641 	@Override
642 	public BalanceTransfer transferSsto(BalanceTransfer balanceTransfer) {
643 		if(ObjectUtils.isNull(balanceTransfer))
644 			throw new RuntimeException("did not supply a valid BalanceTransfer object.");
645 		else {
646 			List<LeaveBlock> sstoLbList = TkServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(balanceTransfer.getPrincipalId(), balanceTransfer.getSstoId(), balanceTransfer.getEffectiveDate());
647 			String leaveDocId = "";
648 			if(CollectionUtils.isNotEmpty(sstoLbList)) {
649 				leaveDocId = sstoLbList.get(0).getDocumentId();
650 			}
651 			List<LeaveBlock> lbList = new ArrayList<LeaveBlock>();
652 			// create a new leave block with transferred amount, make sure system scheduled timeoff id is added to it
653 			LeaveBlock aLeaveBlock = new LeaveBlock();
654 			aLeaveBlock.setPrincipalId(balanceTransfer.getPrincipalId());
655 			aLeaveBlock.setLeaveDate(balanceTransfer.getEffectiveDate());
656 			aLeaveBlock.setEarnCode(balanceTransfer.getCreditedAccrualCategory().getEarnCode());
657 			aLeaveBlock.setAccrualCategory(balanceTransfer.getToAccrualCategory());
658 			aLeaveBlock.setDescription("System Scheduled Time off Amount transferred");
659 			aLeaveBlock.setLeaveAmount(balanceTransfer.getAmountTransferred());
660 			aLeaveBlock.setAccrualGenerated(false);
661 			aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
662 			aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
663 			aLeaveBlock.setBlockId(0L);
664 			aLeaveBlock.setScheduleTimeOffId(balanceTransfer.getSstoId());
665 			aLeaveBlock.setDocumentId(leaveDocId);
666 			
667 			lbList.add(aLeaveBlock);
668 			TkServiceLocator.getLeaveBlockService().saveLeaveBlocks(lbList);
669 
670 	    	balanceTransfer.setAccruedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());	
671 			return balanceTransfer;
672 		}
673 	}
674 
675 	@Override
676 	public List<BalanceTransfer> getBalanceTransfers(String viewPrincipal,
677 			Date beginPeriodDate, Date endPeriodDate) {
678 		// TODO Auto-generated method stub
679 		return balanceTransferDao.getBalanceTransfers(viewPrincipal, beginPeriodDate, endPeriodDate);
680 	}
681 
682 	@Override
683 	public void saveOrUpdate(BalanceTransfer balanceTransfer) {
684 		// TODO Auto-generated method stub
685 		balanceTransferDao.saveOrUpdate(balanceTransfer);
686 	}
687 }