001    /**
002     * Copyright 2004-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.hr.lm.leavepayout.service;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.apache.commons.lang.time.DateUtils;
020    import org.joda.time.Interval;
021    import org.kuali.hr.lm.LMConstants;
022    import org.kuali.hr.lm.accrual.AccrualCategory;
023    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
024    import org.kuali.hr.lm.leavepayout.LeavePayout;
025    import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
026    import org.kuali.hr.lm.leaveSummary.LeaveSummary;
027    import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
028    import org.kuali.hr.lm.leaveblock.LeaveBlock;
029    import org.kuali.hr.lm.leaveblock.LeaveBlockHistory;
030    import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
031    import org.kuali.hr.lm.leavepayout.LeavePayout;
032    import org.kuali.hr.lm.leavepayout.dao.LeavePayoutDao;
033    
034    import org.kuali.hr.lm.leaveplan.LeavePlan;
035    import org.kuali.hr.time.calendar.CalendarEntries;
036    import org.kuali.hr.time.earncode.EarnCode;
037    import org.kuali.hr.time.principal.PrincipalHRAttributes;
038    import org.kuali.hr.time.service.base.TkServiceLocator;
039    import org.kuali.hr.time.util.TKContext;
040    import org.kuali.hr.time.util.TKUtils;
041    import org.kuali.hr.time.util.TkConstants;
042    import org.kuali.rice.kew.api.exception.WorkflowException;
043    import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
044    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
045    import org.kuali.rice.krad.maintenance.MaintenanceDocument;
046    import org.kuali.rice.krad.service.KRADServiceLocator;
047    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
048    import org.kuali.rice.krad.util.KRADConstants;
049    import org.kuali.rice.krad.util.ObjectUtils;
050    
051    import java.math.BigDecimal;
052    import java.sql.Date;
053    import java.util.ArrayList;
054    import java.util.Calendar;
055    import java.util.HashMap;
056    import java.util.List;
057    import java.util.Map;
058    
059    public class LeavePayoutServiceImpl implements LeavePayoutService {
060    
061        private LeavePayoutDao leavePayoutDao;
062    
063        @Override
064        public List<LeavePayout> getAllLeavePayoutsForPrincipalId(
065                String principalId) {
066            return leavePayoutDao.getAllLeavePayoutsForPrincipalId(principalId);
067        }
068    
069        @Override
070        public List<LeavePayout> getAllLeavePayoutsForPrincipalIdAsOfDate(
071                String principalId, Date effectiveDate) {
072            return leavePayoutDao.getAllLeavePayoutsForPrincipalIdAsOfDate(principalId,effectiveDate);
073        }
074    
075        @Override
076        public List<LeavePayout> getAllLeavePayoutsByEffectiveDate(
077                Date effectiveDate) {
078            return leavePayoutDao.getAllLeavePayoutsByEffectiveDate(effectiveDate);
079        }
080    
081        @Override
082        public LeavePayout getLeavePayoutById(String lmLeavePayoutId) {
083            return leavePayoutDao.getLeavePayoutById(lmLeavePayoutId);
084        }
085    
086        public LeavePayoutDao getLeavePayoutDao() {
087            return leavePayoutDao;
088        }
089    
090        public void setLeavePayoutDao(LeavePayoutDao leavePayoutDao) {
091            this.leavePayoutDao = leavePayoutDao;
092        }
093    
094            @Override
095            public LeavePayout initializePayout(String principalId,
096                            String accrualCategoryRule, BigDecimal accruedBalance,
097                            Date effectiveDate) {
098                    //Initially, principals may be allowed to edit the transfer amount when prompted to submit this balance transfer, however,
099                    //a base transfer amount together with a forfeited amount is calculated to bring the balance back to its limit in accordance
100                    //with transfer limits.
101                    LeavePayout leavePayout = null;
102                    AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRule);
103    
104                    if(ObjectUtils.isNotNull(accrualRule) && ObjectUtils.isNotNull(accruedBalance)) {
105                            leavePayout = new LeavePayout();
106                            //Leave summary is not a requirement, per se, but the information it contains is.
107                            //The only thing that is obtained from leave summary is the accrued balance of the leave summary row matching the
108                            //passed accrualCategoryRules accrual category.
109                            //These two objects are essential to balance transfers triggered when the employee submits their leave calendar for approval.
110                            //Neither of these objects should be null, otherwise this method could not have been called.
111                            AccrualCategory fromAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
112                            BigDecimal fullTimeEngagement = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate);
113                            
114                            // AccrualRule.maxBalance == null -> no balance limit. No balance limit -> no accrual triggered transfer / payout / lose.
115                            // execution point should not be here if max balance on accrualRule is null, unless there exists an employee override.
116                            BigDecimal maxBalance = accrualRule.getMaxBalance();
117                            BigDecimal adjustedMaxBalance = maxBalance.multiply(fullTimeEngagement).setScale(2);
118                            
119                            BigDecimal maxPayoutAmount = null;
120                            BigDecimal adjustedMaxPayoutAmount = null;
121                            if(ObjectUtils.isNotNull(accrualRule.getMaxPayoutAmount())) {
122                                    maxPayoutAmount = new BigDecimal(accrualRule.getMaxPayoutAmount());
123                                    adjustedMaxPayoutAmount = maxPayoutAmount.multiply(fullTimeEngagement).setScale(2);
124                            }
125                            else {
126                                    // no limit on transfer amount
127                                    maxPayoutAmount = new BigDecimal(Long.MAX_VALUE);
128                                    adjustedMaxPayoutAmount = maxPayoutAmount;
129                            }
130    
131                            BigDecimal maxCarryOver = null;
132                            BigDecimal adjustedMaxCarryOver = null;
133                            if(ObjectUtils.isNotNull(accrualRule.getMaxCarryOver())) {
134                                    maxCarryOver = new BigDecimal(accrualRule.getMaxCarryOver());
135                                    adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
136                            }
137                            else {
138                                    //no limit to carry over.
139                                    maxCarryOver = new BigDecimal(Long.MAX_VALUE);
140                                    adjustedMaxCarryOver = maxCarryOver;
141                            }
142                            
143                            List<EmployeeOverride> overrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId, effectiveDate);
144                            for(EmployeeOverride override : overrides) {
145                                    if(StringUtils.equals(override.getAccrualCategory(),fromAccrualCategory.getAccrualCategory())) {
146                                            if(StringUtils.equals(override.getOverrideType(),"MB"))
147                                                    adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
148                                            //override values are not pro-rated for FTE.
149                                            if(StringUtils.equals(override.getOverrideType(),"MPA"))
150                                                    adjustedMaxPayoutAmount = new BigDecimal(override.getOverrideValue());
151                                            if(StringUtils.equals(override.getOverrideType(),"MAC"))
152                                                    adjustedMaxCarryOver = new BigDecimal(override.getOverrideValue());
153                                    }
154                            }
155                            
156                            
157                            BigDecimal transferAmount = accruedBalance.subtract(adjustedMaxBalance);
158                            if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
159                                    //TODO: REMOVE CONDITIONAL
160                                    //Move all time in excess of employee's fte adjusted max balance to forfeiture.
161                                    leavePayout.setForfeitedAmount(transferAmount);
162                                    //There is no transfer to another accrual category.
163                                    leavePayout.setPayoutAmount(BigDecimal.ZERO);
164                                    // to accrual category is a required field on maintenance document. Set as from accrual category
165                                    // to remove validation errors when routing, approving, etc.
166                                    leavePayout.setEarnCode(fromAccrualCategory.getEarnCode());
167                            }
168                            else {
169                                    // ACTION_AT_MAX_BAL = PAYOUT
170                                    leavePayout.setEarnCode(accrualRule.getMaxPayoutEarnCode());
171                                    if(transferAmount.compareTo(adjustedMaxPayoutAmount) > 0) {
172                                            //there's forfeiture.
173                                            //bring transfer amount down to the adjusted maximum transfer amount, and place excess in forfeiture.
174                                            //accruedBalance - adjustedMaxPayoutAmount - adjustedMaxBalance = forfeiture.
175                                            //transferAmount = accruedBalance - adjustedMaxBalance; forfeiture = transferAmount - adjustedMaxPayoutAmount.
176                                            BigDecimal forfeiture = transferAmount.subtract(adjustedMaxPayoutAmount).setScale(2);
177                                            forfeiture = forfeiture.stripTrailingZeros();
178                                            leavePayout.setForfeitedAmount(forfeiture);
179                                            leavePayout.setPayoutAmount(adjustedMaxPayoutAmount);
180                                    }
181                                    else {
182                                            leavePayout.setPayoutAmount(transferAmount);
183                                            leavePayout.setForfeitedAmount(BigDecimal.ZERO);
184                                    }
185                            }
186                            
187                            // Max Carry Over logic for Year End transfers.
188                            if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
189                                    if(ObjectUtils.isNotNull(maxCarryOver)) {
190                                            if(ObjectUtils.isNull(adjustedMaxCarryOver))
191                                                    adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
192                                            //otherwise, adjustedMaxCarryOver has an employee override value, which trumps accrual rule defined MAC.
193                                            //At this point, transfer amount and forfeiture have been set so that the new accrued balance will be the
194                                            //adjusted max balance, so this amount is used to check against carry over.
195                                            if(adjustedMaxBalance.compareTo(adjustedMaxCarryOver) > 0) {
196                                                    BigDecimal carryOverDiff = adjustedMaxBalance.subtract(adjustedMaxCarryOver);
197                                                    
198                                                    if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)){
199                                                            //add carry over excess to forfeiture.
200                                                            leavePayout.setForfeitedAmount(leavePayout.getForfeitedAmount().add(carryOverDiff));
201                                                    }
202                                                    else {
203                                                            //maximize the transfer amount.
204                                                            BigDecimal potentialPayoutAmount = leavePayout.getPayoutAmount().add(carryOverDiff);
205            
206                                                            //Can this amount be added to the transfer amount??
207                                                            if(potentialPayoutAmount.compareTo(adjustedMaxPayoutAmount) <= 0) {
208                                                                    //yes
209                                                                    leavePayout.setPayoutAmount(leavePayout.getPayoutAmount().add(carryOverDiff));
210                                                            }
211                                                            else {
212                                                                    //no
213                                                                    BigDecimal carryOverExcess = potentialPayoutAmount.subtract(adjustedMaxPayoutAmount);
214                                                                    //move excess to forfeiture
215                                                                    leavePayout.setForfeitedAmount(leavePayout.getForfeitedAmount().add(carryOverExcess));
216                                                                    //the remainder (if any) can be added to the transfer amount ( unless action is LOSE ).
217                                                                    leavePayout.setPayoutAmount(leavePayout.getPayoutAmount().add(carryOverDiff.subtract(carryOverExcess)));
218                                                                    assert(leavePayout.getPayoutAmount().compareTo(adjustedMaxPayoutAmount)==0);
219                                                            }
220                                                    }
221                                            }
222                                            //otherwise, given balance will be at or under the max annual carry over.
223                                    }
224                            }
225                            
226                            leavePayout.setEffectiveDate(effectiveDate);
227                            leavePayout.setAccrualCategoryRule(accrualCategoryRule);
228                            leavePayout.setFromAccrualCategory(fromAccrualCategory.getAccrualCategory());
229                            leavePayout.setPrincipalId(principalId);
230                    }
231                    return leavePayout;
232            }
233    
234            @Override
235            public LeavePayout payout(LeavePayout leavePayout) {
236                    if(ObjectUtils.isNull(leavePayout))
237                            throw new RuntimeException("did not supply a valid LeavePayout object.");
238                    else {
239                            List<LeaveBlock> leaveBlocks = new ArrayList<LeaveBlock>();
240                            BigDecimal transferAmount = leavePayout.getPayoutAmount();
241                            LeaveBlock aLeaveBlock = null;
242                            
243                            if(ObjectUtils.isNotNull(transferAmount)) {
244                                    if(transferAmount.compareTo(BigDecimal.ZERO) > 0) {
245                    
246                                            aLeaveBlock = new LeaveBlock();
247                                            //Create a leave block that adds the adjusted transfer amount to the "transfer to" accrual category.
248                                            aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
249                                            aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
250                                            aLeaveBlock.setEarnCode(leavePayout.getEarnCode());
251                                            aLeaveBlock.setAccrualCategory(leavePayout.getEarnCodeObj().getAccrualCategory());
252                                            aLeaveBlock.setDescription("Amount payed out");
253                                            aLeaveBlock.setLeaveAmount(leavePayout.getPayoutAmount());
254                                            aLeaveBlock.setAccrualGenerated(true);
255                                            aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
256                                            aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
257                                            aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
258                                            aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
259                                            aLeaveBlock.setBlockId(0L);
260    
261                                            //Want to store the newly created leave block id on this maintainable object
262                                            //when the status of the maintenance document encapsulating this maintainable changes
263                                            //the id will be used to fetch and update the leave block statuses.
264                                    aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
265    
266                                    leavePayout.setPayoutLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
267                                    // save history
268                                    LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock);
269                                    lbh.setAction(LMConstants.ACTION.ADD);
270                                    TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
271                                            leaveBlocks.add(aLeaveBlock);
272                                            
273                                            //Create leave block that removes the correct transfer amount from the originating accrual category.
274                                            aLeaveBlock = new LeaveBlock();
275                                            aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
276                                            aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
277                                            aLeaveBlock.setEarnCode(leavePayout.getFromAccrualCategoryObj().getEarnCode());
278                                            aLeaveBlock.setAccrualCategory(leavePayout.getFromAccrualCategory());
279                                            aLeaveBlock.setDescription("Payout amount");
280                                            aLeaveBlock.setLeaveAmount(leavePayout.getPayoutAmount().negate());
281                                            aLeaveBlock.setAccrualGenerated(true);
282                                            aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
283                                            aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
284                                            aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
285                                            aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
286                                            aLeaveBlock.setBlockId(0L);
287                                            
288                                            //Want to store the newly created leave block id on this maintainable object.
289                                            //when the status of the maintenance document encapsulating this maintainable changes
290                                            //the id will be used to fetch and update the leave block statuses.
291                                    aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
292    
293                                    leavePayout.setPayoutFromLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
294                                    // save history
295                                    lbh = new LeaveBlockHistory(aLeaveBlock);
296                                    lbh.setAction(LMConstants.ACTION.ADD);
297                                    TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
298                                            
299                                            leaveBlocks.add(aLeaveBlock);
300                                    }
301                            }
302                            
303                            BigDecimal forfeitedAmount = leavePayout.getForfeitedAmount();
304                            if(ObjectUtils.isNotNull(forfeitedAmount)) {
305                                    //Any amount forfeited must come out of the originating accrual category in order to bring balance back to max.
306                                    if(forfeitedAmount.compareTo(BigDecimal.ZERO) > 0) {
307                                            //for balance transfers with action = lose, transfer amount must be moved to forfeitedAmount
308                                            aLeaveBlock = new LeaveBlock();
309                                            aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
310                                            aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
311                                            aLeaveBlock.setEarnCode(leavePayout.getFromAccrualCategoryObj().getEarnCode());
312                                            aLeaveBlock.setAccrualCategory(leavePayout.getFromAccrualCategory());
313                                            aLeaveBlock.setDescription("Forfeited payout amount");
314                                            aLeaveBlock.setLeaveAmount(forfeitedAmount.negate());
315                                            aLeaveBlock.setAccrualGenerated(true);
316                                            aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
317                                            aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
318                                            aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
319                                            aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
320                                            aLeaveBlock.setBlockId(0L);
321                                            
322                                            //Want to store the newly created leave block id on this maintainable object
323                                            //when the status of the maintenance document encapsulating this maintainable changes
324                                            //the id will be used to fetch and update the leave block statuses.
325                                    aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
326    
327                                    leavePayout.setForfeitedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
328                                    // save history
329                                    LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock);
330                                    lbh.setAction(LMConstants.ACTION.ADD);
331                                    TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
332                                            
333                                            leaveBlocks.add(aLeaveBlock);
334                                    }
335                            }
336    
337                            return leavePayout;
338                    }
339            }
340    
341            @Override
342            public Map<String,ArrayList<String>> getEligiblePayouts(CalendarEntries calendarEntry,
343                            String principalId) throws Exception {
344                    //Employee override check here, or return base-eligible accrual categories,
345                    //filtering out those that have increased balance limits due to employee override in caller?
346                    //null check inserted to fix LeaveCalendarWebTest failures on kpme-trunk-build-unit #2069       
347                    List<String> eligibleAccrualCategories = new ArrayList<String>();
348                    Map<String, ArrayList<String>> eligibilities = new HashMap<String,ArrayList<String>>();
349                    //TODO: create map for MAX_BAL_ACTION_FREQ in LMConstants
350                    eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE, new ArrayList<String>());
351                    eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END, new ArrayList<String>());
352                    eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND, new ArrayList<String>());
353    
354                    PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, calendarEntry.getEndPeriodDate());
355                    List<AccrualCategory> accrualCategories = TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), calendarEntry.getEndPeriodDate());
356                    
357                    org.kuali.hr.time.calendar.Calendar leaveCalendar = pha.getLeaveCalObj();
358                    CalendarEntries thisLeaveEntry = null;
359                    Interval thisEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
360                    Date asOfDate = TKUtils.getCurrentDate();
361                    if(TKUtils.getCurrentDate().after(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1)))
362                            asOfDate = new Date(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(), -1).getTime());
363                    if(ObjectUtils.isNotNull(leaveCalendar)) {
364                            for(CalendarEntries entry : leaveCalendar.getCalendarEntries()) {
365                                    if(thisEntryInterval.contains(DateUtils.addDays(entry.getEndPeriodTime(),-1).getTime()))
366                                            thisLeaveEntry = entry;
367                            }
368                    }
369                    // this calendar entry interval does not contain a leave calendar's rollover date.
370                    if(ObjectUtils.isNull(thisLeaveEntry)) {
371                            return eligibilities;
372            }
373                    //TODO: Find the end period date for the corresponding leave calendar.
374                    // must check if this date falls within the interval of the calendar entries begin / end.
375                    // if so, get the leave blocks and calculate the accrued balance.
376                    //LeaveSummary leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId, getCalendarEntry());
377                    if(!accrualCategories.isEmpty()) {
378                LeaveSummary summary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId, calendarEntry);
379                //null check inserted to fix LeaveCalendarWebTst failures on kpme-trunk-build-unit #2069
380                            for(AccrualCategory accrualCategory : accrualCategories) {
381                                    //TODO: Iterate over Accrual Categories within this calendar entry.
382                                    AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(accrualCategory, DateUtils.addDays(calendarEntry.getEndPeriodDate(),-1), pha.getServiceDate());
383                                    //Employee overrides...
384                                    if(ObjectUtils.isNotNull(rule)) {
385                                            if(StringUtils.equals(rule.getMaxBalFlag(),"Y")) {
386                                                    if(StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.PAYOUT)) {
387                                                            //There is a disagreement between the constant value LMConstants.MAX_BAL_ACTION_FREQ, and the value being
388                                                            //set on LM_ACCRUAL_CATEGORY_RULES_T table. Temporarily have changed the constant to reflect the field
389                                                            //value being set for MAX_BAL_ACTION_FREQ when accrual category rule records are saved.
390                                                            if(ObjectUtils.isNotNull(rule.getMaxBalanceActionFrequency())) {
391                                                                    BigDecimal maxBalance = rule.getMaxBalance();
392                                                                    
393                                                                    LeaveSummaryRow row = summary.getLeaveSummaryRowForAccrualCategory(accrualCategory.getLmAccrualCategoryId());
394                                                                    BigDecimal accruedBalance = row.getAccruedBalance();
395    /*                                                              for(LeaveBlock leaveBlock : leaveBlockMap.get(accrualCategory.getAccrualCategory())) {
396                                                                            //TODO: limit leave blocks to those created on or after the calendar year period containing this calendar entry.
397                                                                            if(StringUtils.equals(leaveBlock.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED))
398                                                                                    accruedBalance = accruedBalance.add(leaveBlock.getLeaveAmount());
399                                                                    }*/
400                                                                    BigDecimal fte = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, TKUtils.getCurrentDate());
401                                                                    BigDecimal adjustedMaxBalance = maxBalance.multiply(fte);
402                                                                    BigDecimal maxAnnualCarryOver = null;
403                                                                    if(ObjectUtils.isNotNull(rule.getMaxCarryOver()))
404                                                                            maxAnnualCarryOver = new BigDecimal(rule.getMaxCarryOver());
405                                                                    BigDecimal adjustedMaxAnnualCarryOver = null;
406                                                                    if(ObjectUtils.isNotNull(maxAnnualCarryOver)) {
407                                                                            adjustedMaxAnnualCarryOver = maxAnnualCarryOver.multiply(fte);
408                                    }
409                                                                            
410                                                                    List<EmployeeOverride> overrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId, TKUtils.getCurrentDate());
411                                                                    for(EmployeeOverride override : overrides) {
412                                                                            if(StringUtils.equals(override.getAccrualCategory(),accrualCategory.getAccrualCategory())) {
413                                                                                    if(StringUtils.equals(override.getOverrideType(),"MB")) {
414                                                                                            adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
415                                            }
416                                                                                    if(StringUtils.equals(override.getOverrideType(),"MAC")) {
417                                                                                            adjustedMaxAnnualCarryOver = new BigDecimal(override.getOverrideValue());
418                                            }
419                                                                                    //override values are not pro-rated.
420                                                                            }
421                                                                    }
422                                                                    //should extend a BalanceTransferBase class, or use an algorithm swapping pattern.
423                                                                    //allow institutions to extend/customize/implement their own max_bal_action_frequency types.
424                                                                    if(StringUtils.equals(rule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
425                                                                            //For year end transfer frequencies...
426                                                                            //Should use an "asOfDate" or effective date for principalHRAttributes. If getting eligibilities for a past calendar,
427                                                                            //pha may not be the same.
428                                                                            LeavePlan lp = TkServiceLocator.getLeavePlanService().getLeavePlan(pha.getLeavePlan(),TKUtils.getCurrentDate());
429                                                                            StringBuilder sb = new StringBuilder("");
430                                                                            String calendarYearStart = lp.getCalendarYearStart();
431                                                                            // mm/dd
432                                                                            sb.append(calendarYearStart+"/");
433                                                                            if(lp.getCalendarYearStartMonth().equals("01") && calendarEntry.getBeginPeriodDate().getMonth() == 11) {
434                                                                                    //a calendar may start on 01/15, with monthly intervals.
435                                                                                    //calendarEntry.beginPeriodDate.year = calendarYearStart.year - 1
436                                                                                    sb.append(DateUtils.toCalendar(DateUtils.addYears(calendarEntry.getBeginPeriodDate(),1)).get(Calendar.YEAR));
437                                                                            }
438                                                                            else {
439                                                                                    sb.append(DateUtils.toCalendar(calendarEntry.getBeginPeriodDateTime()).get(Calendar.YEAR));
440                                        }
441                                                                            //if the calendar being submitted is the final calendar in the leave plans calendar year.
442                                                                            //must check the calendar year start month. If its the first month of the year, add a year to the date.
443                                                                            //otherwise, the end period date and the calendar year start date have the same year.
444                                                                            if(thisEntryInterval.contains(DateUtils.addDays(TKUtils.formatDateString(sb.toString()),-1).getTime())) {
445                                                                                    //BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
446                                                                                    if(accruedBalance.compareTo(adjustedMaxBalance) > 0 ||
447                                                                                                    (ObjectUtils.isNotNull(adjustedMaxAnnualCarryOver) &&
448                                                                                                    accruedBalance.compareTo(adjustedMaxAnnualCarryOver) > 0)) {
449                                                                                            eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
450                                                                                            eligibilities.get(LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END).add(rule.getLmAccrualCategoryRuleId());
451                                                                                    }
452                                                                            }
453                                                                            //otherwise its not transferable under year end frequency.
454                                                                    }
455                                                                    else {
456                                                                            //BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
457                                                                            if(accruedBalance.compareTo(adjustedMaxBalance) > 0 ) {
458                                                                                    eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
459                                                                                    eligibilities.get(rule.getMaxBalanceActionFrequency()).add(rule.getLmAccrualCategoryRuleId());
460                                                                            }
461                                                                    }
462                                                            }
463                                                    }
464                                            }
465                                    }
466                            }
467                    }
468                    return eligibilities;
469            }
470    
471            @Override
472            public void submitToWorkflow(LeavePayout leavePayout)
473                            throws WorkflowException {
474                    //leavePayout.setStatus(TkConstants.ROUTE_STATUS.ENROUTE);
475            EntityNamePrincipalName principalName = null;
476            if (leavePayout.getPrincipalId() != null) {
477                principalName = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(leavePayout.getPrincipalId());
478            }
479    
480                    MaintenanceDocument document = KRADServiceLocatorWeb.getMaintenanceDocumentService().setupNewMaintenanceDocument(LeavePayout.class.getName(),
481                                    "LeavePayoutDocumentType",KRADConstants.MAINTENANCE_NEW_ACTION);
482    
483            String personName = (principalName != null  && principalName.getDefaultName() != null) ? principalName.getDefaultName().getCompositeName() : StringUtils.EMPTY;
484            String date = TKUtils.formatDate(new java.sql.Date(leavePayout.getEffectiveDate().getTime()));
485            document.getDocumentHeader().setDocumentDescription(personName + " (" + leavePayout.getPrincipalId() + ")  - " + date);
486                    Map<String,String[]> params = new HashMap<String,String[]>();
487                    
488                    KRADServiceLocatorWeb.getMaintenanceDocumentService().setupMaintenanceObject(document, KRADConstants.MAINTENANCE_NEW_ACTION, params);
489                    LeavePayout lpObj = (LeavePayout) document.getNewMaintainableObject().getDataObject();
490                    
491                    lpObj.setAccrualCategoryRule(leavePayout.getAccrualCategoryRule());
492                    lpObj.setEffectiveDate(leavePayout.getEffectiveDate());
493                    lpObj.setForfeitedAmount(leavePayout.getForfeitedAmount());
494                    lpObj.setFromAccrualCategory(leavePayout.getFromAccrualCategory());
495                    lpObj.setPrincipalId(leavePayout.getPrincipalId());
496                    lpObj.setEarnCode(leavePayout.getEarnCode());
497                    lpObj.setPayoutAmount(leavePayout.getPayoutAmount());
498                    lpObj.setDocumentHeaderId(document.getDocumentHeader().getWorkflowDocument().getDocumentId());
499                    
500                    document.getNewMaintainableObject().setDataObject(lpObj);
501                    KRADServiceLocatorWeb.getDocumentService().saveDocument(document);
502                    document.getDocumentHeader().getWorkflowDocument().saveDocument("");
503    
504                    document.getDocumentHeader().getWorkflowDocument().route("");
505            }
506    
507            @Override
508            public List<LeavePayout> getLeavePayouts(String viewPrincipal,
509                            Date beginPeriodDate, Date endPeriodDate) {
510                    // TODO Auto-generated method stub
511                    return leavePayoutDao.getLeavePayouts(viewPrincipal, beginPeriodDate, endPeriodDate);
512            }
513    
514            @Override
515            public void saveOrUpdate(LeavePayout payout) {
516                    leavePayoutDao.saveOrUpdate(payout);
517            }
518    }