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.balancetransfer.service;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.ArrayList;
021    import java.util.Calendar;
022    import java.util.HashMap;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.commons.collections.CollectionUtils;
027    import org.apache.commons.lang.StringUtils;
028    import org.apache.commons.lang.time.DateUtils;
029    import org.joda.time.Interval;
030    import org.kuali.hr.lm.LMConstants;
031    import org.kuali.hr.lm.accrual.AccrualCategory;
032    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
033    import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
034    import org.kuali.hr.lm.balancetransfer.dao.BalanceTransferDao;
035    import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
036    import org.kuali.hr.lm.leaveSummary.LeaveSummary;
037    import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
038    import org.kuali.hr.lm.leaveblock.LeaveBlock;
039    import org.kuali.hr.lm.leaveblock.LeaveBlockHistory;
040    import org.kuali.hr.lm.leaveplan.LeavePlan;
041    import org.kuali.hr.time.calendar.CalendarEntries;
042    import org.kuali.hr.time.principal.PrincipalHRAttributes;
043    import org.kuali.hr.time.service.base.TkServiceLocator;
044    import org.kuali.hr.time.util.TKUtils;
045    import org.kuali.hr.time.util.TkConstants;
046    import org.kuali.rice.kew.api.exception.WorkflowException;
047    import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
048    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
049    import org.kuali.rice.krad.maintenance.MaintenanceDocument;
050    import org.kuali.rice.krad.service.KRADServiceLocator;
051    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
052    import org.kuali.rice.krad.util.KRADConstants;
053    import org.kuali.rice.krad.util.ObjectUtils;
054    
055    public class BalanceTransferServiceImpl implements BalanceTransferService {
056    
057            private BalanceTransferDao balanceTransferDao;
058            
059            @Override
060            public List<BalanceTransfer> getAllBalanceTransfersForPrincipalId(
061                            String principalId) {
062                    return balanceTransferDao.getAllBalanceTransfersForPrincipalId(principalId);
063            }
064    
065            @Override
066            public List<BalanceTransfer> getAllBalanceTransferForPrincipalIdAsOfDate(
067                            String principalId, Date effectiveDate) {
068                    return balanceTransferDao.getAllBalanceTransferForPrincipalIdAsOfDate(principalId,effectiveDate);
069            }
070    
071            @Override
072            public List<BalanceTransfer> getAllBalanceTransferByEffectiveDate(
073                            Date effectiveDate) {
074                    return balanceTransferDao.getAllBalanceTransferByEffectiveDate(effectiveDate);
075            }
076    
077            @Override
078            public BalanceTransfer getBalanceTransferById(String balanceTransferId) {
079                    return balanceTransferDao.getBalanceTransferById(balanceTransferId);
080            }
081            
082            @Override
083            public BalanceTransfer initializeTransfer(String principalId, String accrualCategoryRule, BigDecimal accruedBalance, Date effectiveDate) {
084                    //Initially, principals may be allowed to edit the transfer amount when prompted to submit this balance transfer, however,
085                    //a base transfer amount together with a forfeited amount is calculated to bring the balance back to its limit in accordance
086                    //with transfer limits.
087                    BalanceTransfer bt = null;
088                    AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRule);
089    
090                    if(ObjectUtils.isNotNull(accrualRule) && ObjectUtils.isNotNull(accruedBalance)) {
091                            bt = new BalanceTransfer();
092                            //Leave summary is not a requirement, per se, but the information it contains is.
093                            //The only thing that is obtained from leave summary is the accrued balance of the leave summary row matching the
094                            //passed accrualCategoryRules accrual category.
095                            //These two objects are essential to balance transfers triggered when the employee submits their leave calendar for approval.
096                            //Neither of these objects should be null, otherwise this method could not have been called.
097                            AccrualCategory fromAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
098                            AccrualCategory toAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getMaxBalanceTransferToAccrualCategory(),effectiveDate);
099                            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    }