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.web;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.*;
021    import java.util.Map.Entry;
022    
023    import javax.servlet.http.HttpServletRequest;
024    import javax.servlet.http.HttpServletResponse;
025    
026    import org.apache.commons.collections.CollectionUtils;
027    import org.apache.commons.lang.time.DateUtils;
028    import org.apache.commons.lang3.StringUtils;
029    import org.apache.struts.action.ActionForm;
030    import org.apache.struts.action.ActionForward;
031    import org.apache.struts.action.ActionMapping;
032    import org.apache.struts.action.ActionRedirect;
033    import org.kuali.hr.lm.LMConstants;
034    import org.kuali.hr.lm.accrual.AccrualCategory;
035    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
036    import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
037    import org.kuali.hr.lm.balancetransfer.validation.BalanceTransferValidationUtils;
038    import org.kuali.hr.lm.leaveSummary.LeaveSummary;
039    import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
040    import org.kuali.hr.lm.leaveblock.LeaveBlock;
041    import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
042    import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
043    import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
044    import org.kuali.hr.time.base.web.TkAction;
045    import org.kuali.hr.time.calendar.Calendar;
046    import org.kuali.hr.time.calendar.CalendarEntries;
047    import org.kuali.hr.time.earncode.EarnCode;
048    import org.kuali.hr.time.service.base.TkServiceLocator;
049    import org.kuali.hr.time.timesheet.TimesheetDocument;
050    import org.kuali.hr.time.util.TKUtils;
051    import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
052    import org.kuali.rice.krad.service.KRADServiceLocator;
053    import org.kuali.rice.krad.util.GlobalVariables;
054    import org.kuali.rice.krad.util.ObjectUtils;
055    
056    public class BalanceTransferAction extends TkAction {
057    
058            public ActionForward balanceTransferOnLeaveApproval(ActionMapping mapping, ActionForm form,
059                            HttpServletRequest request, HttpServletResponse response) throws Exception {
060    
061                    //if action was submit, execute the transfer
062                    BalanceTransferForm btf = (BalanceTransferForm) form;
063                    BalanceTransfer balanceTransfer = btf.getBalanceTransfer();
064            
065                    boolean valid = BalanceTransferValidationUtils.validateTransfer(balanceTransfer);
066                    
067                    //if transfer amount has changed, and the resulting change produces forfeiture
068                    //or changes the forfeiture amount, prompt for confirmation with the amount of
069                    //forfeiture that the entered amount would produce.
070    
071                    if(valid) {
072                            
073                            String accrualRuleId = balanceTransfer.getAccrualCategoryRule();
074                            
075                            String documentId = balanceTransfer.getLeaveCalendarDocumentId();
076                            TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
077                            LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
078                            CalendarEntries calendarEntry = null;
079                            String strutsActionForward = "";
080                            String methodToCall = "approveLeaveCalendar";
081                            if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) {
082                                    throw new RuntimeException("No document found");
083                            }
084                            else if(ObjectUtils.isNotNull(tsdh)) {
085                                    //Throws runtime exception, separate action forwards for timesheet/leave calendar transfers.
086                                    TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
087                                    calendarEntry = tsd != null ? tsd.getCalendarEntry() : null;
088                                    strutsActionForward = "timesheetTransferSuccess";
089                                    methodToCall = "approveTimesheet";
090                            }
091                            else {
092                                    LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId);
093                                    calendarEntry = lcd != null ? lcd.getCalendarEntry() : null;
094                                    strutsActionForward = "leaveCalendarTransferSuccess";
095                                    methodToCall = "approveLeaveCalendar";
096                            }
097                            
098                            if(ObjectUtils.isNull(calendarEntry)) {
099                                    throw new RuntimeException("Could not retreive calendar entry for document " + documentId);
100                            }
101                            
102                            AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
103                            
104                            AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
105                            BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(balanceTransfer.getPrincipalId(), accrualCategory, balanceTransfer.getEffectiveDate());
106    
107                            BalanceTransfer defaultBT = TkServiceLocator.getBalanceTransferService().initializeTransfer(balanceTransfer.getPrincipalId(), accrualRuleId, accruedBalance, balanceTransfer.getEffectiveDate());
108                            if(balanceTransfer.getTransferAmount().compareTo(defaultBT.getTransferAmount()) != 0) {
109                                    //employee changed the transfer amount, recalculate forfeiture.
110                                    //Note: transfer form has been validated.
111                                    balanceTransfer = defaultBT.adjust(balanceTransfer.getTransferAmount());
112                                    // showing the adjusted balance transfer via the execution of another forward
113                                    // would cause a loop that would break only if the original transfer amount was re-established in the form.
114                                    // javascript must be written if the forfeited amount is to be updated on the form object.
115                                    // an alternative to javascript would be to render a "re-calculate" button attached to a dedicated action forward method.
116                                    // must re-set leaveCalendarDocumentId, as balanceTransfer is now just an adjustment of the default initialized BT with no leave calendar doc id.
117                                    balanceTransfer.setLeaveCalendarDocumentId(documentId);
118                            }
119    
120                            TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
121                            
122                            if(ObjectUtils.isNotNull(documentId)) {
123                                    if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
124                                                    StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
125                                            ActionForward forward = new ActionForward(mapping.findForward(strutsActionForward));
126                                            forward.setPath(forward.getPath()+"?documentId="+documentId+"&action=R&methodToCall="+methodToCall);
127                                            return forward;
128                                    }
129                                    else
130                                            return mapping.findForward("closeBalanceTransferDoc");
131                            }
132                            else
133                                    return mapping.findForward("closeBalanceTransferDoc");
134                    }
135                    else //show user errors.
136                            return mapping.findForward("basic");
137            }
138    
139            public ActionForward cancel(ActionMapping mapping, ActionForm form,
140                            HttpServletRequest request, HttpServletResponse response)
141                            throws Exception {
142                    
143                    BalanceTransferForm btf = (BalanceTransferForm) form;
144                    BalanceTransfer bt = btf.getBalanceTransfer();
145    
146                    if(btf.isSstoTransfer()) {
147                            return mapping.findForward("closeBalanceTransferDoc");
148                    }
149                    
150                    String accrualCategoryRuleId = bt.getAccrualCategoryRule();
151                    AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId);
152                    String actionFrequency = accrualRule.getMaxBalanceActionFrequency();
153                    
154                    if(StringUtils.equals(actionFrequency,LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND)) 
155                            return mapping.findForward("closeBalanceTransferDoc");
156                    else 
157                            if(StringUtils.equals(actionFrequency, LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
158                                            StringUtils.equals(actionFrequency, LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
159                                    String documentId = bt.getLeaveCalendarDocumentId();
160                                    TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
161                                    LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
162                                    String strutsActionForward = "";
163                                    if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) {
164                                            strutsActionForward = "/";
165                                    }
166                                    else if(ObjectUtils.isNotNull(tsdh)) {
167                                            //Throws runtime exception, separate action forwards for timesheet/leave calendar transfers.
168                                            strutsActionForward = mapping.findForward("timesheetCancel").getPath() + "?documentId=" + bt.getLeaveCalendarDocumentId();
169                                    }
170                                    else {
171                                            strutsActionForward = mapping.findForward("leaveCalendarCancel").getPath() + "?documentId=" + bt.getLeaveCalendarDocumentId();
172                                    }
173    
174                                    ActionRedirect redirect = new ActionRedirect();
175                                    redirect.setPath(strutsActionForward);
176                                    return redirect;
177                            }
178                            else
179                                    throw new RuntimeException("Action should only be reachable through triggers with frequency ON_DEMAND or LEAVE_APPROVE");
180            }
181            
182            //Entry point for BalanceTransfer.do for accrual category rule triggered transfers with action frequency On Demand.
183            //May be better suited in the LeaveCalendarAction class.
184            public ActionForward balanceTransferOnDemand(ActionMapping mapping, ActionForm form,
185                            HttpServletRequest request, HttpServletResponse response)
186                            throws Exception {
187                    GlobalVariables.getMessageMap().putWarning("document.transferAmount","balanceTransfer.transferAmount.adjust");
188    
189                    BalanceTransferForm btf = (BalanceTransferForm) form;
190                    //the leave calendar document that triggered this balance transfer.
191                    String documentId = request.getParameter("documentId");
192                    String accrualRuleId = request.getParameter("accrualRuleId");
193                    String timesheet = request.getParameter("timesheet");
194                    boolean isTimesheet = false;
195                    if(StringUtils.equals(timesheet, "true")) {
196                            btf.isTimesheet(true);
197                            isTimesheet = true;
198                    }
199                    if(ObjectUtils.isNotNull(accrualRuleId)) {
200                            //LeaveBlock lb = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
201                            AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
202                            if(ObjectUtils.isNotNull(aRule)) {
203                                    //should somewhat safegaurd against url fabrication.
204                                    if(!StringUtils.equals(aRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND))
205                                            throw new RuntimeException("attempted to execute on-demand balance transfer for accrual category with action frequency " + aRule.getMaxBalanceActionFrequency());
206                                    else {
207                                            String principalId = null;
208    
209                                            if(isTimesheet) {
210                            TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
211                                                    principalId = tsd == null ? null : tsd.getPrincipalId();
212                                            }
213                                            else {
214                            LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId);
215                                                    principalId = lcd == null ? null : lcd.getPrincipalId();
216                                            }
217    
218                                            AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
219                                            AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
220                                            BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, TKUtils.getCurrentDate());
221    
222                                            BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(principalId, aRule.getLmAccrualCategoryRuleId(), accruedBalance, TKUtils.getCurrentDate());
223                                            balanceTransfer.setLeaveCalendarDocumentId(documentId);
224                                            if(ObjectUtils.isNotNull(balanceTransfer)) {
225                                                    if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {      
226                                                            // this particular combination of action / action frequency does not particularly make sense
227                                                            // unless for some reason users still need to be prompted to submit the loss.
228                                                            // For now, we treat as though it is a valid use-case.
229                                                            //TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
230                                                            // May need to update to save the business object to KPME's tables for record keeping.
231                                                            balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
232                                                            // May need to update to save the business object to KPME's tables for record keeping.
233                                                            LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
234                                                            forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
235                                                            TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId);
236                                                            return mapping.findForward("closeBalanceTransferDoc");
237                                                    }
238                                                    else {
239                                                            ActionForward forward = mapping.findForward("basic");
240                                                            btf.setLeaveCalendarDocumentId(documentId);
241                                                            btf.setBalanceTransfer(balanceTransfer);
242                                                            btf.setTransferAmount(balanceTransfer.getTransferAmount());
243                                                            return forward;
244                                                    }
245                                            }
246                                            else
247                                                    throw new RuntimeException("could not initialize a balance transfer");
248    
249                                    }
250                            }
251                            else
252                                    throw new RuntimeException("No rule for this accrual category could be found");
253                    }
254                    else
255                            throw new RuntimeException("No accrual category rule id has been sent in the request.");
256            }
257    
258            //Entry point for BalanceTransfer.do for accrual category rule triggered transfers with action frequency Leave Approve.
259            //TODO: Rename method to differentiate from ActionForward with same name in LeaveCalendarSubmit.
260            public ActionForward approveLeaveCalendar(ActionMapping mapping, ActionForm form,
261                            HttpServletRequest request, HttpServletResponse response)
262                                            throws Exception {
263                    
264                    GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferAmount.adjust");
265                    BalanceTransferForm btf = (BalanceTransferForm) form;
266    
267                    List<LeaveBlock> eligibleTransfers = (List<LeaveBlock>) request.getSession().getAttribute("eligibilities");
268                    if(!eligibleTransfers.isEmpty()) {
269                            
270                            Collections.sort(eligibleTransfers, new Comparator() {
271    
272                    @Override
273                    public int compare(Object o1, Object o2) {
274                        LeaveBlock l1 = (LeaveBlock) o1;
275                        LeaveBlock l2 = (LeaveBlock) o2;
276                        return l1.getLeaveDate().compareTo(l2.getLeaveDate());
277                    }
278    
279                });
280                            
281                            //This is the leave calendar document that triggered this balance transfer.
282    
283                            String leaveCalendarDocumentId = request.getParameter("documentId");
284                            ActionForward forward = new ActionForward(mapping.findForward("basic"));
285                            LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(leaveCalendarDocumentId);
286                            String principalId = lcd == null ? null : lcd.getPrincipalId();
287                            LeaveBlock leaveBlock = eligibleTransfers.get(0);
288                            Date effectiveDate = leaveBlock.getLeaveDate();
289                            String accrualCategoryRuleId = leaveBlock.getAccrualCategoryRuleId();
290                            if(!StringUtils.isBlank(accrualCategoryRuleId)) {
291                                    
292                                    AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId);
293                                    AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
294                                    BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, leaveBlock.getLeaveDate());
295                                    
296                                    BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(principalId, accrualCategoryRuleId, accruedBalance, effectiveDate);
297                                    
298                                    balanceTransfer.setLeaveCalendarDocumentId(leaveCalendarDocumentId);
299                                    
300                                    if(ObjectUtils.isNotNull(balanceTransfer)) {
301                                            if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
302            
303                            //TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
304                            balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
305                            // May need to update to save the business object to KPME's tables for record keeping.
306                            LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
307                            KRADServiceLocator.getBusinessObjectService().save(balanceTransfer);
308                            forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
309                            TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId);
310    
311                            if(ObjectUtils.isNotNull(leaveCalendarDocumentId)) {
312                                if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
313                                        StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
314                                    ActionForward loseForward = new ActionForward(mapping.findForward("leaveCalendarTransferSuccess"));
315                                    loseForward.setPath(loseForward.getPath()+"?documentId="+leaveCalendarDocumentId+"&action=R&methodToCall=approveLeaveCalendar");
316                                    return loseForward;
317                                }
318                                    //on demand handled in separate action forward.
319                            }
320    
321                                            } else {
322                                                    btf.setLeaveCalendarDocumentId(leaveCalendarDocumentId);
323                                                    btf.setBalanceTransfer(balanceTransfer);
324                                                    btf.setTransferAmount(balanceTransfer.getTransferAmount());
325                                                    return forward;
326                                            }
327                                }
328                                throw new RuntimeException("could not initialize balance transfer");
329                        } else {
330                                throw new RuntimeException("unable to fetch the accrual category that triggerred this transfer");
331                }
332                    } else {
333                            throw new RuntimeException("No infractions given");
334            }
335            }
336            
337            public ActionForward approveTimesheet(ActionMapping mapping, ActionForm form,
338                            HttpServletRequest request, HttpServletResponse response)
339                                            throws Exception {
340                    
341                    GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferAmount.adjust");
342                    BalanceTransferForm btf = (BalanceTransferForm) form;
343    
344                    List<LeaveBlock> eligibleTransfers = (List<LeaveBlock>) request.getSession().getAttribute("eligibilities");
345                    if(!eligibleTransfers.isEmpty()) {
346                            
347                            Collections.sort(eligibleTransfers, new Comparator() {
348                                    
349                                    @Override
350                                    public int compare(Object o1, Object o2) {
351                                            LeaveBlock l1 = (LeaveBlock) o1;
352                                            LeaveBlock l2 = (LeaveBlock) o2;
353                                            return l1.getLeaveDate().compareTo(l2.getLeaveDate());
354                                    }
355                                    
356                            });
357                            
358                            //This is the leave calendar document that triggered this balance transfer.
359    
360                            String timesheetDocumentId = request.getParameter("documentId");
361                            ActionForward forward = new ActionForward(mapping.findForward("basic"));
362                            TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentId);
363                            String principalId = tsd == null ? null : tsd.getPrincipalId();
364    
365                            LeaveBlock leaveBlock = eligibleTransfers.get(0);
366                            Date effectiveDate = leaveBlock.getLeaveDate();
367                            String accrualCategoryRuleId = leaveBlock.getAccrualCategoryRuleId();
368                            if(!StringUtils.isBlank(accrualCategoryRuleId)) {
369                                    AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId);
370                                    AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
371                                    BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, leaveBlock.getLeaveDate());
372    
373                                    BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(principalId, accrualCategoryRuleId, accruedBalance, effectiveDate);
374                                    balanceTransfer.setLeaveCalendarDocumentId(timesheetDocumentId);
375            
376                                    if(ObjectUtils.isNotNull(balanceTransfer)) {
377            
378                                            if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
379                                                    // TODO: Redirect user to prompt stating excess leave will be forfeited and ask for confirmation.
380                                                    // Do not submit the object to workflow for this max balance action.
381                                                    balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
382                                                    KRADServiceLocator.getBusinessObjectService().save(balanceTransfer);
383            
384                                                    // May need to update to save the business object to KPME's tables for record keeping.
385                                                    LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
386                                                    forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
387                                                    TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId);
388    
389                                                    if(ObjectUtils.isNotNull(timesheetDocumentId)) {
390                                                            if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
391                                                                            StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
392                                                                    ActionForward loseForward = new ActionForward(mapping.findForward("timesheetTransferSuccess"));
393                                                                    loseForward.setPath(loseForward.getPath()+"?documentId="+timesheetDocumentId+"&action=R&methodToCall=approveTimesheet");
394                                                                    return loseForward;
395                                                            }
396                                                            //on demand handled in separate action forward.
397                                                    }
398            
399                                            } else {
400                                                    btf.setLeaveCalendarDocumentId(timesheetDocumentId);
401                                                    btf.setBalanceTransfer(balanceTransfer);
402                                                    btf.setTransferAmount(balanceTransfer.getTransferAmount());
403                                                    return forward;
404                                            }
405            
406                                    }
407                                    throw new RuntimeException("could not initialize balance transfer");
408    
409                    }
410                    else
411                            throw new RuntimeException("unable to fetch the accrual category that triggerred this transfer");
412                    }
413                    else
414                            throw new RuntimeException("no eligible transfers exist");
415            }
416            
417            public ActionForward closeBalanceTransferDoc(ActionMapping mapping, ActionForm form,
418                            HttpServletRequest request, HttpServletResponse response)
419                            throws Exception {
420                    return mapping.findForward("closeBalanceTransferDoc");
421            }
422            
423            /* Delete system scheduled time off usage leave block from Leave or Time Calendar 
424             */
425            public ActionForward deleteSSTOLeaveBlock(ActionMapping mapping, ActionForm form,
426                            HttpServletRequest request, HttpServletResponse response)
427                            throws Exception {
428                    BalanceTransferForm btf = (BalanceTransferForm) form;
429                    buildBalanceTransferForLeaveBlock(btf, request.getParameter("leaveBlockId"));
430            
431                    return new ActionForward(mapping.findForward("basic"));
432            }
433            
434            /* Build balance transfer based on the to-be-deleted leave block 
435             */
436            private void buildBalanceTransferForLeaveBlock(BalanceTransferForm btf, String lbId) {
437                    LeaveBlock lb = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lbId);
438                    // this leave block is a ssto usage block, need to use it fo find the accrualed leave block which has a positive amount
439                    if(lb == null || StringUtils.isEmpty(lb.getScheduleTimeOffId())) {
440                            throw new RuntimeException("could not find the System Scheduled Time Off leave block that needs to be transferred!");   
441                    }
442                    SystemScheduledTimeOff ssto = TkServiceLocator.getSysSchTimeOffService().getSystemScheduledTimeOff(lb.getScheduleTimeOffId());
443                    BigDecimal amountTransferred = ssto.getTransferConversionFactor() == null ? lb.getLeaveAmount() : lb.getLeaveAmount().multiply(ssto.getTransferConversionFactor());
444                    EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(ssto.getTransfertoEarnCode(), lb.getLeaveDate());
445                    
446                    BalanceTransfer bt = new BalanceTransfer();
447                    bt.setTransferAmount(lb.getLeaveAmount().abs());        // the usage leave block's leave amount is negative
448                    bt.setFromAccrualCategory(lb.getAccrualCategory());
449                    bt.setAmountTransferred(amountTransferred.abs());
450                    bt.setToAccrualCategory(ec.getAccrualCategory());
451                    bt.setSstoId(lb.getScheduleTimeOffId());
452                    bt.setEffectiveDate(lb.getLeaveDate());
453                    bt.setPrincipalId(lb.getPrincipalId());
454                    
455                    btf.setBalanceTransfer(bt);
456                    btf.setTransferAmount(bt.getTransferAmount());
457                    GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferSSTO", 
458                                    bt.getTransferAmount().toString(), bt.getAmountTransferred().toString());
459            }
460            /*
461             * Submit a balance transfer document when deleting a ssto usage leave block from current Leave/time calendar
462             * delete both accrued and usage ssto leave blocks, a pending transferred leave block is created by the BT doc
463             */
464            public ActionForward balanceTransferOnSSTO(ActionMapping mapping, ActionForm form,
465                            HttpServletRequest request, HttpServletResponse response) throws Exception {
466                    BalanceTransferForm btf = (BalanceTransferForm) form;
467                    BalanceTransfer bt = btf.getBalanceTransfer();
468                    
469                    if(StringUtils.isEmpty(bt.getSstoId())) {
470                            throw new RuntimeException("System Scheduled Time Off not found for this balance transfer!");
471                    }
472                    List<LeaveBlock> lbList = TkServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(bt.getPrincipalId(), bt.getSstoId(), bt.getEffectiveDate());
473                    if(CollectionUtils.isEmpty(lbList) || (CollectionUtils.isNotEmpty(lbList) && lbList.size() != 2)) {
474                            throw new RuntimeException("There should be 2 system scheduled time off leave blocks!");
475                    }
476                    TkServiceLocator.getBalanceTransferService().submitToWorkflow(bt);
477                    // delete both SSTO accrualed and usage leave blocks
478                    for(LeaveBlock lb : lbList) {
479                            TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(lb.getLmLeaveBlockId(), lb.getPrincipalId());
480                    }
481                    return mapping.findForward("closeBalanceTransferDoc");
482            }
483    
484    }